Security Integration Testing (Part 1): Resource Monitoring with AWS Config Rules

Continuous Security: Security in the Continuous Delivery Pipeline is a series of articles addressing security concerns and testing in the Continuous Delivery pipeline. This is the third article in the series.

Introduction

In a Continuous Delivery pipeline it is imperative to enforce infrastructure security policies and ensure that any new code or infrastructure changes don’t result in security compliance regressions.  The AWS Config service is a great tool for this requirement because it monitors your infrastructure resources for changes, logs the changes, and notifies you when they occur.  In addition, the Config Rules feature of the service allows you to define rules against which each resource change will be evaluated.  This is a very powerful feature that enables you to set up continuous security compliance monitoring to proactively flag security holes related to EC2 instances, security group rules, IAM users, and more.  In addition to continuous monitoring, you can leverage this service inside of your Continuous Delivery pipeline and run tests on-demand, at any stage, to ensure that your new deployments don’t break any security policies.  When violations are detected the test stage will fail and the pipeline will stop.

Stelligent has developed a tool to facilitate the use of the Config service to implement security integration tests as part of your Continuous Delivery pipeline.  In this 3-part blog series I will describe all the concepts and AWS services in play and walkthrough setting up Stelligent’s Config-Rule-Status tool to test the security compliance of AWS resources.  This blog series assumes the reader has experience using the AWS CLI and at least a basic understanding of CloudFormation.  Part 1 deals only with CloudFormation, but subsequent posts will dig into the Config-Rule-Status tool which makes use of other technologies such as Node.js, NPM, Gulp, and the Serverless framework.  Let’s go ahead and jump into Part 1…

Setting up AWS-managed Config Rules to identify security vulnerabilites

Setting up the Config service requires configuring a number of resources across a variety of AWS services.  This blog post will show how to do it using CloudFormation (CFN).  This post will also show how to use CFN to create AWS-managed Config Rules to test security compliance of AWS resources whenever a change is detected by the Config service.

Setting up the Config Service

To get the Config Service turned on and monitoring your resources you will need to create an S3 Bucket, an SNS Topic, and an IAM Role.  In addition, within the Config service itself, you will need to configure a ConfigurationRecorder and a DeliveryChannel.  I have a CFN template file called config-service-resources.json that defines all of these resources.  Let’s take a look at each section…

S3 Bucket – When the Config service is activated it will generate a configuration history file every 6 hours to log any changes that happened during that period, and will also generate a configuration snapshot of all recorded resources.  These logs get persisted as JSON files to an S3 Bucket.  Here is what this bucket definition looks like in the CFN template.  Notice there is a DeletionPolicy set to “Retain”.  This tells CloudFormation to not attempt to delete the bucket if the CFN Stack is deleted.  The Config service always writes encrypted objects to the bucket, but I went ahead and added a BucketPolicy to enforce that standard.

