Overview

A configuration package to monitor VPC related API activity as well as configuration compliance rules to ensure the security of VPC configuration. The package includes:

Configure & Deploy

Configuration Presets

  • Enables AWS CloudTrail with streaming to a CloudWatch Log Group: Required to configure CloudWatch Alarms based on CloudTrail metrics
  • Enables AWS Config: Required to enable AWS Config Rules for continuous compliance monitoring
  • A shared S3 bucket is configured to store logs from AWS CloudTrail and AWS Config
  • Enables selected AWS Config rules and CloudWatch Alarms for notifications on important API activity.

Configuration Template

EDIT
EDIT
EDIT
EDIT
EDIT
EDIT
EDIT
EDIT
EDIT
EDIT
EDIT
EDIT
EDIT
EDIT
EDIT
EDIT
Items
23
Size
17.6 KB
AWSTemplateFormatVersion: '2010-09-09'
Description: ''
Resources:
  BucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket:
        Ref: S3SharedBucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Principal:
              Service:
                - cloudtrail.amazonaws.com
                - config.amazonaws.com
            Action:
              - 's3:GetBucketAcl'
            Resource:
              - 'Fn::GetAtt':
                  - S3SharedBucket
                  - Arn
            Effect: Allow
            Condition: {}
          - Principal:
              Service:
                - cloudtrail.amazonaws.com
                - config.amazonaws.com
            Action:
              - 's3:PutObject'
            Resource:
              - 'Fn::Join':
                  - ''
                  - - ''
                    - 'Fn::GetAtt':
                        - S3SharedBucket
                        - Arn
                    - /*
            Effect: Allow
            Condition:
              StringEquals:
                's3:x-amz-acl': bucket-owner-full-control
    DependsOn: S3SharedBucket
  CloudTrail:
    Type: 'AWS::CloudTrail::Trail'
    Properties:
      TrailName: ManagementEventsTrail
      IsLogging: true
      EnableLogFileValidation: true
      EventSelectors:
        - IncludeManagementEvents: true
          ReadWriteType: All
      IsMultiRegionTrail: true
      IncludeGlobalServiceEvents: true
      S3BucketName:
        Ref: S3SharedBucket
      CloudWatchLogsLogGroupArn:
        'Fn::GetAtt':
          - CWLogGroupForCloudTrail
          - Arn
      CloudWatchLogsRoleArn:
        'Fn::GetAtt':
          - IamRoleForCwLogs
          - Arn
    DependsOn:
      - BucketPolicy
  IamRoleForCwLogs:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: iamRoleCloudTrailToCloudWatchLogs
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: ''
            Effect: Allow
            Principal:
              Service: cloudtrail.amazonaws.com
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: allow-access-to-cw-logs
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                Resource: '*'
  CWLogGroupForCloudTrail:
    Type: 'AWS::Logs::LogGroup'
    Properties:
      LogGroupName: CloudTrailLogs
      RetentionInDays: 90
  ConfigurationRecorder:
    Type: 'AWS::Config::ConfigurationRecorder'
    Properties:
      RoleARN:
        'Fn::GetAtt':
          - IamRoleForAwsConfig
          - Arn
      RecordingGroup:
        AllSupported: true
        IncludeGlobalResourceTypes: true
  DeliveryChannel:
    Type: 'AWS::Config::DeliveryChannel'
    Properties:
      S3BucketName:
        Ref: S3SharedBucket
  IamRoleForAwsConfig:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: iamRoleForAWSConfig
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AWSConfigRole'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: ''
            Effect: Allow
            Principal:
              Service: config.amazonaws.com
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: allow-access-to-config-s3-bucket
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 's3:PutObject'
                Resource:
                  - 'arn:aws:s3:::/prefix/AWSLogs/*'
                Condition:
                  StringLike:
                    's3:x-amz-acl': bucket-owner-full-control
              - Effect: Allow
                Action:
                  - 's3:GetBucketAcl'
                Resource: 'arn:aws:s3:::'
  S3SharedBucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      LoggingConfiguration: {}
      AccessControl: LogDeliveryWrite
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
  SnsTopic1:
    Type: 'AWS::SNS::Topic'
    Properties:
      Subscription:
        - Endpoint: email@example.com
          Protocol: email
      TopicName: sns-topic
  ConfigRule1:
    Type: 'AWS::Config::ConfigRule'
    Properties:
      ConfigRuleName: restricted-ssh
      Description: >-
        A Config rule that checks whether security groups in use do not allow
        restricted incoming SSH traffic. This rule applies only to IPv4.
      Scope:
        ComplianceResourceTypes:
          - 'AWS::EC2::SecurityGroup'
      Source:
        Owner: AWS
        SourceIdentifier: INCOMING_SSH_DISABLED
    DependsOn:
      - ConfigurationRecorder
  ConfigRule2:
    Type: 'AWS::Config::ConfigRule'
    Properties:
      ConfigRuleName: restricted-common-ports
      Description: >-
        A Config rule that checks whether security groups in use do not allow
        restricted incoming TCP traffic to the specified ports. This rule
        applies only to IPv4.
      Scope:
        ComplianceResourceTypes:
          - 'AWS::EC2::SecurityGroup'
      InputParameters:
        blockedPort1: '20'
        blockedPort2: '21'
        blockedPort3: '3389'
        blockedPort4: '3306'
        blockedPort5: '4333'
      Source:
        Owner: AWS
        SourceIdentifier: RESTRICTED_INCOMING_TRAFFIC
    DependsOn:
      - ConfigurationRecorder
  ConfigRule3:
    Type: 'AWS::Config::ConfigRule'
    Properties:
      ConfigRuleName: ec2_vpc_public_subnet
      Description: A Config rule that checks that no EC2 Instances are in Public Subnet.
      Scope:
        ComplianceResourceTypes:
          - 'AWS::EC2::Instance'
      Source:
        Owner: CUSTOM_LAMBDA
        SourceIdentifier:
          'Fn::GetAtt':
            - LambdaFunctionForConfigRule3
            - Arn
        SourceDetails:
          - EventSource: aws.config
            MessageType: ConfigurationItemChangeNotification
          - EventSource: aws.config
            MessageType: OversizedConfigurationItemChangeNotification
    DependsOn:
      - ConfigurationRecorder
  LambdaInvokePermissionsConfigRule3:
    Type: 'AWS::Lambda::Permission'
    Properties:
      FunctionName:
        'Fn::GetAtt':
          - LambdaFunctionForConfigRule3
          - Arn
      Action: 'lambda:InvokeFunction'
      Principal: config.amazonaws.com
  LambdaFunctionForConfigRule3:
    Type: 'AWS::Lambda::Function'
    Properties:
      FunctionName: LambdaForec2_vpc_public_subnet
      Handler: index.lambda_handler
      Role:
        'Fn::GetAtt':
          - LambdaIamRoleConfigRule3
          - Arn
      Runtime: python3.6
      Code:
        ZipFile:
          'Fn::Join':
            - |+

            - - ''
              - '#'
              - >-
                # This file made available under CC0 1.0 Universal
                (https://creativecommons.org/publicdomain/zero/1.0/legalcode)
              - '#'
              - '# Description: Check that no EC2 Instances are in Public Subnet'
              - '#'
              - '# Trigger Type: Change Triggered'
              - '# Scope of Changes: EC2:Instance'
              - '# Accepted Parameters: None'
              - >-
                # Your Lambda function execution role will need to have a policy
                that provides the appropriate
              - >-
                # permissions.  Here is a policy that you can consider.  You
                should validate this for your own
              - '# environment'
              - '#{'
              - '#    "Version": "2012-10-17",'
              - '#    "Statement": ['
              - '#        {'
              - '#            "Effect": "Allow",'
              - '#            "Action": ['
              - '#                "logs:CreateLogGroup",'
              - '#                "logs:CreateLogStream",'
              - '#                "logs:PutLogEvents"'
              - '#            ],'
              - '#            "Resource": "arn:aws:logs:*:*:*"'
              - '#        },'
              - '#        {'
              - '#            "Effect": "Allow",'
              - '#            "Action": ['
              - '#                "config:PutEvaluations",'
              - '#                "ec2:DescribeRouteTables"'
              - '#            ],'
              - '#            "Resource": "*"'
              - '#        }'
              - '#    ]'
              - '#}'
              - '#'
              - ''
              - import boto3
              - import botocore
              - import json
              - import logging
              - ''
              - log = logging.getLogger()
              - log.setLevel(logging.INFO)
              - ''
              - 'def evaluate_compliance(configuration_item):'
              - '    subnet_id   = configuration_item["configuration"]["subnetId"]'
              - '    vpc_id      = configuration_item["configuration"]["vpcId"]'
              - '    client      = boto3.client("ec2");'
              - ''
              - '    response    = client.describe_route_tables()'
              - ''
              - '    # If the subnet is explicitly associated to a route table, check if there'
              - '    # is a public route. If no explicit association exists, check if the main'
              - '    # route table has a public route.'
              - ''
              - '    private = True'
              - '    mainTableIsPublic = False'
              - '    noExplicitAssociationFound = True'
              - '    explicitAssocationIsPublic = False'
              - ''
              - '    for i in response[''RouteTables'']:'
              - '        if i[''VpcId''] == vpc_id:'
              - '            for j in i[''Associations'']:'
              - '                if j[''Main''] == True:'
              - '                    for k in i[''Routes'']:'
              - '                        if k[''DestinationCidrBlock''] == ''0.0.0.0/0'' or k[''GatewayId''].startswith(''igw-''):'
              - '                            mainTableIsPublic = True'
              - '                else:'
              - '                    if j[''SubnetId''] == subnet_id:'
              - '                        noExplicitAssociationFound = False'
              - '                        for k in i[''Routes'']:'
              - '                            if k[''DestinationCidrBlock''] == ''0.0.0.0/0'' or k[''GatewayId''].startswith(''igw-''):'
              - '                                explicitAssocationIsPublic = True'
              - ''
              - '    if (mainTableIsPublic and noExplicitAssociationFound) or explicitAssocationIsPublic:'
              - '        private = False'
              - ''
              - '    if private:'
              - '        return {'
              - '            "compliance_type": "COMPLIANT",'
              - '            "annotation": ''Its in private subnet'''
              - '        }'
              - '    else:'
              - '        return {'
              - '            "compliance_type" : "NON_COMPLIANT",'
              - '            "annotation" : ''Not in private subnet'''
              - '        }'
              - ''
              - 'def lambda_handler(event, context):'
              - '    log.debug(''Event %s'', event)'
              - '    invoking_event      = json.loads(event[''invokingEvent''])'
              - '    configuration_item  = invoking_event["configurationItem"]'
              - '    evaluation          = evaluate_compliance(configuration_item)'
              - '    config              = boto3.client(''config'')'
              - ''
              - '    response = config.put_evaluations('
              - '       Evaluations=['
              - '           {'
              - '               ''ComplianceResourceType'':    invoking_event[''configurationItem''][''resourceType''],'
              - '               ''ComplianceResourceId'':      invoking_event[''configurationItem''][''resourceId''],'
              - '               ''ComplianceType'':            evaluation["compliance_type"],'
              - '               "Annotation":                evaluation["annotation"],'
              - '               ''OrderingTimestamp'':         invoking_event[''configurationItem''][''configurationItemCaptureTime'']'
              - '           },'
              - '       ],'
              - '       ResultToken=event[''resultToken''])'
              - ''
      Timeout: 300
    DependsOn: LambdaIamRoleConfigRule3
  LambdaIamRoleConfigRule3:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: IAMRoleForec2_vpc_public_subnet
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess'
        - 'arn:aws:iam::aws:policy/service-role/AWSConfigRulesExecutionRole'
        - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
      Policies: []
  CwAlarm1:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      AlarmName: igw_changes
      AlarmDescription: >-
        A CloudWatch Alarm that triggers when changes are made to an Internet
        Gateway in a VPC.
      MetricName: GatewayEventCount
      Namespace: CloudTrailMetrics
      Statistic: Sum
      Period: '300'
      EvaluationPeriods: '1'
      Threshold: '1'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      AlarmActions:
        - Ref: SnsTopic1
      TreatMissingData: notBreaching
  MetricFilter1:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      LogGroupName:
        Ref: CWLogGroupForCloudTrail
      FilterPattern: >-
        { ($.eventName = CreateCustomerGateway) || ($.eventName =
        DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) ||
        ($.eventName = CreateInternetGateway) || ($.eventName =
        DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }
      MetricTransformations:
        - MetricValue: '1'
          MetricNamespace: CloudTrailMetrics
          MetricName: GatewayEventCount
  CwAlarm2:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      AlarmName: vpc_changes
      AlarmDescription: A CloudWatch Alarm that triggers when changes are made to a VPC.
      MetricName: VpcEventCount
      Namespace: CloudTrailMetrics
      Statistic: Sum
      Period: '300'
      EvaluationPeriods: '1'
      Threshold: '1'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      AlarmActions:
        - Ref: SnsTopic1
      TreatMissingData: notBreaching
  MetricFilter2:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      LogGroupName:
        Ref: CWLogGroupForCloudTrail
      FilterPattern: >-
        { ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName
        = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) ||
        ($.eventName = CreateVpcPeeringConnection) || ($.eventName =
        DeleteVpcPeeringConnection) || ($.eventName =
        RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) ||
        ($.eventName = DetachClassicLinkVpc) || ($.eventName =
        DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }
      MetricTransformations:
        - MetricValue: '1'
          MetricNamespace: CloudTrailMetrics
          MetricName: VpcEventCount
  CwAlarm3:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      AlarmName: securitygroup_changes
      AlarmDescription: >-
        A CloudWatch Alarm that triggers when changes are made to Security
        Groups.
      MetricName: SecurityGroupEventCount
      Namespace: CloudTrailMetrics
      Statistic: Sum
      Period: '300'
      EvaluationPeriods: '1'
      Threshold: '1'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      AlarmActions:
        - Ref: SnsTopic1
      TreatMissingData: notBreaching
  MetricFilter3:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      LogGroupName:
        Ref: CWLogGroupForCloudTrail
      FilterPattern: >-
        { ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName =
        AuthorizeSecurityGroupEgress) || ($.eventName =
        RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress)
        || ($.eventName = CreateSecurityGroup) || ($.eventName =
        DeleteSecurityGroup) }
      MetricTransformations:
        - MetricValue: '1'
          MetricNamespace: CloudTrailMetrics
          MetricName: SecurityGroupEventCount
  CwAlarm4:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      AlarmName: nacl_changes
      AlarmDescription: A CloudWatch Alarm that triggers when changes are made to Network ACLs.
      MetricName: NetworkAclEventCount
      Namespace: CloudTrailMetrics
      Statistic: Sum
      Period: '300'
      EvaluationPeriods: '1'
      Threshold: '1'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      AlarmActions:
        - Ref: SnsTopic1
      TreatMissingData: notBreaching
  MetricFilter4:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      LogGroupName:
        Ref: CWLogGroupForCloudTrail
      FilterPattern: >-
        { ($.eventName = CreateNetworkAcl) || ($.eventName =
        CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) ||
        ($.eventName = DeleteNetworkAclEntry) || ($.eventName =
        ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation)
        }
      MetricTransformations:
        - MetricValue: '1'
          MetricNamespace: CloudTrailMetrics
          MetricName: NetworkAclEventCount
Parameters: {}
Metadata: {}
Conditions: {}