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:
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':
- IamRoleForCwLogsCloudTrail
- Arn
DependsOn:
- BucketPolicy
IamRoleForCwLogsCloudTrail:
Type: 'AWS::IAM::Role'
Properties:
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:
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/service-role/AWS_ConfigRole'
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:
- 'Fn::Join':
- ''
- - 'Fn::GetAtt':
- S3SharedBucket
- Arn
- /*
Condition:
StringLike:
's3:x-amz-acl': bucket-owner-full-control
- Effect: Allow
Action:
- 's3:GetBucketAcl'
Resource:
'Fn::GetAtt':
- S3SharedBucket
- Arn
RoleName: iamRoleForAWSConfig
S3SharedBucket:
Type: 'AWS::S3::Bucket'
Properties:
LoggingConfiguration: {}
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
VersioningConfiguration:
Status: Suspended
AccessControl: LogDeliveryWrite
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: 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
Scope:
ComplianceResourceTypes:
- 'AWS::EC2::SecurityGroup'
Description: A Config rule that checks whether security groups in use do not allow restricted incoming SSH traffic. This rule applies only to IPv4.
Source:
Owner: AWS
SourceIdentifier: INCOMING_SSH_DISABLED
DependsOn:
- ConfigurationRecorder
ConfigRule2:
Type: 'AWS::Config::ConfigRule'
Properties:
ConfigRuleName: restricted-common-ports
Scope:
ComplianceResourceTypes:
- 'AWS::EC2::SecurityGroup'
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.
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
Scope:
ComplianceResourceTypes:
- 'AWS::EC2::Instance'
Description: A Config rule that checks that no EC2 Instances are in Public Subnet.
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.9
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_subnetpZy
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'
CwEvent1:
Type: 'AWS::Events::Rule'
Properties:
Name: detect-config-rule-compliance-changes
Description: A CloudWatch Event Rule that detects changes to AWS Config Rule compliance status and publishes change events to an SNS topic for notification.
State: ENABLED
Targets:
- Arn:
Ref: SnsTopic1
Id: target-id1
EventPattern:
detail-type:
- Config Rules Compliance Change
source:
- aws.config
SnsTopicPolicyCwEvent1:
Type: 'AWS::SNS::TopicPolicy'
Properties:
PolicyDocument:
Statement:
- Sid: __default_statement_ID
Effect: Allow
Principal:
AWS: '*'
Action:
- 'SNS:GetTopicAttributes'
- 'SNS:SetTopicAttributes'
- 'SNS:AddPermission'
- 'SNS:RemovePermission'
- 'SNS:DeleteTopic'
- 'SNS:Subscribe'
- 'SNS:ListSubscriptionsByTopic'
- 'SNS:Publish'
- 'SNS:Receive'
Resource:
Ref: SnsTopic1
Condition:
StringEquals:
'AWS:SourceOwner':
Ref: 'AWS::AccountId'
- Sid: TrustCWEToPublishEventsToMyTopic
Effect: Allow
Principal:
Service: events.amazonaws.com
Action: 'sns:Publish'
Resource:
Ref: SnsTopic1
Topics:
- Ref: SnsTopic1
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: {}