{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Description": "Setup Config Service",
    "Resources": {
        "ConfigBucket": {
            "Type": "AWS::S3::Bucket",
            "DeletionPolicy": "Retain",
            "Properties": {
                "AccessControl": "BucketOwnerFullControl"
            }
        },
        "ConfigBucketPolicy": {
            "Type": "AWS::S3::BucketPolicy",
            "Properties": {
                "Bucket": {
                    "Ref": "ConfigBucket"
                },
                "PolicyDocument": {
                    "Version": "2012-10-17",
                    "Id": "PutObjPolicy",
                    "Statement": [{
                        "Sid": "DenyUnEncryptedObjectUploads",
                        "Effect": "Deny",
                        "Principal": "*",
                        "Action": "s3:PutObject",
                        "Resource": {"Fn::Join":["", ["arn:aws:s3:::", {"Ref": "ConfigBucket"}, "/*"]]},
                        "Condition": {
                            "StringNotEquals": {
                                "s3:x-amz-server-side-encryption": "AES256"
                            }
                        }
                    }]
                }
            }
        },
        "ConfigTopic": {
            "Type": "AWS::SNS::Topic",
            "Properties": {
                "DisplayName": "config-topic",
                "TopicName": "config-topic"
            }
        },

 

SNS Topic – In addition to saving snapshot configuration logs to S3, AWS Config also sends a stream of configuration items to the Simple Notification Service as soon as the resource changes are detected.  This allows other applications to subscribe and be notified as changes occur.  To enable this functionality our CFN template needs to define an SNS Topic to host this configuration stream.

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "Setup Config Service",
  "Resources": {
    "ConfigBucket": {
      "Type": "AWS::S3::Bucket",
      "DeletionPolicy" : "Retain",
      "Properties": {
        "AccessControl": "BucketOwnerFullControl"
      }
    },
    "ConfigTopic": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "DisplayName": "config-topic",
        "TopicName": "config-topic"
      }
    },
    "ConfigRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [
            {
              "Action": [
                "sts:AssumeRole"
              ],
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "config.amazonaws.com"
                ]
              }
            }
          ],
          "Version": "2012-10-17"
        },
        "Path": "/",
        "Policies": [
          {
            "PolicyDocument": {
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": "sns:Publish",
                  "Resource": {
                    "Ref": "ConfigTopic"
                  }
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "s3:PutObject"
                  ],
                  "Resource": [
                    {
                      "Fn::Join": [
                        "",
                        [
                          "arn:aws:s3:::",
                          {
                            "Ref": "ConfigBucket"
                          },
                          "/AWSLogs/",
                          {
                            "Ref": "AWS::AccountId"
                          },
                          "/*"
                        ]
                      ]
                    }
                  ],
                  "Condition": {
                    "StringLike": {
                      "s3:x-amz-acl": "bucket-owner-full-control"
                    }
                  }
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "s3:GetBucketAcl"
                  ],
                  "Resource": {
                    "Fn::Join": [
                      "",
                      [
                        "arn:aws:s3:::",
                        {
                          "Ref": "ConfigBucket"
                        }
                      ]
                    ]
                  }
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "config:Put*",
                    "ec2:Describe*",
                    "iam:List*",
                    "iam:Get*"
                  ],
                  "Resource": "*"
                }
              ]
            },
            "PolicyName": "root"
          }
        ]
      }
    },
    "ConfigRecorder": {
      "Type": "AWS::Config::ConfigurationRecorder",
      "Properties": {
        "Name": "default",
        "RecordingGroup": {
          "ResourceTypes": [
            "AWS::EC2::Instance",
            "AWS::EC2::InternetGateway",
            "AWS::EC2::NetworkAcl",
            "AWS::EC2::NetworkInterface",
            "AWS::EC2::RouteTable",
            "AWS::EC2::SecurityGroup",
            "AWS::EC2::Subnet",
            "AWS::EC2::Volume",
            "AWS::EC2::VPC",
            "AWS::IAM::Policy",
            "AWS::IAM::Role",
            "AWS::IAM::User"
          ]
        },
        "RoleARN": {
          "Fn::GetAtt": [
            "ConfigRole",
            "Arn"
          ]
        }
      }
    },
    "DeliveryChannel": {
      "Type": "AWS::Config::DeliveryChannel",
      "Properties": {
        "ConfigSnapshotDeliveryProperties": {
          "DeliveryFrequency": "Twelve_Hours"
        },
        "S3BucketName": {
          "Ref": "ConfigBucket"
        },
        "SnsTopicARN": {
          "Ref": "ConfigTopic"
        }
      }
    }
  },
  "Outputs":{
    "StackName":{
      "Value":{
        "Ref":"AWS::StackName"
      }
    }
  }
}

 

