AWS Config Rules Blessed with Cloudformation cfn-guard Support!

Cloudformation Guard AWS Config
Photo by Tobias Tullius / Unsplash

They said it was coming, and here it is! Support for defining custom cfn-guard rules for AWS Config via Cloudformation.

Build AWS Config rules using AWS CloudFormation Guard

For some background on cfn-guard and AWS Config, check out this post.

The magic has been enabled within Cloudformation here.

We now have an additional CustomPolicyDetails section as follows

{
  "EnableDebugLogDelivery" : Boolean,
  "PolicyRuntime" : String,
  "PolicyText" : String
}

While I have a fondness for Cloudformation, running up a new resource configuration is a little troublesome so here are some examples.

They are all available in the jonesy1234/cfn-guard-examples GitHub repo and I'll be continuing to populate with more examples. PRs always welcome so go check it out.

SecurityGroupNoIngressAny

This rule will pick up on lazy people who just didn't have the time to work out what was needed.

Rule intent

  • SecurityGroup should not have an ingress rule for 0.0.0.0/0

Expectations

  • SKIP when there are no AWS::EC2::SecurityGroup resources
  • PASS when there are no cidrIp ingress entries to 0.0.0.0/0
  • FAIL otherwise
---
AWSTemplateFormatVersion: 2010-09-09
Description: Example Stack to deploy Guard Security Group Policies

Resources:
  SecurityGroupNoIngressAny: 
    Type: AWS::Config::ConfigRule
    Properties: 
      ConfigRuleName: SecurityGroupNoIngressAny
      Scope: 
        ComplianceResourceTypes: 
          - "AWS::EC2::SecurityGroup"
      Source: 
        Owner: "CUSTOM_POLICY"
        CustomPolicyDetails: 
          EnableDebugLogDelivery: false
          PolicyRuntime: guard-2.x.x
          PolicyText:
            !Sub |
            rule check_resource_type_and_status {
                resourceType == /AWS::EC2::SecurityGroup/
            }

            rule check_no_access_ingress_any when check_resource_type_and_status {
                # 
                # select all cidrIp entries that point to '0.0.0.0/0'
                #
                let any_ip_permissions = configuration.ipPermissions[
                    #
                    # if either ipv4 or ipv6 that allows access from any address
                    #
                    some ipv4Ranges[*].cidrIp == '0.0.0.0/0' or
                    some ipv6Ranges[*].cidrIpv6 == '::/0']
                
                %any_ip_permissions empty
            }
        Owner: CUSTOM_POLICY
        SourceDetails:
          - 
            EventSource: aws.config
            MessageType: ConfigurationItemChangeNotification

SecurityGroupNoEgressAny

This rule will pick up on the annoying default imposed on us from AWS, allowing outbound anything. Please fix that one day...

Rule intent

  • SecurityGroup should not have an egress rule for 0.0.0.0/0

Expectations

  • SKIP when there are no AWS::EC2::SecurityGroup resources
  • PASS when there are no cidrIp egress entries to 0.0.0.0/0 - FAIL otherwise
---
AWSTemplateFormatVersion: 2010-09-09
Description: Example Stack to deploy Guard Security Group Policies

Resources:
  SecurityGroupNoEgressAny: 
    Type: AWS::Config::ConfigRule
    Properties: 
      ConfigRuleName: SecurityGroupNoEgressAny
      Scope: 
        ComplianceResourceTypes: 
          - "AWS::EC2::SecurityGroup"
      Source: 
        Owner: "CUSTOM_POLICY"
        CustomPolicyDetails: 
          EnableDebugLogDelivery: false
          PolicyRuntime: guard-2.x.x
          PolicyText:
            !Sub |
            rule check_resource_type_and_status {
                resourceType == /AWS::EC2::SecurityGroup/
            }
            rule check_no_access_egress_any when check_resource_type_and_status {
                # 
                # select all cidrIp entries that point to '0.0.0.0/0'
                #
                let any_ip_permissions = configuration.ipPermissionsEgress[
                    #
                    # if either ipv4 or ipv6 that allows access from any address
                    #
                    some ipv4Ranges[*].cidrIp == '0.0.0.0/0' or
                    some ipv6Ranges[*].cidrIpv6 == '::/0']
                
                %any_ip_permissions empty
            }
        Owner: CUSTOM_POLICY
        SourceDetails:
          - 
            EventSource: aws.config
            MessageType: ConfigurationItemChangeNotification

SecurityGroupNoIngressAnyMgmtPort

This one already exists in AWS Config as a managed rule; however, adding as a reference.

Rule intent

  • SecurityGroup should not have ingress management ports rule for 0.0.0.0/0

Expectations

  • SKIP when there are no AWS::EC2::SecurityGroup resources
  • PASS when there are no management ports ingress entries to 0.0.0.0/0 - FAIL otherwise
---
AWSTemplateFormatVersion: 2010-09-09
Description: Example Stack to deploy Guard Security Group Policies

