--- AWSTemplateFormatVersion: '2010-09-09' Description: Demonstrates how to use GuardDuty Findings to automate WAFv2 ACL and VPC NACL entries. The template installs a Lambda function that updates an AWS WAFv2 IP Set and VPC NACL. Parameters: Retention: Description: How long to retain IP addresses in the blocklist (in minutes). Default is 12 hours, minimum is 5 minutes and maximum one week (10080 minutes) Type: Number Default: 720 MinValue: 5 MaxValue: 10080 ConstraintDescription: Minimum of 5 minutes and maximum of 10080 (one week). AdminEmail: Description: Email address to receive notifications. Must be a valid email address. Type: String AllowedPattern: ^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$ EnableCloudFront: Type: String Default: True AllowedValues: - True - False Description: CloudFront resources (WebACL and IP sets) will be created automatically. ArtifactsBucket: Description: S3 bucket with artifact files (Lambda functions, templates, html files, etc.). Type: String AllowedPattern: ^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$ ConstraintDescription: ArtifactsBucket S3 bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-). ArtifactsPrefix: Description: Path in the S3 folder, e.g. "folder_name/" containing artifact files. Leave empty if artifacts are in root of S3 bucket. Type: String Default: "" AllowedPattern: ^[0-9a-zA-Z-/|]*$ ConstraintDescription: ArtifactsPrefix key prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slash (/). Leave empty if artifacts are in root of S3 bucket. Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: GD2ACL Configuration Parameters: - AdminEmail - Retention - EnableCloudFront - Label: default: Artifact Configuration Parameters: - ArtifactsBucket - ArtifactsPrefix ParameterLabels: AdminEmail: default: Notification email (REQUIRED) Retention: default: Retention time in minutes EnableCloudFront: default: Create CloudFront Web ACL and IP set? ArtifactsBucket: default: S3 bucket for artifacts ArtifactsPrefix: default: S3 path to artifacts Conditions: # Create CloudFront resources? CloudFrontEnable: !Equals [True, !Ref EnableCloudFront ] Resources: GuardDutytoACLLambda: Type: AWS::Lambda::Function Properties: Description: "GuardDuty to ACL Function" Architectures: - arm64 Handler : "guardduty_to_acl_lambda.lambda_handler" MemorySize: 1024 Timeout: 300 Role: !GetAtt GuardDutytoACLRole.Arn Runtime : "python3.11" Environment: Variables: ACLMETATABLE: !Ref GuardDutytoACLDDBTable REGIONAL_IP_SET: !Ref RegionalBlocklistIPSetV4 CLOUDFRONT_IP_SET: !If [CloudFrontEnable, !Ref CloudFrontBlocklistIPSetV4, ''] SNSTOPIC: !Ref GuardDutytoACLSNSTopic CLOUDFRONT_ENABLE: !Ref EnableCloudFront Code: S3Bucket: !Sub ${ArtifactsBucket} S3Key: !Sub ${ArtifactsPrefix}guardduty_to_acl_lambda_wafv2.zip GuardDutytoACLRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "lambda.amazonaws.com" Action: "sts:AssumeRole" Path: "/" GuardDutytoACLPolicy: Type: AWS::IAM::Policy Properties: PolicyName: Fn::Join: - '-' - [ !Ref "AWS::Region", 'guardduty-to-nacl-wafipset' ] PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - wafv2:GetIPSet - wafv2:UpdateIPSet Resource: Fn::If: - CloudFrontEnable - - !GetAtt CloudFrontBlocklistIPSetV4.Arn - !GetAtt RegionalBlocklistIPSetV4.Arn - - !GetAtt RegionalBlocklistIPSetV4.Arn - Effect: "Allow" Action: - "ec2:Describe*" - "ec2:*NetworkAcl*" Resource: "*" - Effect: "Allow" Action: - "logs:CreateLogGroup" - "logs:CreateLogStream" - "logs:PutLogEvents" Resource: "arn:aws:logs:*:*:*" - Effect: Allow Action: - dynamodb:GetItem - dynamodb:PutItem - dynamodb:Query - dynamodb:Scan - dynamodb:DeleteItem Resource: !GetAtt GuardDutytoACLDDBTable.Arn - Effect: Allow Action: - sns:Publish Resource: !Ref GuardDutytoACLSNSTopic Roles: - Ref: "GuardDutytoACLRole" # GuardDuty CloudWatch Event - For GuardDuty Finding: GuardDutytoACLEvent: Type: "AWS::Events::Rule" Properties: Description: "GuardDuty Malicious Host Events" EventPattern: source: - aws.guardduty detail: type: - prefix: "UnauthorizedAccess:EC2" - prefix: "Recon:EC2" - prefix: "Trojan:EC2" - prefix: "Backdoor:EC2" - prefix: "Impact:EC2" - prefix: "CryptoCurrency:EC2" - prefix: "Behavior:EC2" State: "ENABLED" Targets: - Arn: !GetAtt GuardDutytoACLLambda.Arn Id: "GuardDutyEvent-Lambda-Trigger" GuardDutytoACLInvokePermissions: Type: "AWS::Lambda::Permission" Properties: FunctionName: !Ref "GuardDutytoACLLambda" Action: "lambda:InvokeFunction" SourceArn: !GetAtt GuardDutytoACLEvent.Arn Principal: "events.amazonaws.com" GuardDutytoACLDDBTable: Type: "AWS::DynamoDB::Table" Properties: AttributeDefinitions: - AttributeName: "NetACLId" AttributeType: "S" - AttributeName: "CreatedAt" AttributeType: "N" KeySchema: - AttributeName: "NetACLId" KeyType: "HASH" - AttributeName: "CreatedAt" KeyType: "RANGE" ProvisionedThroughput: ReadCapacityUnits: "5" WriteCapacityUnits: "5" CloudFrontBlocklistIPSetV4: Condition: CloudFrontEnable Type: 'AWS::WAFv2::IPSet' Properties: Description: GD2ACLIPSetV4 Scope: CLOUDFRONT IPAddressVersion: IPV4 Addresses: - 127.0.0.0/8 CloudFrontBlocklistIPSetV6: Condition: CloudFrontEnable Type: 'AWS::WAFv2::IPSet' Properties: Description: GD2ACLIPSetV6 Scope: CLOUDFRONT IPAddressVersion: IPV6 Addresses: - ::1/128 CloudFrontBlocklistWebACL: Condition: CloudFrontEnable Type: AWS::WAFv2::WebACL Properties: Scope: CLOUDFRONT DefaultAction: Allow: {} VisibilityConfig: SampledRequestsEnabled: true CloudWatchMetricsEnabled: true MetricName: GD2ACLCloudFrontBlocklistWebACL Rules: - Name: CloudFrontBlocklistIPSetRule Priority: 1 Action: Block: {} VisibilityConfig: SampledRequestsEnabled: true CloudWatchMetricsEnabled: true MetricName: CloudFrontBlockIPMetric Statement: OrStatement: Statements: - IPSetReferenceStatement: Arn: !If [CloudFrontEnable, !GetAtt CloudFrontBlocklistIPSetV4.Arn, ''] - IPSetReferenceStatement: Arn: !GetAtt CloudFrontBlocklistIPSetV6.Arn RegionalBlocklistIPSetV4: Type: 'AWS::WAFv2::IPSet' Properties: Description: GD2ACLIPSetV4 Scope: REGIONAL IPAddressVersion: IPV4 Addresses: - 127.0.0.0/8 RegionalBlocklistIPSetV6: Type: 'AWS::WAFv2::IPSet' Properties: Description: GD2ACLIPSetV6 Scope: REGIONAL IPAddressVersion: IPV6 Addresses: - ::1/128 RegionalBlocklistWebACL: Type: AWS::WAFv2::WebACL Properties: Scope: REGIONAL DefaultAction: Allow: {} VisibilityConfig: SampledRequestsEnabled: true CloudWatchMetricsEnabled: true MetricName: GD2ACLRegionalBlocklistWebACL Rules: - Name: RegionalBlocklistIPSetRule Priority: 1 Action: Block: {} VisibilityConfig: SampledRequestsEnabled: true CloudWatchMetricsEnabled: true MetricName: RegionalBlockIPMetric Statement: OrStatement: Statements: - IPSetReferenceStatement: Arn: !GetAtt RegionalBlocklistIPSetV4.Arn - IPSetReferenceStatement: Arn: !GetAtt RegionalBlocklistIPSetV6.Arn PruneOldEntriesLambda: Type: AWS::Lambda::Function Properties: Description: "Prune old entries in WAF ACL and NACLs" Architectures: - arm64 Handler : "prune_old_entries.lambda_handler" MemorySize: 1024 Timeout: 300 Role: !GetAtt PruneOldEntriesRole.Arn Runtime : "python3.11" Environment: Variables: ACLMETATABLE: !Ref GuardDutytoACLDDBTable REGIONAL_IP_SET: !Ref RegionalBlocklistIPSetV4 CLOUDFRONT_IP_SET: !If [CloudFrontEnable, !Ref CloudFrontBlocklistIPSetV4, ''] RETENTION: !Ref Retention CLOUDFRONT_ENABLE: !Ref EnableCloudFront Code: S3Bucket: !Sub ${ArtifactsBucket} S3Key: !Sub ${ArtifactsPrefix}prune_old_entries_wafv2.zip PruneOldEntriesRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "lambda.amazonaws.com" Action: "sts:AssumeRole" Path: "/" PruneOldEntriesPolicy: Type: AWS::IAM::Policy Properties: PolicyName: Fn::Join: - '-' - [ !Ref "AWS::Region", 'prune-old-entries' ] PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - wafv2:GetIPSet - wafv2:UpdateIPSet Resource: Fn::If: - CloudFrontEnable - - !GetAtt CloudFrontBlocklistIPSetV4.Arn - !GetAtt RegionalBlocklistIPSetV4.Arn - - !GetAtt RegionalBlocklistIPSetV4.Arn - Effect: "Allow" Action: - "ec2:Describe*" - "ec2:*NetworkAcl*" Resource: "*" - Effect: "Allow" Action: - "logs:CreateLogGroup" - "logs:CreateLogStream" - "logs:PutLogEvents" Resource: "arn:aws:logs:*:*:*" - Effect: Allow Action: - dynamodb:GetItem - dynamodb:PutItem - dynamodb:Query - dynamodb:Scan - dynamodb:DeleteItem Resource: !GetAtt GuardDutytoACLDDBTable.Arn Roles: - Ref: "PruneOldEntriesRole" PruneOldEntriesSchedule: Type: "AWS::Events::Rule" Properties: Description: "ScheduledPruningRule" ScheduleExpression: "rate(5 minutes)" State: "ENABLED" Targets: - Arn: !GetAtt PruneOldEntriesLambda.Arn Id: "TargetFunctionV1" PruneOldEntriesPermissionToInvoke: DependsOn: - GuardDutytoACLLambda Type: "AWS::Lambda::Permission" Properties: FunctionName: !Ref PruneOldEntriesLambda Action: "lambda:InvokeFunction" Principal: "events.amazonaws.com" SourceArn: !GetAtt PruneOldEntriesSchedule.Arn GuardDutytoACLSNSTopic: Type: "AWS::SNS::Topic" Properties: Subscription: - Endpoint: !Ref AdminEmail Protocol: "email" Outputs: GuardDutytoACLLambda: Description: GD2ACL Primary Lambda Function. Value: !Sub https://console.aws.amazon.com/lambda/home?region=${AWS::Region}#/functions/${GuardDutytoACLLambda} PruneOldEntriesLambda: Description: GD2ACL Entry Pruning Lambda Function. Value: !Sub https://console.aws.amazon.com/lambda/home?region=${AWS::Region}#/functions/${PruneOldEntriesLambda} ACLMetaTable: Description: GD2ACL DynamoDB State Table Value: !Ref GuardDutytoACLDDBTable RegionalIPSetId: Description: Regional IP Set Value: !Ref RegionalBlocklistIPSetV4 CloudFrontIPSetId: Condition: CloudFrontEnable Description: CloudFront IP Set Value: !Ref CloudFrontBlocklistIPSetV4 RegionalWebACL: Description: Regional Web ACL Value: !Ref RegionalBlocklistWebACL CloudFrontWebACL: Condition: CloudFrontEnable Description: CloudFront Web ACL Value: !Ref CloudFrontBlocklistWebACL Retention: Description: ACL Entry Time to Live in Minutes Value: !Ref Retention Region: Description: Region of the stack. Value: Ref: AWS::Region