IAM Role – The role that we define next will get assigned to the ConfigurationRecorder which is described below.  This role defines policies that give the ConfigurationRecorder permission to query information about other AWS Services and write the data to the DeliveryChannel (the S3 bucket and SNS Topic described above).  Also notice the use of “sts:AssumeRole” inside the “AssumeRolePolicyDocument” property.  For more information on Config permissions check out the AWS documentation.

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "Setup Config Service",
  "Resources": {
    "ConfigBucket": {
      "Type": "AWS::S3::Bucket",
      "DeletionPolicy" : "Retain",
      "Properties": {
        "AccessControl": "BucketOwnerFullControl"
      }
    },
    "ConfigTopic": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "DisplayName": "config-topic",
        "TopicName": "config-topic"
      }
    },
    "ConfigRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [
            {
              "Action": ["sts:AssumeRole"],
              "Effect": "Allow",
              "Principal": {
                "Service": ["config.amazonaws.com"]
              }
            }
          ],
          "Version": "2012-10-17"
        },
        "Path": "/",
        "Policies": [
          {
            "PolicyDocument": {
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": "sns:Publish",
                  "Resource": {"Ref": "ConfigTopic"}
                },
                {
                  "Effect": "Allow",
                  "Action": ["s3:PutObject"],
                  "Resource": [
                    {
                      "Fn::Join": [
                        "",
                        ["arn:aws:s3:::",
                          {"Ref": "ConfigBucket"},
                          "/AWSLogs/",
                          {"Ref": "AWS::AccountId"},
                          "/*"
                        ]
                      ]
                    }
                  ],
                  "Condition": {
                    "StringLike": {
                      "s3:x-amz-acl": "bucket-owner-full-control"
                    }
                  }
                },
                {
                  "Effect": "Allow",
                  "Action": ["s3:GetBucketAcl"],
                  "Resource": {
                    "Fn::Join": [
                      "",["arn:aws:s3:::",{"Ref": "ConfigBucket"}]
                    ]
                  }
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "config:Put*",
                    "ec2:Describe*",
                    "iam:List*",
                    "iam:Get*"
                  ],
                  "Resource": "*"
                }
              ]
            },
            "PolicyName": "root"
          }
        ]
      }
    },
    "ConfigRecorder": {
      "Type": "AWS::Config::ConfigurationRecorder",
      "Properties": {
        "Name": "default",
        "RecordingGroup": {
          "ResourceTypes": [
            "AWS::EC2::Instance",
            "AWS::EC2::InternetGateway",
            "AWS::EC2::NetworkAcl",
            "AWS::EC2::NetworkInterface",
            "AWS::EC2::RouteTable",
            "AWS::EC2::SecurityGroup",
            "AWS::EC2::Subnet",
            "AWS::EC2::Volume",
            "AWS::EC2::VPC",
            "AWS::IAM::Policy",
            "AWS::IAM::Role",
            "AWS::IAM::User"
          ]
        },
        "RoleARN": {
          "Fn::GetAtt": [
            "ConfigRole",
            "Arn"
          ]
        }
      }
    },
    "DeliveryChannel": {
      "Type": "AWS::Config::DeliveryChannel",
      "Properties": {
        "ConfigSnapshotDeliveryProperties": {
          "DeliveryFrequency": "Twelve_Hours"
        },
        "S3BucketName": {
          "Ref": "ConfigBucket"
        },
        "SnsTopicARN": {
          "Ref": "ConfigTopic"
        }
      }
    }
  },
  "Outputs":{
    "StackName":{
      "Value":{
        "Ref":"AWS::StackName"
      }
    }
  }
}

 