Resources:
  SecurityGroupNoIngressAnyMgmtPort: 
    Type: AWS::Config::ConfigRule
    Properties: 
      ConfigRuleName: SecurityGroupNoIngressAnyMgmtPort
      Scope: 
        ComplianceResourceTypes: 
          - "AWS::EC2::SecurityGroup"
      Source: 
        Owner: "CUSTOM_POLICY"
        CustomPolicyDetails: 
          EnableDebugLogDelivery: false
          PolicyRuntime: guard-2.x.x
          PolicyText:
            !Sub |
            rule check_resource_type_and_status {
                resourceType == /AWS::EC2::SecurityGroup/
            }
            rule check_no_access_ingress_any_mgmt_port when check_resource_type_and_status {

                let management_ports = [22, 3389]

                # 
                # select all ipPermission instances that can be reached by ANY IP address
                # IPv4 or IPv6 and not UDP
                #
                let any_ip_permissions = configuration.ipPermissions[
                    #
                    # if either ipv4 or ipv6 that allows access from any address
                    #
                    some ipv4Ranges[*].cidrIp == '0.0.0.0/0' or
                    some ipv6Ranges[*].cidrIpv6 == '::/0'

                    #
                    # the ipProtocol is not UDP
                    #
                    ipProtocol != 'udp' ]
                    
                when %any_ip_permissions !empty
                {
                    %any_ip_permissions {
                        ipProtocol != '-1'
                        when fromPort exists 
                            toPort exists 
                        {
                            let each_any_ip_perm = this
                            %management_ports {
                                this != %each_any_ip_perm.fromPort or
                                this != %each_any_ip_perm.toPort
                            }
                        }
                    }       
                }   
            }
        Owner: CUSTOM_POLICY
        SourceDetails:
          - 
            EventSource: aws.config
            MessageType: ConfigurationItemChangeNotification

SecurityGroupNoIngressAnyMgmtPortRestrictedVpcs

Building on the previous example, we may only want to restrict management ports to VPCs connected to the corporate network. If someone has a detached VPC then maybe we allow direct access to management ports and live dangerously!

Rule intent

  • SecurityGroup should not have ingress management ports rule for 0.0.0.0/0 for restricted VPCs

Expectations

  • SKIP when there are no AWS::EC2::SecurityGroup resources
  • PASS when there are no management ports ingress entries to 0.0.0.0/0 for none restricted VPCs
  • FAIL otherwise
---
AWSTemplateFormatVersion: 2010-09-09
Description: Example Stack to deploy Guard Security Group Policies

Resources:
  SecurityGroupNoIngressAnyMgmtPortRestrictedVpcs: 
    Type: AWS::Config::ConfigRule
    Properties: 
      ConfigRuleName: SecurityGroupNoIngressAnyMgmtPortRestrictedVpcs
      Scope: 
        ComplianceResourceTypes: 
          - "AWS::EC2::SecurityGroup"
      Source: 
        Owner: "CUSTOM_POLICY"
        CustomPolicyDetails: 
          EnableDebugLogDelivery: false
          PolicyRuntime: guard-2.x.x
          PolicyText:
            !Sub |
            rule check_resource_type_and_status {
                resourceType == /AWS::EC2::SecurityGroup/
            }
            rule check_no_access_ingress_any_mgmt_port_restricted_vpcs when check_resource_type_and_status {

                let management_ports = [22, 3389]
                let restricted_vpcs = ['vpc-0cbee09e78864b987']

                # 
                # select only restricted VPCs
                #
                let any_restricted_vpc = configuration[
                    vpcId IN %restricted_vpcs]
                
                when %any_restricted_vpc !empty
                {
                    # 
                    # select all ipPermission instances that can be reached by ANY IP address
                    # IPv4 or IPv6 and not UDP
                    #
                    let any_ip_permissions = configuration.ipPermissions[
                        #
                        # if either ipv4 or ipv6 that allows access from any address
                        #
                        some ipv4Ranges[*].cidrIp == '0.0.0.0/0' or
                        some ipv6Ranges[*].cidrIpv6 == '::/0'

                        #
                        # the ipProtocol is not UDP
                        #
                        ipProtocol != 'udp' ]
                        
                    when %any_ip_permissions !empty
                    {
                        %any_ip_permissions {
                            ipPermissions.ipProtocol != '-1'
                            when ipPermissions.fromPort exists 
                                ipPermissions.toPort exists 
                            {
                                let each_any_ip_perm = this
                                %management_ports {
                                    this != %each_any_ip_perm.ipPermissions.fromPort or
                                    this != %each_any_ip_perm.ipPermissions.toPort
                                }
                            }
                        }       
                    }  
                } 
            }
        Owner: CUSTOM_POLICY
        SourceDetails:
          - 
            EventSource: aws.config
            MessageType: ConfigurationItemChangeNotification

Hope this helps someone else!

Cheers