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:
- "\n"
-
- ""
- "#"
- "# 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_subnetXhu"
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: {}