ConfigurationRecorder – The ConfigurationRecorder is part of the Config service and is the monitoring mechanism for capturing resource changes in the infrastructure.  The IAM Role defined above gives it permission to get the data it needs from the various resources it is monitoring.  The RecordingGroup property shown in the snippet below identifies which resource types the ConfigurationRecorder should be monitoring.  As you can see I have it configured to monitor a variety of EC2 and IAM resource types related to implementing security policies.

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "Setup Config Service",
  "Resources": {
    "ConfigBucket": {
      "Type": "AWS::S3::Bucket",
      "DeletionPolicy" : "Retain",
      "Properties": {
        "AccessControl": "BucketOwnerFullControl"
      }
    },
    "ConfigTopic": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "DisplayName": "config-topic",
        "TopicName": "config-topic"
      }
    },
    "ConfigRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [
            {
              "Action": ["sts:AssumeRole"],
              "Effect": "Allow",
              "Principal": {
                "Service": ["config.amazonaws.com"]
              }
            }
          ],
          "Version": "2012-10-17"
        },
        "Path": "/",
        "Policies": [
          {
            "PolicyDocument": {
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": "sns:Publish",
                  "Resource": {"Ref": "ConfigTopic"}
                },
                {
                  "Effect": "Allow",
                  "Action": ["s3:PutObject"],
                  "Resource": [
                    {
                      "Fn::Join": [
                        "",
                        ["arn:aws:s3:::",
                          {"Ref": "ConfigBucket"},
                          "/AWSLogs/",
                          {"Ref": "AWS::AccountId"},
                          "/*"
                        ]
                      ]
                    }
                  ],
                  "Condition": {
                    "StringLike": {
                      "s3:x-amz-acl": "bucket-owner-full-control"
                    }
                  }
                },
                {
                  "Effect": "Allow",
                  "Action": ["s3:GetBucketAcl"],
                  "Resource": {
                    "Fn::Join": [
                      "",["arn:aws:s3:::",{"Ref": "ConfigBucket"}]
                    ]
                  }
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "config:Put*",
                    "ec2:Describe*",
                    "iam:List*",
                    "iam:Get*"
                  ],
                  "Resource": "*"
                }
              ]
            },
            "PolicyName": "root"
          }
        ]
      }
    },
    "ConfigRecorder": {
      "Type": "AWS::Config::ConfigurationRecorder",
      "Properties": {
        "Name": "default",
        "RecordingGroup": {
          "ResourceTypes": [
            "AWS::EC2::Instance",
            "AWS::EC2::InternetGateway",
            "AWS::EC2::NetworkAcl",
            "AWS::EC2::NetworkInterface",
            "AWS::EC2::RouteTable",
            "AWS::EC2::SecurityGroup",
            "AWS::EC2::Subnet",
            "AWS::EC2::Volume",
            "AWS::EC2::VPC",
            "AWS::IAM::Policy",
            "AWS::IAM::Role",
            "AWS::IAM::User"
          ]
        },
        "RoleARN": {
          "Fn::GetAtt": ["ConfigRole","Arn"]
        }
      }
    },
    "DeliveryChannel": {
      "Type": "AWS::Config::DeliveryChannel",
      "Properties": {
        "ConfigSnapshotDeliveryProperties": {
          "DeliveryFrequency": "Twelve_Hours"
        },
        "S3BucketName": {
          "Ref": "ConfigBucket"
        },
        "SnsTopicARN": {
          "Ref": "ConfigTopic"
        }
      }
    }
  },
  "Outputs":{
    "StackName":{
      "Value":{
        "Ref":"AWS::StackName"
      }
    }
  }
}

 

Delivery Channel – The last section we want to analyze in this CFN template is for the Delivery Channel.  This section simply tells the Config service which S3 Bucket and SNS Topic to use when delivering snapshots and streams.  It also defines the delivery frequency for the snapshots.

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "Setup Config Service",
  "Resources": {
    "ConfigBucket": {
      "Type": "AWS::S3::Bucket",
      "DeletionPolicy" : "Retain",
      "Properties": {
        "AccessControl": "BucketOwnerFullControl"
      }
    },
    "ConfigTopic": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "DisplayName": "config-topic",
        "TopicName": "config-topic"
      }
    },
    "ConfigRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [
            {
              "Action": ["sts:AssumeRole"],
              "Effect": "Allow",
              "Principal": {
                "Service": ["config.amazonaws.com"]
              }
            }
          ],
          "Version": "2012-10-17"
        },
        "Path": "/",
        "Policies": [
          {
            "PolicyDocument": {
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": "sns:Publish",
                  "Resource": {"Ref": "ConfigTopic"}
                },
                {
                  "Effect": "Allow",
                  "Action": ["s3:PutObject"],
                  "Resource": [
                    {
                      "Fn::Join": [
                        "",
                        ["arn:aws:s3:::",
                          {"Ref": "ConfigBucket"},
                          "/AWSLogs/",
                          {"Ref": "AWS::AccountId"},
                          "/*"
                        ]
                      ]
                    }
                  ],
                  "Condition": {
                    "StringLike": {
                      "s3:x-amz-acl": "bucket-owner-full-control"
                    }
                  }
                },
                {
                  "Effect": "Allow",
                  "Action": ["s3:GetBucketAcl"],
                  "Resource": {
                    "Fn::Join": [
                      "",["arn:aws:s3:::",{"Ref": "ConfigBucket"}]
                    ]
                  }
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "config:Put*",
                    "ec2:Describe*",
                    "iam:List*",
                    "iam:Get*"
                  ],
                  "Resource": "*"
                }
              ]
            },
            "PolicyName": "root"
          }
        ]
      }
    },
    "ConfigRecorder": {
      "Type": "AWS::Config::ConfigurationRecorder",
      "Properties": {
        "Name": "default",
        "RecordingGroup": {
          "ResourceTypes": [
            "AWS::EC2::Instance",
            "AWS::EC2::InternetGateway",
            "AWS::EC2::NetworkAcl",
            "AWS::EC2::NetworkInterface",
            "AWS::EC2::RouteTable",
            "AWS::EC2::SecurityGroup",
            "AWS::EC2::Subnet",
            "AWS::EC2::Volume",
            "AWS::EC2::VPC",
            "AWS::IAM::Policy",
            "AWS::IAM::Role",
            "AWS::IAM::User"
          ]
        },
        "RoleARN": {
          "Fn::GetAtt": ["ConfigRole","Arn"]
        }
      }
    },
    "DeliveryChannel": {
      "Type": "AWS::Config::DeliveryChannel",
      "Properties": {
        "ConfigSnapshotDeliveryProperties": {
          "DeliveryFrequency": "Twelve_Hours"
        },
        "S3BucketName": {
          "Ref": "ConfigBucket"
        },
        "SnsTopicARN": {
          "Ref": "ConfigTopic"
        }
      }
    }
  },
  "Outputs":{
    "StackName":{
      "Value":{"Ref":"AWS::StackName"}
    }
  }
}

 

Creating AWS-managed Config Rules

There are two types of Config Rules:  AWS-managed rules, and custom Lambda-backed rules.  I’ll cover Lambda-backed rules in Part 2 of this blog series, but let’s first focus on AWS-managed rules.

AWS has predefined several Config rules that can be easily used to test some common configuration best practices.  The full list of managed rules can be found in the AWS Documentation.  In my CFN template I have 3 AWS-managed rules plus several custom Lambda-backed rules.  The snippet below shows the resource definition for one of the managed rules.  This rule will test each EC2 instance to make sure it is inside a VPC.  If any instances are not then the status of the Config rule will be “NON_COMPLIANT”.

{
  "AWSTemplateFormatVersion":"2010-09-09",
  "Description":"Creates Config rules and permissions",
  "Parameters": {...},
  "Resources":{
    "ec2VPCRule":{
      "Type":"AWS::Config::ConfigRule",
      "Properties":{
        "ConfigRuleName":"ConfigRuleStatus-EC2-VPC-Rule",
        "Description": "Checks whether your EC2 instances belong to a
                        virtual private cloud (VPC).",
        "Scope":{
          "ComplianceResourceTypes":[
          ]
        },
        "Source":{
          "Owner":"AWS",
          "SourceDetails":[],
          "SourceIdentifier":"INSTANCES_IN_VPC"
        }
      }
    },
    "ec2SSHRule":{
      "Type":"AWS::Config::ConfigRule",
      "Properties":{
        "ConfigRuleName":"ConfigRuleStatus-EC2-SSH-Rule",
        "Description": "Checks whether security groups that are in use disallow unrestricted incoming SSH traffic.",
        "Scope":{
          "ComplianceResourceTypes":[
          ]
        },
        "Source":{
          "Owner":"AWS",
          "SourceDetails":[],
          "SourceIdentifier":"INCOMING_SSH_DISABLED"
        }
      }
    },
    "ec2EncryptionRule":{
      "Type":"AWS::Config::ConfigRule",
      "Properties":{
        "ConfigRuleName":"ConfigRuleStatus-EC2-Encryption-Rule",
        "Description": "Checks whether EBS volumes that are in an attached state are encrypted.",
        "Scope":{
          "ComplianceResourceTypes":[
          ]
        },
        "Source":{
          "Owner":"AWS",
          "SourceDetails":[],
          "SourceIdentifier":"ENCRYPTED_VOLUMES"
        }
      }
    },
    "iamMFARule":{
      "Type":"AWS::Config::ConfigRule",
      "Properties":{
        "ConfigRuleName":"ConfigRuleStatus-IAM-MFA-Rule",
        "Description": "Checks whether Users have an MFA Device configured.",
        "Scope":{
          "ComplianceResourceTypes":[
            "AWS::IAM::User"
          ]
        },
        "Source":{
          "Owner":"CUSTOM_LAMBDA",
          "SourceDetails":[
            {
              "EventSource":"aws.config",
              "MessageType":"ConfigurationItemChangeNotification"
            }
          ],
          "SourceIdentifier":{"Fn::Join":["", [
            { "Ref" : "LambdaArnBase" },
            {"Fn::Join":[":",[
              {"Ref": "AWS::Region"},
              {"Ref": "AWS::AccountId"}]]},
            {"Ref": "LambdaArnFunctionPrefix"},
            {"Fn::Join":[":",["-userMFA", {"Ref": "LambdaStage"} ]]}
          ]
          ]}
        }
      },
      "DependsOn":"iamMFAPerm"
    },
    "iamUserInlinePolicyRule":{
      "Type":"AWS::Config::ConfigRule",
      "Properties":{
        "ConfigRuleName":"ConfigRuleStatus-IAM-User-InlinePolicy-Rule",
        "Description": "Checks whether Users have an inline policy.",
        "Scope":{
          "ComplianceResourceTypes":[
            "AWS::IAM::User"
          ]
        },
        "Source":{
          "Owner":"CUSTOM_LAMBDA",
          "SourceDetails":[
            {
              "EventSource":"aws.config",
              "MessageType":"ConfigurationItemChangeNotification"
            }
          ],
          "SourceIdentifier":{"Fn::Join":["", [
            { "Ref" : "LambdaArnBase" },
            {"Fn::Join":[":",[
              {"Ref": "AWS::Region"},
              {"Ref": "AWS::AccountId"}]]},
            {"Ref": "LambdaArnFunctionPrefix"},
            {"Fn::Join":[":",["-userInlinePolicy", {"Ref": "LambdaStage"} ]]}
          ]
          ]}
        }
      },
      "DependsOn":"iamUserInlinePolicyPerm"
    },
    "iamUserManagedPolicyRule":{
      "Type":"AWS::Config::ConfigRule",
      "Properties":{
        "ConfigRuleName":"ConfigRuleStatus-IAM-User-ManagedPolicy-Rule",
        "Description": "Checks whether Users have a managed policy directly attached.",
        "Scope":{
          "ComplianceResourceTypes":[
            "AWS::IAM::User"
          ]
        },
        "Source":{
          "Owner":"CUSTOM_LAMBDA",
          "SourceDetails":[
            {
              "EventSource":"aws.config",
              "MessageType":"ConfigurationItemChangeNotification"
            }
          ],
          "SourceIdentifier":{"Fn::Join":["", [
            { "Ref" : "LambdaArnBase" },
            {"Fn::Join":[":",[
              {"Ref": "AWS::Region"},
              {"Ref": "AWS::AccountId"}]]},
            {"Ref": "LambdaArnFunctionPrefix"},
            {"Fn::Join":[":",["-userManagedPolicy", {"Ref": "LambdaStage"} ]]}
          ]
          ]}
        }
      },
      "DependsOn":"iamUserManagedPolicyPerm"
    },
    "ec2SecGrpCidrIngressRule":{
      "Type":"AWS::Config::ConfigRule",
      "Properties":{
        "ConfigRuleName":"ConfigRuleStatus-EC2-SecGrp-Cidr-Ingress-Rule",
        "Description": "Checks whether a Security Group has an ingress rule with a CIDR range that disallows unrestricted traffic and applies to a single host.",
        "Scope":{
          "ComplianceResourceTypes":[
            "AWS::EC2::SecurityGroup"
          ]
        },
        "Source":{
          "Owner":"CUSTOM_LAMBDA",
          "SourceDetails":[
            {
              "EventSource":"aws.config",
              "MessageType":"ConfigurationItemChangeNotification"
            }
          ],
          "SourceIdentifier":{"Fn::Join":["", [
            { "Ref" : "LambdaArnBase" },
            {"Fn::Join":[":",[
              {"Ref": "AWS::Region"},
              {"Ref": "AWS::AccountId"}]]},
            {"Ref": "LambdaArnFunctionPrefix"},
            {"Fn::Join":[":",["-cidrIngress", {"Ref": "LambdaStage"} ]]}
          ]
          ]}
        }
      },
      "DependsOn":"ec2SecGrpCidrIngressPerm"
    },
    "ec2SecGrpCidrEgressRule":{
      "Type":"AWS::Config::ConfigRule",
      "Properties":{
        "ConfigRuleName":"ConfigRuleStatus-EC2-SecGrp-Cidr-Egress-Rule",
        "Description": "Checks whether a Security Group has an egress rule with a CIDR range that disallows unrestricted traffic and applies to a single host.",
        "Scope":{
          "ComplianceResourceTypes":[
            "AWS::EC2::SecurityGroup"
          ]
        },
        "Source":{
          "Owner":"CUSTOM_LAMBDA",
          "SourceDetails":[
            {
              "EventSource":"aws.config",
              "MessageType":"ConfigurationItemChangeNotification"
            }
          ],
          "SourceIdentifier":{"Fn::Join":["", [
            { "Ref" : "LambdaArnBase" },
            {"Fn::Join":[":",[
              {"Ref": "AWS::Region"},
              {"Ref": "AWS::AccountId"}]]},
            {"Ref": "LambdaArnFunctionPrefix"},
            {"Fn::Join":[":",["-cidrEgress", {"Ref": "LambdaStage"} ]]}
          ]
          ]}
        }
      },
      "DependsOn":"ec2SecGrpCidrEgressPerm"
    },
    "iamMFAPerm":{
      "Type":"AWS::Lambda::Permission",
      "Properties":{
        "FunctionName":{"Fn::Join":["", [
          { "Ref" : "LambdaArnBase" },
          {"Fn::Join":[":",[
            {"Ref": "AWS::Region"},
            {"Ref": "AWS::AccountId"}]]},
          {"Ref": "LambdaArnFunctionPrefix"},
          {"Fn::Join":[":",["-userMFA", {"Ref": "LambdaStage"} ]]}
        ]
        ]},
        "Action":"lambda:InvokeFunction",
        "Principal":"config.amazonaws.com"
      }
    },
    "iamUserInlinePolicyPerm":{
      "Type":"AWS::Lambda::Permission",
      "Properties":{
        "FunctionName":{"Fn::Join":["", [
          { "Ref" : "LambdaArnBase" },
          {"Fn::Join":[":",[
            {"Ref": "AWS::Region"},
            {"Ref": "AWS::AccountId"}]]},
          {"Ref": "LambdaArnFunctionPrefix"},
          {"Fn::Join":[":",["-userInlinePolicy", {"Ref": "LambdaStage"} ]]}
        ]
        ]},
        "Action":"lambda:InvokeFunction",
        "Principal":"config.amazonaws.com"
      }
    },
    "iamUserManagedPolicyPerm":{
      "Type":"AWS::Lambda::Permission",
      "Properties":{
        "FunctionName":{"Fn::Join":["", [
          { "Ref" : "LambdaArnBase" },
          {"Fn::Join":[":",[
            {"Ref": "AWS::Region"},
            {"Ref": "AWS::AccountId"}]]},
          {"Ref": "LambdaArnFunctionPrefix"},
          {"Fn::Join":[":",["-userManagedPolicy", {"Ref": "LambdaStage"} ]]}
        ]
        ]},
        "Action":"lambda:InvokeFunction",
        "Principal":"config.amazonaws.com"
      }
    },
    "ec2SecGrpCidrIngressPerm":{
      "Type":"AWS::Lambda::Permission",
      "Properties":{
        "FunctionName":{"Fn::Join":["", [
          { "Ref" : "LambdaArnBase" },
          {"Fn::Join":[":",[
            {"Ref": "AWS::Region"},
            {"Ref": "AWS::AccountId"}]]},
          {"Ref": "LambdaArnFunctionPrefix"},
          {"Fn::Join":[":",["-cidrIngress", {"Ref": "LambdaStage"} ]]}
        ]
        ]},
        "Action":"lambda:InvokeFunction",
        "Principal":"config.amazonaws.com"
      }
    },
    "ec2SecGrpCidrEgressPerm":{
      "Type":"AWS::Lambda::Permission",
      "Properties":{
        "FunctionName":{"Fn::Join":["", [
          { "Ref" : "LambdaArnBase" },
          {"Fn::Join":[":",[
            {"Ref": "AWS::Region"},
            {"Ref": "AWS::AccountId"}]]},
          {"Ref": "LambdaArnFunctionPrefix"},
          {"Fn::Join":[":",["-cidrEgress", {"Ref": "LambdaStage"} ]]}
        ]
        ]},
        "Action":"lambda:InvokeFunction",
        "Principal":"config.amazonaws.com"
      }
    }
  },
  "Outputs":{
    "StackName":{
      "Value":{
        "Ref":"AWS::StackName"
      }
    }
  }
}

Viewing Config settings in AWS Console

So far we’ve looked at two CFN templates:  config-service-resources.json to set up the Config service and it’s dependencies, and config-rule-resources.json to create Config rules.  These template files define CloudFormation stacks that can be created using the AWS Console, the AWS CLI, or any of the SDKs.  Later in the blog series I’ll show how these CFN templates can be integrated into a larger project to help manage and automate deployments, but at this point we’ll just look at what we should now see in the AWS Console after running these stacks.

When you run your stack template in CloudFormation, you’ll see in the Events tab that your declared resources get created.

config-cfn-log

Next, navigate to the Config service which is listed under Management Tools.

config-navigation

Under the Settings tab we should now see that the Config service is recording the Resource Types we identified in the CFN template and logging and streaming to our configured S3 Bucket and SNS Topic respectively.

config-settings

Next you can check to verify that the Config Rules were created by clicking on the Rules tab.

config-rules

And you can click the rule name to drill down and see the resources that were evaluated by this rule.

config-rule-details

Drilling down into an individual resource will then take you to it’s configuration timeline which provides a nice visual representation of changes over time as well as links to any related resources.

config-resource-timeline

Clicking on one of the Relationship links will navigate you to the timeline screen for that resource.  Here we can see that the Security Group related to the EC2 instance has had several configuration changes which are easily viewable in the timeline.  You can also see the details of what changed at any point in the timeline.  Here we can see that this Security Group was configured with a Network Interface at 9:20 PM on September 8.

config-resource-timeline2

Wrapping Up

You’ve just learned how to setup resource monitoring with AWS Config and test for certain security issues using AWS-managed Config Rules.  We covered how to set this up as CloudFormation stacks and then got familiar with the Console interface for the service.  In order to leverage Config’s powerful monitoring capabilities in a Continuous Delivery pipeline we’ll need a programmatic way to interact with the service.  All of the information in the Console is also available via the AWS CLI and SDKs, but we’ll go a step further and use a higher level framework for interfacing with the Config Service and testing our infrastructure’s security compliance.  We’ll take a deep dive into this topic in Parts 2 and 3.  Stay Tuned!

Stelligent is hiring! Do you enjoy working on complex problems like security in the CD pipeline? Do you believe in the “everything-as-code” mantra? If your skills and interests lie at the intersection of DevOps automation and the AWS cloud, check out the careers page on our website.

Leave a Reply