# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 AWSTemplateFormatVersion: 2010-09-09 Description: >- (SO0006) - Security Automations for AWS WAF %VERSION%: This AWS CloudFormation template helps you provision the Security Automations for AWS WAF stack without worrying about creating and configuring the underlying AWS infrastructure. **WARNING** This template creates multiple AWS Lambda functions, an AWS WAFv2 Web ACL, an Amazon S3 bucket, and an Amazon CloudWatch custom metric. You will be billed for the AWS resources used if you create a stack from this template. Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Resource Type Parameters: - EndpointType - Label: default: AWS Managed IP Reputation Rule Groups Parameters: - ActivateAWSManagedIPRParam - ActivateAWSManagedAIPParam - Label: default: AWS Managed Baseline Rule Groups Parameters: - ActivateAWSManagedRulesParam - ActivateAWSManagedAPParam - ActivateAWSManagedKBIParam - Label: default: AWS Managed Use-case Specific Rule Groups Parameters: - ActivateAWSManagedSQLParam - ActivateAWSManagedLinuxParam - ActivateAWSManagedPOSIXParam - ActivateAWSManagedWindowsParam - ActivateAWSManagedPHPParam - ActivateAWSManagedWPParam - Label: default: Custom Rule - Scanner & Probes Parameters: - ActivateScannersProbesProtectionParam - AppAccessLogBucket - AppAccessLogBucketPrefixParam - AppAccessLogBucketLoggingStatusParam - ErrorThreshold - KeepDataInOriginalS3Location - Label: default: Custom Rule - HTTP Flood Parameters: - ActivateHttpFloodProtectionParam - RequestThreshold - RequestThresholdByCountryParam - HTTPFloodAthenaQueryGroupByParam - WAFBlockPeriod - AthenaQueryRunTimeScheduleParam - Label: default: Custom Rule - Bad Bot Parameters: - ActivateBadBotProtectionParam - ApiGatewayBadBotCWRoleParam - Label: default: Custom Rule - Third Party IP Reputation Lists Parameters: - ActivateReputationListsProtectionParam - Label: default: Legacy Custom Rules Parameters: - ActivateSqlInjectionProtectionParam - SqlInjectionProtectionSensitivityLevelParam - ActivateCrossSiteScriptingProtectionParam - Label: default: Allowed and Denied IP Retention Settings Parameters: - IPRetentionPeriodAllowedParam - IPRetentionPeriodDeniedParam - SNSEmailParam - Label: default: Advanced Settings Parameters: - LogGroupRetentionParam ParameterLabels: ActivateAWSManagedRulesParam: default: Activate Core Rule Set Managed Rule Group Protection ActivateAWSManagedAPParam: default: Activate Admin Protection Managed Rule Group Protection ActivateAWSManagedKBIParam: default: Activate Known Bad Inputs Managed Rule Group Protection ActivateAWSManagedIPRParam: default: Activate Amazon IP reputation List Managed Rule Group Protection ActivateAWSManagedAIPParam: default: Activate Anonymous IP List Managed Rule Group Protection ActivateAWSManagedSQLParam: default: Activate SQL Database Managed Rule Group Protection ActivateAWSManagedLinuxParam: default: Activate Linux Operating System Managed Rule Group Protection ActivateAWSManagedPOSIXParam: default: Activate POSIX Operating System Managed Rule Group Protection ActivateAWSManagedWindowsParam: default: Activate Windows Operating System Managed Rule Group Protection ActivateAWSManagedPHPParam: default: Activate PHP Application Managed Rule Group Protection ActivateAWSManagedWPParam: default: Activate WordPress Application Managed Rule Group Protection ActivateSqlInjectionProtectionParam: default: Activate SQL Injection Protection SqlInjectionProtectionSensitivityLevelParam: default: Sensitivity Level for SQL Injection Protection ActivateCrossSiteScriptingProtectionParam: default: Activate Cross-site Scripting Protection ActivateHttpFloodProtectionParam: default: Activate HTTP Flood Protection ActivateScannersProbesProtectionParam: default: Activate Scanner & Probe Protection ActivateReputationListsProtectionParam: default: Activate Reputation List Protection ActivateBadBotProtectionParam: default: Activate Bad Bot Protection ApiGatewayBadBotCWRoleParam: default: ARN of an IAM role that has write access to CloudWatch logs in your account EndpointType: default: Endpoint AppAccessLogBucket: default: Application Access Log Bucket Name AppAccessLogBucketPrefixParam: default: Application Access Log Bucket Prefix AppAccessLogBucketLoggingStatusParam: default: Is bucket access logging turned on? ErrorThreshold: default: Error Threshold RequestThreshold: default: Default Request Threshold RequestThresholdByCountryParam: default: Request Threshold by Country HTTPFloodAthenaQueryGroupByParam: default: Group By Requests in HTTP Flood Athena Query WAFBlockPeriod: default: WAF Block Period AthenaQueryRunTimeScheduleParam: default: Athena Query Run Time Schedule (Minute) KeepDataInOriginalS3Location: default: Keep Data in Original S3 Location IPRetentionPeriodAllowedParam: default: Retention Period (Minutes) for Allowed IP Set IPRetentionPeriodDeniedParam: default: Retention Period (Minutes) for Denied IP Set SNSEmailParam: default: Email for receiving notification upon Allowed or Denied IP Sets expiration LogGroupRetentionParam: default: Retention Period (Days) for Log Groups Parameters: ActivateAWSManagedRulesParam: Type: String Default: 'no' AllowedValues: - 'yes' - 'no' Description: >- Core Rule Set provides protection against exploitation of a wide range of vulnerabilities, including some of the high risk and commonly occurring vulnerabilities. Consider using this rule group for any AWS WAF use case. Required WCU: 700. Your account should have sufficient WCU capacity to avoid WebACL stack deployment failure due to exceeding the capacity limit. ActivateAWSManagedAPParam: Type: String Default: 'no' AllowedValues: - 'yes' - 'no' Description: >- The Admin protection rule group blocks external access to exposed administrative pages. This might be useful if you run third-party software or want to reduce the risk of a malicious actor gaining administrative access to your application. Required WCU: 100. ActivateAWSManagedKBIParam: Type: String Default: 'no' AllowedValues: - 'yes' - 'no' Description: >- The Known bad inputs rule group blocks request patterns that are known to be invalid and are associated with exploitation or discovery of vulnerabilities. This can help reduce the risk of a malicious actor discovering a vulnerable application. Required WCU: 200. ActivateAWSManagedIPRParam: Type: String Default: 'no' AllowedValues: - 'yes' - 'no' Description: >- The Amazon IP reputation list rule group are based on Amazon internal threat intelligence. This is useful if you would like to block IP addresses typically associated with bots or other threats. Blocking these IP addresses can help mitigate bots and reduce the risk of a malicious actor discovering a vulnerable application. Required WCU: 25. ActivateAWSManagedAIPParam: Type: String Default: 'no' AllowedValues: - 'yes' - 'no' Description: >- The Anonymous IP list rule group blocks requests from services that permit the obfuscation of viewer identity. These include requests from VPNs, proxies, Tor nodes, and hosting providers. This rule group is useful if you want to filter out viewers that might be trying to hide their identity from your application. Blocking the IP addresses of these services can help mitigate bots and evasion of geographic restrictions. Required WCU: 50. ActivateAWSManagedSQLParam: Type: String Default: 'no' AllowedValues: - 'yes' - 'no' Description: >- The SQL database rule group blocks request patterns associated with exploitation of SQL databases, like SQL injection attacks. This can help prevent remote injection of unauthorized queries. Evaluate this rule group for use if your application interfaces with an SQL database. Using the SQL injection custom rule is optional, if you already have AWS managed SQL rule group activated. Required WCU: 200. ActivateAWSManagedLinuxParam: Type: String Default: 'no' AllowedValues: - 'yes' - 'no' Description: >- The Linux operating system rule group blocks request patterns associated with the exploitation of vulnerabilities specific to Linux, including Linux-specific Local File Inclusion (LFI) attacks. This can help prevent attacks that expose file contents or run code for which the attacker should not have had access. Evaluate this rule group if any part of your application runs on Linux. You should use this rule group in conjunction with the POSIX operating system rule group. Required WCU: 200. ActivateAWSManagedPOSIXParam: Type: String Default: 'no' AllowedValues: - 'yes' - 'no' Description: >- The POSIX operating system rule group blocks request patterns associated with the exploitation of vulnerabilities specific to POSIX and POSIX-like operating systems, including Local File Inclusion (LFI) attacks. This can help prevent attacks that expose file contents or run code for which the attacker should not have had access. Evaluate this rule group if any part of your application runs on a POSIX or POSIX-like operating system. Required WCU: 100. ActivateAWSManagedWindowsParam: Type: String Default: 'no' AllowedValues: - 'yes' - 'no' Description: >- The Windows operating system rule group blocks request patterns associated with the exploitation of vulnerabilities specific to Windows, like remote execution of PowerShell commands. This can help prevent exploitation of vulnerabilities that permit an attacker to run unauthorized commands or run malicious code. Evaluate this rule group if any part of your application runs on a Windows operating system. Required WCU: 200. ActivateAWSManagedPHPParam: Type: String Default: 'no' AllowedValues: - 'yes' - 'no' Description: >- The PHP application rule group blocks request patterns associated with the exploitation of vulnerabilities specific to the use of the PHP programming language, including injection of unsafe PHP functions. This can help prevent exploitation of vulnerabilities that permit an attacker to remotely run code or commands for which they are not authorized. Evaluate this rule group if PHP is installed on any server with which your application interfaces. Required WCU: 100. ActivateAWSManagedWPParam: Type: String Default: 'no' AllowedValues: - 'yes' - 'no' Description: >- The WordPress application rule group blocks request patterns associated with the exploitation of vulnerabilities specific to WordPress sites. Evaluate this rule group if you are running WordPress. This rule group should be used in conjunction with the SQL database and PHP application rule groups. Required WCU: 100. ActivateSqlInjectionProtectionParam: Type: String Default: 'yes' AllowedValues: - 'yes' - 'yes - MATCH' - 'yes - NO_MATCH' - 'no' Description: >- Choose yes to deploy the default SQL injection protection rule designed to block common SQL injection attacks. Consider activating it if you are not using Core Rule Set or AWS managed SQL database rule group. The 'yes' option uses CONTINUE for oversized request handling by default. Note: If you customized the rule outside of CloudFormation, your changes will be overwritten after stack update. SqlInjectionProtectionSensitivityLevelParam: Type: String Default: 'LOW' AllowedValues: - 'LOW' - 'HIGH' Description: >- Choose the sensitivity level used by WAF to inspect for SQL injection attacks. If you choose to deactivate SQL injection protection, ignore this parameter. Note: The stack deploys the default SQL injection protection rule into your AWS account. If you customized the rule outside of CloudFormation, your changes will be overwritten after stack update. ActivateCrossSiteScriptingProtectionParam: Type: String Default: 'yes' AllowedValues: - 'yes' - 'yes - MATCH' - 'yes - NO_MATCH' - 'no' Description: >- Choose yes to deploy the default cross-site scripting protection rule designed to block common cross-site scripting attacks. Consider activating it if you are not using Core Rule Set. The 'yes' option uses CONTINUE for oversized request handling by default. Note: If you customized the rule outside of CloudFormation, your changes will be overwritten after stack update. ActivateHttpFloodProtectionParam: Type: String Default: 'yes - AWS WAF rate based rule' AllowedValues: - 'yes - AWS WAF rate based rule' - 'yes - AWS Lambda log parser' - 'yes - Amazon Athena log parser' - 'no' Description: Choose yes to activate the component designed to block HTTP flood attacks. ActivateScannersProbesProtectionParam: Type: String Default: 'yes - AWS Lambda log parser' AllowedValues: - 'yes - AWS Lambda log parser' - 'yes - Amazon Athena log parser' - 'no' Description: Choose yes to activate the component designed to block scanners and probes. ActivateReputationListsProtectionParam: Type: String Default: 'yes' AllowedValues: - 'yes' - 'no' Description: >- Choose yes to block requests from IP addresses on third-party reputation lists (supported lists: spamhaus, torproject, and emergingthreats). ActivateBadBotProtectionParam: Type: String Default: 'yes' AllowedValues: - 'yes' - 'no' Description: Choose yes to activate the component designed to block bad bots and content scrapers. ApiGatewayBadBotCWRoleParam: Type: String Default: '' Description: >- Provide an optional ARN of an IAM role that has write access to CloudWatch logs in your account. Example ARN: arn:aws:iam::account_id:role/myrolename. See https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-logging.html for instructions on how to create the role. If you leave it blank (default), a new role will be created for you. EndpointType: Type: String Default: 'CloudFront' AllowedValues: - 'CloudFront' - 'ALB' Description: Select the resource type and then select the resource below that you want to associate with this web ACL. AppAccessLogBucket: Type: String Default: '' AllowedPattern: '(^$|^([a-z]|(\d(?!\d{0,2}\.\d{1,3}\.\d{1,3}\.\d{1,3})))([a-z\d]|(\.(?!(\.|-)))|(-(?!\.))){1,61}[a-z\d]$)' Description: >- If you chose yes for the Activate Scanners & Probes Protection parameter, enter a name for the Amazon S3 bucket (new or existing) where you want to store access logs for your CloudFront distribution or Application Load Balancer. More about bucket name restriction here: http://amzn.to/1p1YlU5. If you chose to deactivate this protection, ignore this parameter. AppAccessLogBucketPrefixParam: Type: String Default: 'AWSLogs/' Description: >- If you chose yes for the Activate Scanners & Probes Protection parameter, you can enter an optional user defined prefix for the application access logs bucket above. For ALB resource, you must append AWSLogs/ to your prefix such as yourprefix/AWSLogs/. For CloudFront resource, you can enter any prefix such as yourprefix/. Leave it to AWSLogs/ (default) if there isn't a user-defined prefix. If you chose to deactivate this protection, ignore this parameter. AppAccessLogBucketLoggingStatusParam: Type: String Default: 'no' AllowedValues: - 'yes' - 'no' Description: >- Choose yes if you provided an existing application access log bucket above and the server access logging for the bucket is already turned on. If you chose no, the solution will turn on server access logging for your bucket. If you deactivate Scanners & Probes Protection, ignore this parameter. ErrorThreshold: Type: Number Default: 50 MinValue: 0 Description: >- If you chose yes for the Activate Scanners & Probes Protection parameter, enter the maximum acceptable bad requests per minute per IP. If you chose to deactivate this protection protection, ignore this parameter. RequestThreshold: Type: Number Default: 100 MinValue: 0 Description: >- If you chose yes for the Activate HTTP Flood Protection parameter, enter the maximum acceptable requests per IP address per FIVE-minute period (default). You can change the time period by entering a different number for Athena Query Run Time Schedule below. The request threshold is divided by this number to get the desired threshold per minute that is used in Athena query. Note: AWS WAF rate based rule requires a value greater than 100 (if you chose Lambda/Athena log parser options, you can use any value greater than zero). If you chose to deactivate this protection, ignore this parameter. RequestThresholdByCountryParam: Type: String Default: '' AllowedPattern: '^$|^\{"\w+":\d+([,]"\w+":\d+)*\}+$' Description: >- If you chose Athena Log Parser to activate HTTP Flood Protection, you can enter a threshold by country following this JSON format {"TR":50,"ER":150}. These thresholds will be used for the requests originated from the specified countries, while the default threshold above will be used for the remaining requests. The threshold is calculated in a default FIVE-minute period. You can change the time period by entering a different number for Athena Query Run Time Schedule below. The request threshold is divided by this number to get the desired threshold per minute that is used in Athena query. Note: If you define a threshold by country, country will automatically be included in Athena query group-by clause, along with ip and other group-by fields you may select below. If you chose to deactivate this protection, ignore this parameter. HTTPFloodAthenaQueryGroupByParam: Type: String Default: 'None' AllowedValues: - 'Country' - 'URI' - 'Country and URI' - 'None' Description: >- If you chose Athena Log Parser to activate HTTP Flood Protection, you can select a group-by field to count requests per IP along with the selected group-by field. For example, if URI is selected, the requests will be counted per IP and URI. If you chose to deactivate this protection, ignore this parameter. WAFBlockPeriod: Type: Number Default: 240 MinValue: 0 Description: >- If you chose yes for the Activate Scanners & Probes Protection or HTTP Flood Lambda/Athena log parser parameters, enter the period (in minutes) to block applicable IP addresses. If you chose to deactivate log parsing, ignore this parameter. AthenaQueryRunTimeScheduleParam: Type: Number Default: 5 MinValue: 1 Description: >- If you chose Athena Log Parser to activate Scanners & Probes Protection or HTTP Flood Protection, you can enter a time interval (in minutes) over which the Athena query runs. By default, the Athena query runs every 5 minutes. Request threshold entered above is divided by this number to get the threshold per minute in the Athena query. If you chose to deactivate these protections, ignore this parameter. KeepDataInOriginalS3Location: Type: String Default: 'No' AllowedValues: - 'Yes' - 'No' Description: >- If you chose Amazon Athena log parser for the Activate Scanners & Probes Protection parameter, partitioning will be applied to log files and Athena queries. By default log files will be moved from their original location to a partitioned folder structure in s3. Choose Yes if you also want to keep a copy of the logs in their original location. Selecting "Yes" will duplicate your log storage. If you did not choose to activate Athena log parsing, ignore this parameter. IPRetentionPeriodAllowedParam: Type: Number Default: -1 MinValue: -1 Description: >- If you want to activate IP retention for the Allowed IP set, enter a number (15 or above) as the retention period (minutes). IP addresses reaching the retention period will expire and be removed from the IP set. A minimum 15-minute retention period is supported. If you enter a number between 0 and 15, it will be treated as 15. Leave it to default value -1 to disable IP retention. IPRetentionPeriodDeniedParam: Type: Number Default: -1 MinValue: -1 Description: >- If you want to activate IP retention for the Denied IP set, enter a number (15 or above) as the retention period (minutes). IP addresses reaching the retention period will expire and be removed from the IP set. A minimum 15-minute retention period is supported. If you enter a number between 0 and 15, it will be treated as 15. Leave it to default value -1 to disable IP retention. SNSEmailParam: Type: String Default: '' Description: >- If you activated IP retention period above and want to receive an email notification when IP addresses expire, enter a valid email address. If you did not activate IP retention or want to disable email notification, leave it blank (default). LogGroupRetentionParam: Type: Number Default: 365 AllowedValues: - -1 - 1 - 3 - 5 - 7 - 14 - 30 - 60 - 90 - 120 - 150 - 180 - 365 - 400 - 545 - 731 - 1827 - 2192 - 2557 - 2922 - 3288 - 3653 Description: >- If you want to activate retention for the CloudWatch Log Groups, enter a number (1 or above) as the retention period (days). You can choose a retention period between one day and 10 years. By default logs will expired after 1 year. Set it to -1 to keep the logs indefinitely. Conditions: HttpFloodProtectionRateBasedRuleActivated: !Equals - !Ref ActivateHttpFloodProtectionParam - 'yes - AWS WAF rate based rule' HttpFloodLambdaLogParser: !Equals - !Ref ActivateHttpFloodProtectionParam - 'yes - AWS Lambda log parser' HttpFloodAthenaLogParser: !Equals - !Ref ActivateHttpFloodProtectionParam - 'yes - Amazon Athena log parser' HttpFloodProtectionLogParserActivated: !Or - Condition: HttpFloodLambdaLogParser - Condition: HttpFloodAthenaLogParser ScannersProbesLambdaLogParser: !Equals - !Ref ActivateScannersProbesProtectionParam - 'yes - AWS Lambda log parser' ScannersProbesAthenaLogParser: !Equals - !Ref ActivateScannersProbesProtectionParam - 'yes - Amazon Athena log parser' ScannersProbesProtectionActivated: !Or - Condition: ScannersProbesLambdaLogParser - Condition: ScannersProbesAthenaLogParser AthenaLogParser: !Or - Condition: HttpFloodAthenaLogParser - Condition: ScannersProbesAthenaLogParser LogParser: !Or - Condition: HttpFloodProtectionLogParserActivated - Condition: ScannersProbesProtectionActivated CreateFirehoseAthenaStack: !Or - Condition: HttpFloodProtectionLogParserActivated - Condition: AthenaLogParser ReputationListsProtectionActivated: !Equals - !Ref ActivateReputationListsProtectionParam - 'yes' BadBotProtectionActivated: !Equals - !Ref ActivateBadBotProtectionParam - 'yes' ApiGatewayBadBotCWRoleNotExists: !Equals [!Ref ApiGatewayBadBotCWRoleParam, ''] CreateApiGatewayBadBotCloudWatchRole: !And - Condition: BadBotProtectionActivated - Condition: ApiGatewayBadBotCWRoleNotExists AlbEndpoint: !Equals - !Ref EndpointType - 'ALB' CustomResourceLambdaAccess: !Or - Condition: ReputationListsProtectionActivated - Condition: AthenaLogParser IPRetentionAllwedPeriod: !Not [!Equals [!Ref IPRetentionPeriodAllowedParam, -1]] IPRetentionDeniedPeriod: !Not [!Equals [!Ref IPRetentionPeriodDeniedParam, -1]] IPRetentionPeriod: !Or - Condition: IPRetentionAllwedPeriod - Condition: IPRetentionDeniedPeriod SNSEmailProvided: !Not [!Equals [!Ref SNSEmailParam, '']] SNSEmail: !And - Condition: IPRetentionPeriod - Condition: SNSEmailProvided AppAccessLogBucketLoggingOff: !Equals - !Ref AppAccessLogBucketLoggingStatusParam - 'no' TurnOnAppAccessLogBucketLogging: !And - Condition: ScannersProbesProtectionActivated - Condition: AppAccessLogBucketLoggingOff CreateS3LoggingBucket: !Or - Condition: HttpFloodProtectionLogParserActivated - Condition: TurnOnAppAccessLogBucketLogging UserDefinedAppAccessLogBucketPrefix: !Not [!Equals [!Ref AppAccessLogBucketPrefixParam, 'AWSLogs/']] RequestThresholdByCountry: !Not [!Equals [!Ref RequestThresholdByCountryParam, '']] IsAthenaQueryRunEveryMinute: !Equals [!Ref AthenaQueryRunTimeScheduleParam, 1] LogGroupRetentionEnabled: !Not [!Equals [!Ref LogGroupRetentionParam, -1]] Mappings: SourceCode: General: TemplateBucket: '%TEMPLATE_OUTPUT_BUCKET%' SourceBucket: '%DIST_OUTPUT_BUCKET%' KeyPrefix: '%SOLUTION_NAME%/%VERSION%' Solution: Data: SendAnonymousUsageData: 'Yes' LogLevel: 'INFO' SolutionID: 'SO0006' MetricsURL: 'https://metrics.awssolutionsbuilder.com/generic' Action: WAFWhitelistRule: 'ALLOW' WAFBlacklistRule: 'BLOCK' WAFSqlInjectionRule: 'BLOCK' WAFXssRule: 'BLOCK' WAFHttpFloodRateBasedRule: 'BLOCK' WAFHttpFloodRegularRule: 'BLOCK' WAFScannersProbesRule: 'BLOCK' WAFIPReputationListsRule: 'BLOCK' WAFBadBotRule: 'BLOCK' UserAgent: UserAgentExtra: 'AwsSolution/SO0006/%VERSION%' AppRegistry: AppRegistryApplicationName: 'waf-security-automations' SolutionName: 'WAF Security Automations' Resources: CheckRequirements: Type: 'Custom::CheckRequirements' Properties: AthenaLogParser: !If [AthenaLogParser, 'yes', 'no'] ServiceToken: !GetAtt Helper.Arn HttpFloodProtectionRateBasedRuleActivated: !If [HttpFloodProtectionRateBasedRuleActivated, 'yes', 'no'] HttpFloodProtectionLogParserActivated: !If [HttpFloodProtectionLogParserActivated, 'yes', 'no'] ProtectionActivatedScannersProbes: !If [ScannersProbesProtectionActivated, 'yes', 'no'] AppAccessLogBucket: !Ref AppAccessLogBucket Region: !Ref 'AWS::Region' EndpointType: !Ref EndpointType RequestThreshold: !Ref RequestThreshold FirehoseAthenaStack: Type: 'AWS::CloudFormation::Stack' Condition: CreateFirehoseAthenaStack DependsOn: CheckRequirements Properties: TemplateURL: !Sub - 'https://${S3Bucket}.s3.amazonaws.com/${KeyPrefix}/aws-waf-security-automations-firehose-athena.template' - S3Bucket: !FindInMap ["SourceCode", "General", "TemplateBucket"] KeyPrefix: !FindInMap ["SourceCode", "General", "KeyPrefix"] Parameters: ActivateHttpFloodProtectionParam: !Ref ActivateHttpFloodProtectionParam ActivateScannersProbesProtectionParam: !Ref ActivateScannersProbesProtectionParam EndpointType: !Ref EndpointType AppAccessLogBucket: !Ref AppAccessLogBucket ParentStackName: !Ref 'AWS::StackName' WafLogBucket: !If [HttpFloodProtectionLogParserActivated, !Ref WafLogBucket, ''] WafLogBucketArn: !If [HttpFloodProtectionLogParserActivated, !GetAtt WafLogBucket.Arn, ''] ErrorThreshold: !Ref ErrorThreshold RequestThreshold: !Ref RequestThreshold WAFBlockPeriod: !Ref WAFBlockPeriod GlueDatabaseName: !If [AthenaLogParser, !GetAtt CreateGlueDatabaseName.DatabaseName, ''] DeliveryStreamName: !If [HttpFloodProtectionLogParserActivated, !GetAtt CreateDeliveryStreamName.DeliveryStreamName, ''] UUID: !GetAtt CreateUniqueID.UUID WebACLStack: Type: 'AWS::CloudFormation::Stack' DependsOn: CheckRequirements Properties: TemplateURL: !Sub - 'https://${S3Bucket}.s3.amazonaws.com/${KeyPrefix}/aws-waf-security-automations-webacl.template' - S3Bucket: !FindInMap ["SourceCode", "General", "TemplateBucket"] KeyPrefix: !FindInMap ["SourceCode", "General", "KeyPrefix"] Parameters: ActivateAWSManagedRulesParam: !Ref ActivateAWSManagedRulesParam ActivateAWSManagedAPParam: !Ref ActivateAWSManagedAPParam ActivateAWSManagedKBIParam: !Ref ActivateAWSManagedKBIParam ActivateAWSManagedIPRParam: !Ref ActivateAWSManagedIPRParam ActivateAWSManagedAIPParam: !Ref ActivateAWSManagedAIPParam ActivateAWSManagedSQLParam: !Ref ActivateAWSManagedSQLParam ActivateAWSManagedLinuxParam: !Ref ActivateAWSManagedLinuxParam ActivateAWSManagedPOSIXParam: !Ref ActivateAWSManagedPOSIXParam ActivateAWSManagedWindowsParam: !Ref ActivateAWSManagedWindowsParam ActivateAWSManagedPHPParam: !Ref ActivateAWSManagedPHPParam ActivateAWSManagedWPParam: !Ref ActivateAWSManagedWPParam ActivateSqlInjectionProtectionParam: !Ref ActivateSqlInjectionProtectionParam ActivateCrossSiteScriptingProtectionParam: !Ref ActivateCrossSiteScriptingProtectionParam SqlInjectionProtectionSensitivityLevelParam: !Ref SqlInjectionProtectionSensitivityLevelParam ActivateHttpFloodProtectionParam: !Ref ActivateHttpFloodProtectionParam ActivateScannersProbesProtectionParam: !Ref ActivateScannersProbesProtectionParam ActivateReputationListsProtectionParam: !Ref ActivateReputationListsProtectionParam ActivateBadBotProtectionParam: !Ref ActivateBadBotProtectionParam RequestThreshold: !Ref RequestThreshold RegionScope: !If [AlbEndpoint, 'REGIONAL', 'CLOUDFRONT'] ParentStackName: !Ref 'AWS::StackName' GlueAccessLogsDatabase: !If [AthenaLogParser, !GetAtt FirehoseAthenaStack.Outputs.GlueAccessLogsDatabase, ''] GlueAppAccessLogsTable: !If [ScannersProbesAthenaLogParser, !GetAtt FirehoseAthenaStack.Outputs.GlueAppAccessLogsTable, ''] GlueWafAccessLogsTable: !If [HttpFloodAthenaLogParser, !GetAtt FirehoseAthenaStack.Outputs.GlueWafAccessLogsTable,''] LogLevel: !FindInMap ["Solution", "Data", "LogLevel"] LambdaRoleHelper: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' Path: / Policies: - PolicyName: S3Access PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 's3:GetBucketLocation' - 's3:GetObject' - 's3:ListBucket' Resource: - !Sub 'arn:${AWS::Partition}:s3:::${AppAccessLogBucket}' - PolicyName: WAFAccess PolicyDocument: Statement: - Effect: Allow Action: - 'wafv2:ListWebACLs' Resource: - !Sub 'arn:${AWS::Partition}:wafv2:${AWS::Region}:${AWS::AccountId}:regional/webacl/*' - !Sub 'arn:${AWS::Partition}:wafv2:${AWS::Region}:${AWS::AccountId}:global/webacl/*' - PolicyName: LogsAccess PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: - !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*Helper*' Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: "LogsAccess permission restricted to account, region and log group name substring (Helper)." - id: W76 reason: "The policy is long as it is scoped down to all the IP set ARNs and function ARNs." LambdaRoleBadBot: Type: 'AWS::IAM::Role' Condition: BadBotProtectionActivated Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' Path: / Policies: - PolicyName: LogsAccess PolicyDocument: Statement: - Effect: Allow Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: - !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*BadBotParser*' - PolicyName: 'CloudFormationAccess' PolicyDocument: Statement: - Effect: Allow Action: 'cloudformation:DescribeStacks' Resource: - !Sub 'arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*' - PolicyName: WAFGetAndUpdateIPSet PolicyDocument: Statement: - Effect: Allow Action: - 'wafv2:GetIPSet' - 'wafv2:UpdateIPSet' Resource: - !GetAtt WebACLStack.Outputs.WAFBadBotSetV4Arn - !GetAtt WebACLStack.Outputs.WAFBadBotSetV6Arn - PolicyName: CloudWatchAccess PolicyDocument: Statement: - Effect: Allow Action: 'cloudwatch:GetMetricStatistics' Resource: - '*' Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: >- LogsAccess - permission restricted to account, region and log group name substring (BadBotParser); CloudFormationAccess - account, region and stack name; CloudWatchAccess - this actions does not support resource-level permissions LambdaRoleReputationListsParser: Type: 'AWS::IAM::Role' Condition: ReputationListsProtectionActivated Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: 'sts:AssumeRole' Policies: - PolicyName: CloudWatchLogs PolicyDocument: Statement: - Effect: Allow Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: - !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*ReputationListsParser*' - PolicyName: WAFGetAndUpdateIPSet PolicyDocument: Statement: - Effect: Allow Action: - 'wafv2:GetIPSet' - 'wafv2:UpdateIPSet' Resource: - !GetAtt WebACLStack.Outputs.WAFReputationListsSetV4Arn - !GetAtt WebACLStack.Outputs.WAFReputationListsSetV6Arn - PolicyName: CloudFormationAccess PolicyDocument: Statement: - Effect: Allow Action: 'cloudformation:DescribeStacks' Resource: - !Sub 'arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*' - PolicyName: CloudWatchAccess PolicyDocument: Statement: - Effect: Allow Action: 'cloudwatch:GetMetricStatistics' Resource: - '*' Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: >- CloudWatchLogs - permission restricted to account, region and log group name substring (ReputationListsParser); CloudFormationAccess - account, region and stack name; CloudWatchAccess - this actions does not support resource-level permissions LambdaRoleLogParser: Type: 'AWS::IAM::Role' Condition: LogParser DependsOn: WebACLStack Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: '/' Policies: - !If - ScannersProbesProtectionActivated - PolicyName: ScannersProbesProtectionActivatedAccess PolicyDocument: Statement: # S3 Resources - Effect: Allow Action: 's3:GetObject' Resource: - !Sub 'arn:${AWS::Partition}:s3:::${AppAccessLogBucket}/*' - Effect: Allow Action: 's3:PutObject' Resource: - !Sub 'arn:${AWS::Partition}:s3:::${AppAccessLogBucket}/${AWS::StackName}-app_log_out.json' - !Sub 'arn:${AWS::Partition}:s3:::${AppAccessLogBucket}/${AWS::StackName}-app_log_conf.json' - Effect: Allow Action: - 'wafv2:GetIPSet' - 'wafv2:UpdateIPSet' Resource: - !GetAtt WebACLStack.Outputs.WAFScannersProbesSetV4Arn - !GetAtt WebACLStack.Outputs.WAFScannersProbesSetV6Arn - !Ref 'AWS::NoValue' - !If - ScannersProbesAthenaLogParser - PolicyName: ScannersProbesAthenaLogParserAccess PolicyDocument: Statement: # Athena Resources - Effect: Allow Action: - 'athena:GetNamedQuery' - 'athena:StartQueryExecution' Resource: - !Sub 'arn:${AWS::Partition}:athena:${AWS::Region}:${AWS::AccountId}:workgroup/WAF*' # S3 Resources - Effect: Allow Action: - 's3:GetBucketLocation' - 's3:GetObject' - 's3:ListBucket' - 's3:ListBucketMultipartUploads' - 's3:ListMultipartUploadParts' - 's3:AbortMultipartUpload' - 's3:CreateBucket' - 's3:PutObject' Resource: - !Sub 'arn:${AWS::Partition}:s3:::${AppAccessLogBucket}/athena_results/*' - !Sub 'arn:${AWS::Partition}:s3:::${AppAccessLogBucket}' # Glue Resources - Effect: Allow Action: - 'glue:GetTable' - 'glue:GetPartitions' Resource: - !Sub 'arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:catalog' - !Sub 'arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:database/${WebACLStack.Outputs.GlueAccessLogsDatabase}' - !Sub 'arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:table/${WebACLStack.Outputs.GlueAccessLogsDatabase}/${WebACLStack.Outputs.GlueAppAccessLogsTable}' - !Ref 'AWS::NoValue' - !If - HttpFloodProtectionLogParserActivated - PolicyName: HttpFloodProtectionLogParserActivatedAccess PolicyDocument: Statement: # S3 Resources - Effect: Allow Action: 's3:GetObject' Resource: - !Sub 'arn:${AWS::Partition}:s3:::${WafLogBucket}/*' - Effect: Allow Action: 's3:PutObject' Resource: - !Sub 'arn:${AWS::Partition}:s3:::${WafLogBucket}/${AWS::StackName}-waf_log_out.json' - !Sub 'arn:${AWS::Partition}:s3:::${WafLogBucket}/${AWS::StackName}-waf_log_conf.json' # AWS WAF Resources - Effect: Allow Action: - 'wafv2:GetIPSet' - 'wafv2:UpdateIPSet' Resource: - !GetAtt WebACLStack.Outputs.WAFHttpFloodSetV4Arn - !GetAtt WebACLStack.Outputs.WAFHttpFloodSetV6Arn - !Ref 'AWS::NoValue' - !If - HttpFloodAthenaLogParser - PolicyName: HttpFloodAthenaLogParserAccess PolicyDocument: Statement: # Athena Resources - Effect: Allow Action: - 'athena:GetNamedQuery' - 'athena:StartQueryExecution' Resource: - !Sub 'arn:${AWS::Partition}:athena:${AWS::Region}:${AWS::AccountId}:workgroup/WAF*' # S3 Resources - Effect: Allow Action: - 's3:GetBucketLocation' - 's3:GetObject' - 's3:ListBucket' - 's3:ListBucketMultipartUploads' - 's3:ListMultipartUploadParts' - 's3:AbortMultipartUpload' - 's3:CreateBucket' - 's3:PutObject' Resource: - !Sub 'arn:${AWS::Partition}:s3:::${WafLogBucket}/athena_results/*' - !Sub 'arn:${AWS::Partition}:s3:::${WafLogBucket}' # Glue Resources - Effect: Allow Action: - 'glue:GetTable' - 'glue:GetPartitions' Resource: - !Sub 'arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:catalog' - !Sub 'arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:database/${WebACLStack.Outputs.GlueAccessLogsDatabase}' - !Sub 'arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:table/${WebACLStack.Outputs.GlueAccessLogsDatabase}/${WebACLStack.Outputs.GlueWafAccessLogsTable}' - !Ref 'AWS::NoValue' - PolicyName: LogsAccess PolicyDocument: Statement: - Effect: Allow Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: - !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*LogParser*' - PolicyName: CloudWatchAccess PolicyDocument: Statement: - Effect: Allow Action: 'cloudwatch:GetMetricStatistics' Resource: - '*' Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: >- LogsAccess - permission restricted to account, region and log group name substring (LogParser); CloudWatchAccess - this actions does not support resource-level permissions LambdaRoleCustomResource: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' Path: / Policies: - PolicyName: S3AccessGeneralAppAccessLog PolicyDocument: Statement: - Effect: Allow Action: - 's3:CreateBucket' - 's3:GetBucketNotification' - 's3:PutBucketNotification' - 's3:PutEncryptionConfiguration' - 's3:PutBucketPublicAccessBlock' Resource: - !Sub 'arn:${AWS::Partition}:s3:::${AppAccessLogBucket}' - !If - HttpFloodProtectionLogParserActivated - PolicyName: S3AccessGeneralWafLog PolicyDocument: Statement: - Effect: Allow Action: - 's3:CreateBucket' - 's3:GetBucketNotification' - 's3:PutBucketNotification' Resource: - !Sub 'arn:${AWS::Partition}:s3:::${WafLogBucket}' - !Ref 'AWS::NoValue' - PolicyName: S3Access PolicyDocument: Statement: - Effect: Allow Action: - 's3:GetBucketLocation' - 's3:GetObject' - 's3:ListBucket' Resource: - !Sub 'arn:${AWS::Partition}:s3:::${AppAccessLogBucket}' - !If - ScannersProbesLambdaLogParser - PolicyName: S3AppAccessPut PolicyDocument: Statement: - Effect: Allow Action: 's3:PutObject' Resource: - !Sub 'arn:${AWS::Partition}:s3:::${AppAccessLogBucket}/${AWS::StackName}-app_log_conf.json' - !Ref 'AWS::NoValue' - !If - HttpFloodLambdaLogParser - PolicyName: S3WafAccessPut PolicyDocument: Statement: - Effect: Allow Action: 's3:PutObject' Resource: - !Sub 'arn:${AWS::Partition}:s3:::${WafLogBucket}/${AWS::StackName}-waf_log_conf.json' - !Ref 'AWS::NoValue' - !If - CustomResourceLambdaAccess - PolicyName: LambdaAccess PolicyDocument: Statement: - Effect: Allow Action: 'lambda:InvokeFunction' Resource: - !Sub 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:*AddAthenaPartitions*' - !Sub 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:*ReputationListsParser*' - !Ref 'AWS::NoValue' - PolicyName: WAFAccess PolicyDocument: Statement: - Effect: Allow Action: - 'wafv2:GetWebACL' - 'wafv2:UpdateWebACL' - 'wafv2:DeleteLoggingConfiguration' Resource: - !GetAtt WebACLStack.Outputs.WAFWebACLArn - PolicyName: IPSetAccess PolicyDocument: Statement: - Effect: Allow Action: - 'wafv2:GetIPSet' - 'wafv2:DeleteIPSet' Resource: - !Sub 'arn:${AWS::Partition}:wafv2:${AWS::Region}:${AWS::AccountId}:regional/ipset/${AWS::StackName}*' - !Sub 'arn:${AWS::Partition}:wafv2:${AWS::Region}:${AWS::AccountId}:global/ipset/${AWS::StackName}*' - !If - HttpFloodProtectionLogParserActivated - PolicyName: WAFLogsAccess PolicyDocument: Statement: - Effect: Allow Action: - 'wafv2:PutLoggingConfiguration' Resource: - !GetAtt WebACLStack.Outputs.WAFWebACLArn - Effect: Allow Action: 'iam:CreateServiceLinkedRole' Resource: - !Sub 'arn:${AWS::Partition}:iam::*:role/aws-service-role/wafv2.amazonaws.com/AWSServiceRoleForWAFV2Logging' Condition: StringLike: iam:AWSServiceName: 'wafv2.amazonaws.com' - !Ref 'AWS::NoValue' - PolicyName: CloudFormationAccess PolicyDocument: Statement: - Effect: Allow Action: 'cloudformation:DescribeStacks' Resource: - !Sub 'arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*' - PolicyName: LogsAccess PolicyDocument: Statement: - Effect: Allow Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: - !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*CustomResource*' - PolicyName: LogsGroupAccess PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'logs:DescribeLogGroups' Resource: - !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:*' - PolicyName: LogsGroupRetentionAccess PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'logs:PutRetentionPolicy' Resource: - !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*' - !If - ScannersProbesProtectionActivated - PolicyName: S3BucketLoggingAccess PolicyDocument: Statement: - Effect: Allow Action: - 's3:GetBucketLogging' - 's3:PutBucketLogging' Resource: - !Sub 'arn:${AWS::Partition}:s3:::${AppAccessLogBucket}' - !Ref 'AWS::NoValue' Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: >- WAFAccess, WAFRuleAccess, WAFIPSetAccess and WAFRateBasedRuleAccess - restricted to WafArnPrefix/AccountId; CloudFormationAccess - account, region and stack name; LogsAccess - permission restricted to account, region and log group name substring (CustomResource); - id: W76 reason: "The policy is long as it is scoped down to all the IP set ARNs and function ARNs." LambdaRolePartitionS3Logs: Type: 'AWS::IAM::Role' Condition: ScannersProbesAthenaLogParser Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: >- LogsAccess - permission restricted to account, region and log group name substring (MoveS3LogsForPartition) Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: '/' Policies: - !If - ScannersProbesAthenaLogParser - PolicyName: PartitionS3LogsAccess PolicyDocument: Statement: # S3 Resources - Effect: Allow Action: - 's3:GetObject' - 's3:DeleteObject' - 's3:PutObject' Resource: - !Sub 'arn:${AWS::Partition}:s3:::${AppAccessLogBucket}/*' - !Ref 'AWS::NoValue' - PolicyName: LogsAccess PolicyDocument: Statement: - Effect: Allow Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: - !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*MoveS3LogsForPartition*' LambdaRoleAddAthenaPartitions: Type: 'AWS::IAM::Role' Condition: AthenaLogParser Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: >- LogsAccess - permission restricted to account, region and log group name substring (AddAthenaPartitions) Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: '/' Policies: - !If - ScannersProbesAthenaLogParser - PolicyName: AddAthenaPartitionsForAppAccessLog PolicyDocument: Statement: # S3 Resources - Effect: Allow Action: - 's3:GetObject' - 's3:PutObject' - 's3:GetBucketLocation' - 's3:ListBucket' - 's3:ListBucketMultipartUploads' - 's3:ListMultipartUploadParts' - 's3:AbortMultipartUpload' - 's3:CreateBucket' Resource: - !Sub 'arn:${AWS::Partition}:s3:::${AppAccessLogBucket}/athena_results/*' - !Sub 'arn:${AWS::Partition}:s3:::${AppAccessLogBucket}' - !Sub 'arn:${AWS::Partition}:s3:::${AppAccessLogBucket}/*' # Athena Resources - Effect: Allow Action: - 'athena:StartQueryExecution' Resource: - !Sub 'arn:${AWS::Partition}:athena:${AWS::Region}:${AWS::AccountId}:workgroup/WAF*' # Glue Resources - Effect: Allow Action: - 'glue:GetTable' - 'glue:GetDatabase' - 'glue:UpdateDatabase' - 'glue:CreateDatabase' - 'glue:BatchCreatePartition' Resource: - !Sub 'arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:catalog' - !Sub 'arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:database/default' - !Sub 'arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:database/${WebACLStack.Outputs.GlueAccessLogsDatabase}' - !Sub 'arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:table/${WebACLStack.Outputs.GlueAccessLogsDatabase}/${WebACLStack.Outputs.GlueAppAccessLogsTable}' - !Ref 'AWS::NoValue' - !If - HttpFloodAthenaLogParser - PolicyName: AddAthenaPartitionsForWAFLog PolicyDocument: Statement: # S3 Resources - Effect: Allow Action: - 's3:GetObject' - 's3:PutObject' - 's3:GetBucketLocation' - 's3:ListBucket' - 's3:ListBucketMultipartUploads' - 's3:ListMultipartUploadParts' - 's3:AbortMultipartUpload' - 's3:CreateBucket' Resource: - !Sub 'arn:${AWS::Partition}:s3:::${WafLogBucket}/athena_results/*' - !Sub 'arn:${AWS::Partition}:s3:::${WafLogBucket}' - !Sub 'arn:${AWS::Partition}:s3:::${WafLogBucket}/*' # Athena Resources - Effect: Allow Action: - 'athena:StartQueryExecution' Resource: - !Sub 'arn:${AWS::Partition}:athena:${AWS::Region}:${AWS::AccountId}:workgroup/WAF*' # Glue Resources - Effect: Allow Action: - 'glue:GetTable' - 'glue:GetDatabase' - 'glue:UpdateDatabase' - 'glue:CreateDatabase' - 'glue:BatchCreatePartition' Resource: - !Sub 'arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:catalog' - !Sub 'arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:database/default' - !Sub 'arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:database/${WebACLStack.Outputs.GlueAccessLogsDatabase}' - !Sub 'arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:table/${WebACLStack.Outputs.GlueAccessLogsDatabase}/${WebACLStack.Outputs.GlueWafAccessLogsTable}' - !Ref 'AWS::NoValue' - PolicyName: LogsAccess PolicyDocument: Statement: - Effect: Allow Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: - !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*AddAthenaPartitions*' Helper: Type: 'AWS::Lambda::Function' Properties: Description: >- This lambda function verifies the main project's dependencies, requirements and implement auxiliary functions. Handler: 'helper.lambda_handler' Role: !GetAtt LambdaRoleHelper.Arn Code: S3Bucket: !Join ['-', [!FindInMap ["SourceCode", "General", "SourceBucket"], !Ref 'AWS::Region']] S3Key: !Join ['/', [!FindInMap ["SourceCode", "General", "KeyPrefix"], 'helper.zip']] Environment: Variables: LOG_LEVEL: !FindInMap ["Solution", "Data", "LogLevel"] SCOPE: !If [AlbEndpoint, 'REGIONAL', 'CLOUDFRONT'] USER_AGENT_EXTRA: !FindInMap [Solution, UserAgent, UserAgentExtra] Runtime: python3.10 MemorySize: 128 Timeout: 300 Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: There is no need to run this lambda in a VPC - id: W92 reason: There is no need for Reserved Concurrency LambdaRoleSetIPRetention: Type: 'AWS::IAM::Role' Condition: IPRetentionPeriod Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' Path: / Policies: - PolicyName: LogsAccess PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: - !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*SetIPRetention*' - PolicyName: DDBAccess PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'dynamodb:PutItem' Resource: - !GetAtt IPRetentionDDBTable.Arn Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: "LogsAccess permission restricted to account, region and log group name substring (SetIPRetention)." LambdaRoleRemoveExpiredIP: Type: 'AWS::IAM::Role' Condition: IPRetentionPeriod Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' Path: / Policies: - PolicyName: LogsAccess PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: - !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*RemoveExpiredIP*' - PolicyName: WAFAccess PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'wafv2:GetIPSet' - 'wafv2:UpdateIPSet' Resource: - !GetAtt WebACLStack.Outputs.WAFWhitelistSetV4Arn - !GetAtt WebACLStack.Outputs.WAFBlacklistSetV4Arn - !GetAtt WebACLStack.Outputs.WAFWhitelistSetV6Arn - !GetAtt WebACLStack.Outputs.WAFBlacklistSetV6Arn - PolicyName: DDBStreamAccess PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - "dynamodb:GetShardIterator" - "dynamodb:DescribeStream" - "dynamodb:GetRecords" - "dynamodb:ListStreams" Resource: - !GetAtt IPRetentionDDBTable.StreamArn - PolicyName: InvokeLambda PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - "lambda:InvokeFunction" Resource: - !GetAtt IPRetentionDDBTable.StreamArn Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: "LogsAccess permission restricted to account, region and log group name substring (RemoveExpiredIP)." SNSPublishPolicy: Type: "AWS::IAM::Policy" Condition: SNSEmail Properties: PolicyName: "SNSPublishPolicy" Roles: - Ref: LambdaRoleRemoveExpiredIP PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - "SNS:Publish" Resource: - !Ref IPExpirationSNSTopic CreateUniqueID: Type: 'Custom::CreateUUID' DependsOn: CheckRequirements Properties: ServiceToken: !GetAtt Helper.Arn SetCloudWatchLogGroupRetention: Type: 'Custom::SetCloudWatchLogGroupRetention' Condition: LogGroupRetentionEnabled DependsOn: CheckRequirements Properties: ServiceToken: !GetAtt CustomResource.Arn StackName: !Ref 'AWS::StackName' SolutionVersion: "%VERSION%" LogGroupRetention: !Ref LogGroupRetentionParam LogParserLambdaName: !If [LogParser, !Ref LogParser, !Ref 'AWS::NoValue'] HelperLambdaName: !Ref Helper MoveS3LogsForPartitionLambdaName: !If [ScannersProbesAthenaLogParser, !Ref MoveS3LogsForPartition, !Ref 'AWS::NoValue'] AddAthenaPartitionsLambdaName: !If [AthenaLogParser, !Ref AddAthenaPartitions, !Ref 'AWS::NoValue'] SetIPRetentionLambdaName: !If [IPRetentionPeriod, !Ref SetIPRetention, !Ref 'AWS::NoValue'] RemoveExpiredIPLambdaName: !If [IPRetentionPeriod, !Ref RemoveExpiredIP, !Ref 'AWS::NoValue'] ReputationListsParserLambdaName: !If [ReputationListsProtectionActivated, !Ref ReputationListsParser, !Ref 'AWS::NoValue'] BadBotParserLambdaName: !If [BadBotProtectionActivated, !Ref BadBotParser, !Ref 'AWS::NoValue'] CustomResourceLambdaName: !Ref CustomResource CustomTimerLambdaName: !GetAtt WebACLStack.Outputs.CustomTimerFunctionName CreateDeliveryStreamName: Type: 'Custom::CreateDeliveryStreamName' Condition: HttpFloodProtectionLogParserActivated DependsOn: CheckRequirements Properties: ServiceToken: !GetAtt Helper.Arn StackName: !Ref 'AWS::StackName' CreateGlueDatabaseName: Type: 'Custom::CreateGlueDatabaseName' Condition: AthenaLogParser DependsOn: CheckRequirements Properties: ServiceToken: !GetAtt Helper.Arn StackName: !Ref 'AWS::StackName' WafLogBucket: Type: 'AWS::S3::Bucket' Condition: HttpFloodProtectionLogParserActivated DependsOn: CheckRequirements DeletionPolicy: Retain UpdateReplacePolicy: Retain Properties: AccessControl: Private BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true LoggingConfiguration: DestinationBucketName: !Ref AccessLoggingBucket LogFilePrefix: WAF_Logs/ Metadata: cfn_nag: rules_to_suppress: - id: W51 reason: "WafLogBucket does not require a bucket policy." AccessLoggingBucket: Type: AWS::S3::Bucket Condition: CreateS3LoggingBucket DependsOn: CheckRequirements DeletionPolicy: Retain UpdateReplacePolicy: Retain Properties: BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 PublicAccessBlockConfiguration: BlockPublicAcls: True BlockPublicPolicy: True IgnorePublicAcls: True RestrictPublicBuckets: True Metadata: cfn_nag: rules_to_suppress: - id: W35 reason: "This bucket is an access logging bucket for another bucket and does not require access logging to be configured for it." AccessLoggingBucketPolicy: Type: AWS::S3::BucketPolicy Condition: CreateS3LoggingBucket Properties: Bucket: Ref: AccessLoggingBucket PolicyDocument: Statement: - Action: "s3:*" Condition: Bool: aws:SecureTransport: 'false' Effect: Deny Principal: "*" Resource: - !GetAtt AccessLoggingBucket.Arn - !Join ["/", [!GetAtt AccessLoggingBucket.Arn, "*"]] Sid: HttpsOnly - Sid: S3ServerAccessLogsPolicy Effect: Allow Principal: Service: logging.s3.amazonaws.com Action: - s3:PutObject Resource: - !GetAtt AccessLoggingBucket.Arn - !Join ["/", [!GetAtt AccessLoggingBucket.Arn, "*"]] Condition: ArnLike: aws:SourceArn: - !If [HttpFloodProtectionLogParserActivated, !GetAtt WafLogBucket.Arn, !GetAtt AccessLoggingBucket.Arn] - !Join ["", ["arn:aws:s3:::", !Ref AppAccessLogBucket]] StringEquals: aws:SourceAccount: !Ref 'AWS::AccountId' Version: '2012-10-17' LogParser: Type: 'AWS::Lambda::Function' Condition: LogParser Properties: Description: >- This function parses access logs to identify suspicious behavior, such as an abnormal amount of errors. It then blocks those IP addresses for a customer-defined period of time. Handler: 'log_parser.lambda_handler' Role: !GetAtt LambdaRoleLogParser.Arn Code: S3Bucket: !Join ['-', [!FindInMap ["SourceCode", "General", "SourceBucket"], !Ref 'AWS::Region']] S3Key: !Join ['/', [!FindInMap ["SourceCode", "General", "KeyPrefix"], 'log_parser.zip']] Environment: Variables: APP_ACCESS_LOG_BUCKET: !If [ScannersProbesProtectionActivated, !Ref AppAccessLogBucket, !Ref 'AWS::NoValue'] WAF_ACCESS_LOG_BUCKET: !If [HttpFloodProtectionLogParserActivated, !Ref WafLogBucket, !Ref 'AWS::NoValue'] SEND_ANONYMOUS_USAGE_DATA: !FindInMap ["Solution", "Data", "SendAnonymousUsageData"] UUID: !GetAtt CreateUniqueID.UUID LIMIT_IP_ADDRESS_RANGES_PER_IP_MATCH_CONDITION: '10000' MAX_AGE_TO_UPDATE: '30' REGION: !Ref 'AWS::Region' SCOPE: !If [AlbEndpoint, 'REGIONAL', 'CLOUDFRONT'] LOG_TYPE: !If [AlbEndpoint, 'alb', 'cloudfront'] METRIC_NAME_PREFIX: !Join ['', !Split ['-', !Ref 'AWS::StackName']] LOG_LEVEL: !FindInMap ["Solution", "Data", "LogLevel"] STACK_NAME: !Ref 'AWS::StackName' IP_SET_ID_HTTP_FLOODV4: !If [HttpFloodProtectionLogParserActivated, !GetAtt WebACLStack.Outputs.WAFHttpFloodSetV4Arn, !Ref 'AWS::NoValue'] IP_SET_ID_HTTP_FLOODV6: !If [HttpFloodProtectionLogParserActivated, !GetAtt WebACLStack.Outputs.WAFHttpFloodSetV6Arn, !Ref 'AWS::NoValue'] IP_SET_NAME_HTTP_FLOODV4: !If [HttpFloodProtectionLogParserActivated, !GetAtt WebACLStack.Outputs.NameHttpFloodSetV4, !Ref 'AWS::NoValue'] IP_SET_NAME_HTTP_FLOODV6: !If [HttpFloodProtectionLogParserActivated, !GetAtt WebACLStack.Outputs.NameHttpFloodSetV6, !Ref 'AWS::NoValue'] IP_SET_ID_SCANNERS_PROBESV4: !If [ScannersProbesProtectionActivated, !GetAtt WebACLStack.Outputs.WAFScannersProbesSetV4Arn, !Ref 'AWS::NoValue'] IP_SET_ID_SCANNERS_PROBESV6: !If [ScannersProbesProtectionActivated, !GetAtt WebACLStack.Outputs.WAFScannersProbesSetV6Arn, !Ref 'AWS::NoValue'] IP_SET_NAME_SCANNERS_PROBESV4: !If [ScannersProbesProtectionActivated, !GetAtt WebACLStack.Outputs.NameScannersProbesSetV4, !Ref 'AWS::NoValue'] IP_SET_NAME_SCANNERS_PROBESV6: !If [ScannersProbesProtectionActivated, !GetAtt WebACLStack.Outputs.NameScannersProbesSetV6, !Ref 'AWS::NoValue'] WAF_BLOCK_PERIOD: !Ref WAFBlockPeriod ERROR_THRESHOLD: !Ref ErrorThreshold REQUEST_THRESHOLD: !Ref RequestThreshold REQUEST_THRESHOLD_BY_COUNTRY: !Ref RequestThresholdByCountryParam HTTP_FLOOD_ATHENA_GROUP_BY: !Ref HTTPFloodAthenaQueryGroupByParam ATHENA_QUERY_RUN_SCHEDULE: !Ref AthenaQueryRunTimeScheduleParam SOLUTION_ID: !FindInMap [Solution, Data, SolutionID] METRICS_URL: !FindInMap [Solution, Data, MetricsURL] USER_AGENT_EXTRA: !FindInMap [Solution, UserAgent, UserAgentExtra] Runtime: python3.10 MemorySize: 512 Timeout: 300 Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: There is no need to run this lambda in a VPC - id: W92 reason: There is no need for Reserved Concurrency - id: W58 reason: "Log permissions are defined in the LambdaRoleLogParser policies" MoveS3LogsForPartition: Type: 'AWS::Lambda::Function' Condition: ScannersProbesAthenaLogParser Properties: Description: >- This function is triggered by S3 event to move log files(upon their arrival in s3) from their original location to a partitioned folder structure created per timestamps in file names, hence allowing the usage of partitioning within AWS Athena. Handler: 'partition_s3_logs.lambda_handler' Role: !GetAtt LambdaRolePartitionS3Logs.Arn Code: S3Bucket: !Join ['-', [!FindInMap ["SourceCode", "General", "SourceBucket"], !Ref 'AWS::Region']] S3Key: !Join ['/', [!FindInMap ["SourceCode", "General", "KeyPrefix"], 'log_parser.zip']] Environment: Variables: LOG_LEVEL: !FindInMap ["Solution", "Data", "LogLevel"] KEEP_ORIGINAL_DATA: !Ref KeepDataInOriginalS3Location ENDPOINT: !Ref EndpointType USER_AGENT_EXTRA: !FindInMap [Solution, UserAgent, UserAgentExtra] Runtime: python3.10 MemorySize: 512 Timeout: 300 Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: There is no need to run this lambda in a VPC - id: W92 reason: There is no need for Reserved Concurrency - id: W58 reason: "Log permissions are defined in the LambdaRolePartitionS3Logs policies" AddAthenaPartitions: Type: 'AWS::Lambda::Function' Condition: AthenaLogParser Properties: Description: >- This function adds a new hourly partition to athena table. It runs every hour, triggered by a CloudWatch event. Handler: 'add_athena_partitions.lambda_handler' Role: !GetAtt LambdaRoleAddAthenaPartitions.Arn Code: S3Bucket: !Join ['-', [!FindInMap ["SourceCode", "General", "SourceBucket"], !Ref 'AWS::Region']] S3Key: !Join ['/', [!FindInMap ["SourceCode", "General", "KeyPrefix"], 'log_parser.zip']] Environment: Variables: LOG_LEVEL: !FindInMap ["Solution", "Data", "LogLevel"] USER_AGENT_EXTRA: !FindInMap [Solution, UserAgent, UserAgentExtra] Runtime: python3.10 MemorySize: 512 Timeout: 300 Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: There is no need to run this lambda in a VPC - id: W92 reason: There is no need for Reserved Concurrency - id: W58 reason: "Log permissions are defined in the LambdaRoleAddAthenaPartitions policies" SetIPRetention: Type: 'AWS::Lambda::Function' Condition: IPRetentionPeriod Properties: Description: >- This lambda function processes CW events for WAF UpdateIPSet API calls. It writes relevant ip retention data into a DynamoDB table. Handler: 'set_ip_retention.lambda_handler' Role: !GetAtt LambdaRoleSetIPRetention.Arn Code: S3Bucket: !Join ['-', [!FindInMap ["SourceCode", "General", "SourceBucket"], !Ref 'AWS::Region']] S3Key: !Join ['/', [!FindInMap ["SourceCode", "General", "KeyPrefix"], 'ip_retention_handler.zip']] Environment: Variables: LOG_LEVEL: !FindInMap ["Solution", "Data", "LogLevel"] TABLE_NAME: !Ref IPRetentionDDBTable STACK_NAME: !Ref 'AWS::StackName' IP_RETENTION_PERIOD_ALLOWED_MINUTE: !Ref IPRetentionPeriodAllowedParam IP_RETENTION_PERIOD_DENIED_MINUTE: !Ref IPRetentionPeriodDeniedParam REMOVE_EXPIRED_IP_LAMBDA_ROLE_NAME: !Ref LambdaRoleRemoveExpiredIP USER_AGENT_EXTRA: !FindInMap [Solution, UserAgent, UserAgentExtra] Runtime: python3.10 MemorySize: 128 Timeout: 300 Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: There is no need to run this lambda in a VPC - id: W92 reason: There is no need for Reserved Concurrency RemoveExpiredIP: Type: 'AWS::Lambda::Function' Condition: IPRetentionPeriod Properties: Description: >- This lambda function processes the DDB streams records (IP) expired by TTL. It removes expired IPs from WAF allowed or denied IP sets. Handler: 'remove_expired_ip.lambda_handler' Role: !GetAtt LambdaRoleRemoveExpiredIP.Arn Code: S3Bucket: !Join ['-', [!FindInMap ["SourceCode", "General", "SourceBucket"], !Ref 'AWS::Region']] S3Key: !Join ['/', [!FindInMap ["SourceCode", "General", "KeyPrefix"], 'ip_retention_handler.zip']] Environment: Variables: LOG_LEVEL: !FindInMap ["Solution", "Data", "LogLevel"] SNS_EMAIL: !If [SNSEmail, 'yes', 'no'] SNS_TOPIC_ARN : !If [SNSEmail, !Ref IPExpirationSNSTopic, ''] SEND_ANONYMOUS_USAGE_DATA: !FindInMap ["Solution", "Data", "SendAnonymousUsageData"] UUID: !GetAtt CreateUniqueID.UUID SOLUTION_ID: !FindInMap [Solution, Data, SolutionID] METRICS_URL: !FindInMap [Solution, Data, MetricsURL] USER_AGENT_EXTRA: !FindInMap [Solution, UserAgent, UserAgentExtra] Runtime: python3.10 MemorySize: 512 Timeout: 300 Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: There is no need to run this lambda in a VPC - id: W92 reason: There is no need for Reserved Concurrency LambdaInvokePermissionAppLogParserS3: Type: 'AWS::Lambda::Permission' Condition: LogParser Properties: FunctionName: !GetAtt LogParser.Arn Action: 'lambda:InvokeFunction' Principal: s3.amazonaws.com SourceAccount: !Ref 'AWS::AccountId' LambdaInvokePermissionMoveS3LogsForPartition: Type: 'AWS::Lambda::Permission' Condition: ScannersProbesAthenaLogParser Properties: FunctionName: !GetAtt MoveS3LogsForPartition.Arn Action: 'lambda:InvokeFunction' Principal: s3.amazonaws.com SourceAccount: !Ref 'AWS::AccountId' LambdaPermissionAddAthenaPartitions: Type: AWS::Lambda::Permission Condition: AthenaLogParser Properties: FunctionName: !GetAtt AddAthenaPartitions.Arn Action: "lambda:InvokeFunction" Principal: "events.amazonaws.com" SourceArn: !GetAtt LambdaAddAthenaPartitionsEventsRule.Arn LambdaInvokePermissionSetIPRetention: Type: 'AWS::Lambda::Permission' Condition: IPRetentionPeriod Properties: FunctionName: !Ref SetIPRetention Action: 'lambda:InvokeFunction' Principal: events.amazonaws.com SourceArn: !GetAtt SetIPRetentionEventsRule.Arn LambdaAthenaWAFLogParser: Type: 'AWS::Events::Rule' Condition: HttpFloodAthenaLogParser Properties: Description: Security Automation - WAF Logs Athena parser ScheduleExpression: !If [ IsAthenaQueryRunEveryMinute, "rate(1 minute)", !Join ["", ["rate(", !Ref AthenaQueryRunTimeScheduleParam, " minutes)"]], ] Targets: - Arn: !GetAtt LogParser.Arn Id: LogParser Input: !Sub > { "resourceType": "LambdaAthenaWAFLogParser", "glueAccessLogsDatabase": "${FirehoseAthenaStack.Outputs.GlueAccessLogsDatabase}", "accessLogBucket": "${WafLogBucket}", "glueWafAccessLogsTable": "${FirehoseAthenaStack.Outputs.GlueWafAccessLogsTable}", "athenaWorkGroup":"${FirehoseAthenaStack.Outputs.WAFLogAthenaQueryWorkGroup}" } LambdaInvokePermissionWafLogParserCloudWatch: Type: 'AWS::Lambda::Permission' Condition: HttpFloodAthenaLogParser Properties: FunctionName: !Ref LogParser Action: 'lambda:InvokeFunction' Principal: events.amazonaws.com SourceArn: !GetAtt LambdaAthenaWAFLogParser.Arn LambdaAthenaAppLogParser: Type: 'AWS::Events::Rule' Condition: ScannersProbesAthenaLogParser Properties: Description: Security Automation - App Logs Athena parser ScheduleExpression: !If [ IsAthenaQueryRunEveryMinute, "rate(1 minute)", !Join ["", ["rate(", !Ref AthenaQueryRunTimeScheduleParam, " minutes)"]], ] Targets: - Arn: !GetAtt LogParser.Arn Id: LogParser Input: !Sub > { "resourceType": "LambdaAthenaAppLogParser", "glueAccessLogsDatabase": "${FirehoseAthenaStack.Outputs.GlueAccessLogsDatabase}", "accessLogBucket": "${AppAccessLogBucket}", "glueAppAccessLogsTable": "${FirehoseAthenaStack.Outputs.GlueAppAccessLogsTable}", "athenaWorkGroup": "${FirehoseAthenaStack.Outputs.WAFAppAccessLogAthenaQueryWorkGroup}" } LambdaAddAthenaPartitionsEventsRule: Type: 'AWS::Events::Rule' Condition: AthenaLogParser Properties: Description: Security Automations - Add partitions to Athena table ScheduleExpression: cron(* ? * * * *) State: ENABLED Targets: - Arn: !GetAtt AddAthenaPartitions.Arn Id: LambdaAddAthenaPartitions Input: !Sub - >- { "resourceType": "LambdaAddAthenaPartitionsEventsRule", "glueAccessLogsDatabase": "${GlueAccessLogsDatabase}", "accessLogBucket": "${AppAccessLogBucket}", "glueAppAccessLogsTable": "${GlueAppAccessLogsTable}", "glueWafAccessLogsTable": "${GlueWafAccessLogsTable}", "wafLogBucket": "${WafLogBucket}", "athenaWorkGroup": "${AthenaWorkGroup}" } - GlueAccessLogsDatabase: !GetAtt FirehoseAthenaStack.Outputs.GlueAccessLogsDatabase AppAccessLogBucket: !If [ScannersProbesAthenaLogParser, !Ref AppAccessLogBucket, ''] GlueAppAccessLogsTable: !If [ScannersProbesAthenaLogParser, !GetAtt FirehoseAthenaStack.Outputs.GlueAppAccessLogsTable, ''] GlueWafAccessLogsTable: !If [HttpFloodAthenaLogParser, !GetAtt FirehoseAthenaStack.Outputs.GlueWafAccessLogsTable, ''] WafLogBucket: !If [HttpFloodAthenaLogParser, !Ref WafLogBucket, ''] AthenaWorkGroup: !GetAtt FirehoseAthenaStack.Outputs.WAFAddPartitionAthenaQueryWorkGroup SetIPRetentionEventsRule: Type: AWS::Events::Rule Condition: IPRetentionPeriod Properties: Description: Security Automations for AWS WAF - Events rule for setting IP retention EventPattern: source: - aws.wafv2 detail-type: - AWS API Call via CloudTrail detail: eventSource: - wafv2.amazonaws.com eventName: - UpdateIPSet requestParameters: name: - !GetAtt WebACLStack.Outputs.NameWAFWhitelistSetV4 - !GetAtt WebACLStack.Outputs.NameWAFBlacklistSetV4 - !GetAtt WebACLStack.Outputs.NameWAFWhitelistSetV6 - !GetAtt WebACLStack.Outputs.NameWAFBlacklistSetV6 State: ENABLED Targets: - Arn: Fn::GetAtt: - SetIPRetention - Arn Id: SetIPRetentionLambda LambdaInvokePermissionAppLogParserCloudWatch: Type: 'AWS::Lambda::Permission' Condition: ScannersProbesAthenaLogParser Properties: FunctionName: !Ref LogParser Action: 'lambda:InvokeFunction' Principal: events.amazonaws.com SourceArn: !GetAtt LambdaAthenaAppLogParser.Arn ReputationListsParser: Type: 'AWS::Lambda::Function' Condition: ReputationListsProtectionActivated Properties: Description: >- This lambda function checks third-party IP reputation lists hourly for new IP ranges to block. These lists include the Spamhaus Dont Route Or Peer (DROP) and Extended Drop (EDROP) lists, the Proofpoint Emerging Threats IP list, and the Tor exit node list. Handler: 'reputation_lists.lambda_handler' Role: !GetAtt LambdaRoleReputationListsParser.Arn Code: S3Bucket: !Join ['-', [!FindInMap ["SourceCode", "General", "SourceBucket"], !Ref 'AWS::Region']] S3Key: !Join ['/', [!FindInMap ["SourceCode", "General", "KeyPrefix"], 'reputation_lists_parser.zip']] Runtime: python3.10 MemorySize: 512 Timeout: 300 Environment: Variables: IP_SET_ID_REPUTATIONV4: !GetAtt WebACLStack.Outputs.WAFReputationListsSetV4Arn IP_SET_ID_REPUTATIONV6: !GetAtt WebACLStack.Outputs.WAFReputationListsSetV6Arn IP_SET_NAME_REPUTATIONV4: !GetAtt WebACLStack.Outputs.NameReputationListsSetV4 IP_SET_NAME_REPUTATIONV6: !GetAtt WebACLStack.Outputs.NameReputationListsSetV6 SCOPE: !If [AlbEndpoint, 'REGIONAL', 'CLOUDFRONT'] LOG_LEVEL: !FindInMap ["Solution", "Data", "LogLevel"] URL_LIST: '[{"url":"https://www.spamhaus.org/drop/drop.txt"},{"url":"https://www.spamhaus.org/drop/edrop.txt"},{"url":"https://check.torproject.org/exit-addresses", "prefix":"ExitAddress"},{"url":"https://rules.emergingthreats.net/fwrules/emerging-Block-IPs.txt"}]' SOLUTION_ID: !FindInMap [Solution, Data, SolutionID] METRICS_URL: !FindInMap [Solution, Data, MetricsURL] STACK_NAME: !Ref 'AWS::StackName' LOG_TYPE: !If [AlbEndpoint, 'alb', 'cloudfront'] SEND_ANONYMOUS_USAGE_DATA: !FindInMap ["Solution", "Data", "SendAnonymousUsageData"] IPREPUTATIONLIST_METRICNAME: !GetAtt WebACLStack.Outputs.IPReputationListsMetricName USER_AGENT_EXTRA: !FindInMap [Solution, UserAgent, UserAgentExtra] Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: There is no need to run this lambda in a VPC - id: W92 reason: There is no need for Reserved Concurrency - id: W58 reason: "Log permissions are defined in the LambdaRoleReputationListsParser policies" ReputationListsParserEventsRule: Condition: ReputationListsProtectionActivated Type: 'AWS::Events::Rule' Properties: Description: Security Automation - WAF Reputation Lists ScheduleExpression: rate(1 hour) Targets: - Arn: !GetAtt ReputationListsParser.Arn Id: ReputationListsParser Input: !Sub - >- { "URL_LIST": [ {"url":"https://www.spamhaus.org/drop/drop.txt"}, {"url":"https://www.spamhaus.org/drop/edrop.txt"}, {"url":"https://check.torproject.org/exit-addresses", "prefix":"ExitAddress"}, {"url":"https://rules.emergingthreats.net/fwrules/emerging-Block-IPs.txt"} ], "IP_SET_ID_REPUTATIONV4": "${IP_SET_ID_REPUTATIONV4}", "IP_SET_ID_REPUTATIONV6": "${IP_SET_ID_REPUTATIONV6}", "IP_SET_NAME_REPUTATIONV4": "${IP_SET_NAME_REPUTATIONV4}", "IP_SET_NAME_REPUTATIONV6": "${IP_SET_NAME_REPUTATIONV6}", "SCOPE": "${SCOPE}" } - IP_SET_ID_REPUTATIONV4: !GetAtt WebACLStack.Outputs.WAFReputationListsSetV4Arn IP_SET_ID_REPUTATIONV6: !GetAtt WebACLStack.Outputs.WAFReputationListsSetV6Arn IP_SET_NAME_REPUTATIONV4: !GetAtt WebACLStack.Outputs.NameReputationListsSetV4 IP_SET_NAME_REPUTATIONV6: !GetAtt WebACLStack.Outputs.NameReputationListsSetV6 SCOPE: 'CLOUDFRONT' UpdateReputationListsOnLoad: Condition: ReputationListsProtectionActivated Type: 'Custom::UpdateReputationLists' DependsOn: WebACLStack Properties: ServiceToken: !GetAtt ReputationListsParser.Arn LambdaInvokePermissionReputationListsParser: Type: 'AWS::Lambda::Permission' Condition: ReputationListsProtectionActivated Properties: FunctionName: !Ref ReputationListsParser Action: 'lambda:InvokeFunction' Principal: events.amazonaws.com SourceArn: !GetAtt ReputationListsParserEventsRule.Arn BadBotParser: Type: 'AWS::Lambda::Function' Condition: BadBotProtectionActivated Properties: Description: >- This lambda function will intercepts and inspects trap endpoint requests to extract its IP address, and then add it to an AWS WAF block list. Handler: 'access_handler.lambda_handler' Role: !GetAtt LambdaRoleBadBot.Arn Code: S3Bucket: !Join ['-', [!FindInMap ["SourceCode", "General", "SourceBucket"], !Ref 'AWS::Region']] S3Key: !Join ['/', [!FindInMap ["SourceCode", "General", "KeyPrefix"], 'access_handler.zip']] Environment: Variables: SCOPE: !If [AlbEndpoint, 'REGIONAL', 'CLOUDFRONT'] IP_SET_ID_BAD_BOTV4: !GetAtt WebACLStack.Outputs.WAFBadBotSetV4Arn IP_SET_ID_BAD_BOTV6: !GetAtt WebACLStack.Outputs.WAFBadBotSetV6Arn IP_SET_NAME_BAD_BOTV4: !GetAtt WebACLStack.Outputs.NameBadBotSetV4 IP_SET_NAME_BAD_BOTV6: !GetAtt WebACLStack.Outputs.NameBadBotSetV6 SEND_ANONYMOUS_USAGE_DATA: !FindInMap ["Solution", "Data", "SendAnonymousUsageData"] UUID: !GetAtt CreateUniqueID.UUID REGION: !Ref 'AWS::Region' LOG_TYPE: !If [AlbEndpoint, 'alb', 'cloudfront'] METRIC_NAME_PREFIX: !Join ['', !Split ['-', !Ref 'AWS::StackName']] LOG_LEVEL: !FindInMap ["Solution", "Data", "LogLevel"] SOLUTION_ID: !FindInMap [Solution, Data, SolutionID] METRICS_URL: !FindInMap [Solution, Data, MetricsURL] STACK_NAME: !Ref 'AWS::StackName' USER_AGENT_EXTRA: !FindInMap [Solution, UserAgent, UserAgentExtra] Runtime: python3.10 MemorySize: 128 Timeout: 300 Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: There is no need to run this lambda in a VPC - id: W92 reason: There is no need for Reserved Concurrency - id: W58 reason: "Log permissions are defined in the LambdaRoleBadBot policies" LambdaInvokePermissionBadBot: Type: 'AWS::Lambda::Permission' Condition: BadBotProtectionActivated Properties: FunctionName: !GetAtt BadBotParser.Arn Action: 'lambda:InvokeFunction' Principal: apigateway.amazonaws.com ApiGatewayBadBot: Type: 'AWS::ApiGateway::RestApi' Condition: BadBotProtectionActivated DependsOn: CheckRequirements Properties: Name: Security Automation - WAF Bad Bot API Description: >- API created by AWS WAF Security Automation CloudFormation template. This endpoint will be used to capture bad bots. ApiGatewayBadBotResource: Type: 'AWS::ApiGateway::Resource' Condition: BadBotProtectionActivated Properties: RestApiId: !Ref ApiGatewayBadBot ParentId: !GetAtt ApiGatewayBadBot.RootResourceId PathPart: '{proxy+}' ApiGatewayBadBotMethodRoot: Type: 'AWS::ApiGateway::Method' Condition: BadBotProtectionActivated DependsOn: LambdaInvokePermissionBadBot Properties: RestApiId: !Ref ApiGatewayBadBot ResourceId: !GetAtt ApiGatewayBadBot.RootResourceId HttpMethod: ANY AuthorizationType: NONE RequestParameters: method.request.header.X-Forwarded-For: false Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Sub "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${BadBotParser.Arn}/invocations" Metadata: cfn_nag: rules_to_suppress: - id: W59 reason: "Creating a honeypot to lure badbots away." ApiGatewayBadBotMethod: Type: 'AWS::ApiGateway::Method' Condition: BadBotProtectionActivated DependsOn: LambdaInvokePermissionBadBot Properties: RestApiId: !Ref ApiGatewayBadBot ResourceId: !Ref ApiGatewayBadBotResource HttpMethod: ANY AuthorizationType: NONE RequestParameters: method.request.header.X-Forwarded-For: false Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Sub "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${BadBotParser.Arn}/invocations" Metadata: cfn_nag: rules_to_suppress: - id: W59 reason: "Creating a honeypot to lure badbots away." ApiGatewayBadBotDeployment: Type: 'AWS::ApiGateway::Deployment' Condition: BadBotProtectionActivated DependsOn: ApiGatewayBadBotMethod Properties: RestApiId: !Ref ApiGatewayBadBot Description: CloudFormation Deployment Stage StageName: CFDeploymentStage Metadata: cfn_nag: rules_to_suppress: - id: W45 reason: "Log not needed for this component." - id: W68 reason: "Usage Plan not required." ApiGatewayBadBotStage: Type: 'AWS::ApiGateway::Stage' Condition: BadBotProtectionActivated Properties: DeploymentId: !Ref ApiGatewayBadBotDeployment Description: Production Stage RestApiId: !Ref ApiGatewayBadBot StageName: ProdStage AccessLogSetting: DestinationArn: !GetAtt ApiGatewayBadBotStageAccessLogGroup.Arn Format: >- {"sourceIp": "$context.identity.sourceIp", "caller": "$context.identity.caller", "user": "$context.identity.user", "requestTime": "$context.requestTime", "httpMethod": "$context.httpMethod", "resourcePath": "$context.resourcePath", "protocol": "$context.protocol", "status": "$context.status", "responseLength": "$context.responseLength", "requestId": "$context.requestId"} Metadata: cfn_nag: rules_to_suppress: - id: W64 reason: "Usage Plan not required." ApiGatewayBadBotStageAccessLogGroup: Type: AWS::Logs::LogGroup Condition: BadBotProtectionActivated Metadata: cfn_nag: rules_to_suppress: - id: W84 reason: "Encryption not required: no sensitive data logged to CloudWatch." - id: W86 reason: "Leave the configuration of the expiration of the log data in CloudWatch log group to user due to potential compliance regulations." Properties: RetentionInDays: !If [LogGroupRetentionEnabled, !Ref LogGroupRetentionParam, !Ref 'AWS::NoValue'] ApiGatewayBadBotCloudWatchRole: Type: AWS::IAM::Role Condition: CreateApiGatewayBadBotCloudWatchRole Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: apigateway.amazonaws.com Policies: - PolicyName: LambdaRestApiCloudWatchRole PolicyDocument: Version: '2012-10-17' Statement: - Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:DescribeLogGroups - logs:DescribeLogStreams - logs:PutLogEvents - logs:GetLogEvents - logs:FilterLogEvents Effect: Allow Resource: - !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:*' ApiGatewayBadBotAccount: Type: AWS::ApiGateway::Account Condition: BadBotProtectionActivated Properties: CloudWatchRoleArn: !If [CreateApiGatewayBadBotCloudWatchRole, !GetAtt ApiGatewayBadBotCloudWatchRole.Arn, !Ref ApiGatewayBadBotCWRoleParam] DependsOn: - ApiGatewayBadBot CustomResource: Type: 'AWS::Lambda::Function' Properties: Description: >- This lambda function configures the Web ACL rules based on the features activated in the CloudFormation template. Handler: 'custom_resource.lambda_handler' Role: !GetAtt LambdaRoleCustomResource.Arn Code: S3Bucket: !Join ['-', [!FindInMap ["SourceCode", "General", "SourceBucket"], !Ref 'AWS::Region']] S3Key: !Join ['/', [!FindInMap ["SourceCode", "General", "KeyPrefix"], 'custom_resource.zip']] Environment: Variables: LOG_LEVEL: !FindInMap ["Solution", "Data", "LogLevel"] SCOPE: !If [AlbEndpoint, 'REGIONAL', 'CLOUDFRONT'] SOLUTION_ID: !FindInMap [Solution, Data, SolutionID] METRICS_URL: !FindInMap [Solution, Data, MetricsURL] USER_AGENT_EXTRA: !FindInMap [Solution, UserAgent, UserAgentExtra] Runtime: python3.10 MemorySize: 128 Timeout: 300 Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: There is no need to run this lambda in a VPC - id: W92 reason: There is no need for Reserved Concurrency - id: W58 reason: "Log permissions are defined in the LambdaRoleCustomResource policies" ConfigureAWSWAFLogs: Type: 'Custom::ConfigureAWSWAFLogs' Condition: HttpFloodProtectionLogParserActivated Properties: SolutionVersion: "%VERSION%" ServiceToken: !GetAtt CustomResource.Arn WAFWebACLArn: !GetAtt WebACLStack.Outputs.WAFWebACLArn DeliveryStreamArn: !GetAtt FirehoseAthenaStack.Outputs.FirehoseWAFLogsDeliveryStreamArn ConfigureAppAccessLogBucket: Type: 'Custom::ConfigureAppAccessLogBucket' Condition: ScannersProbesProtectionActivated Properties: ServiceToken: !GetAtt CustomResource.Arn Region: !Ref 'AWS::Region' SolutionVersion: "%VERSION%" AppAccessLogBucket: !Ref AppAccessLogBucket AppAccessLogBucketPrefix: !Ref AppAccessLogBucketPrefixParam LogParser: !If [LogParser, !GetAtt LogParser.Arn, !Ref 'AWS::NoValue'] ScannersProbesLambdaLogParser: !If [ScannersProbesLambdaLogParser, 'yes', 'no'] ScannersProbesAthenaLogParser: !If [ScannersProbesAthenaLogParser, 'yes', 'no'] MoveS3LogsForPartition: !If [ScannersProbesAthenaLogParser, !GetAtt MoveS3LogsForPartition.Arn, !Ref 'AWS::NoValue'] AccessLoggingBucket: !If [TurnOnAppAccessLogBucketLogging, !Ref AccessLoggingBucket, !Ref 'AWS::NoValue'] ConfigureWafLogBucket: Type: 'Custom::ConfigureWafLogBucket' Condition: HttpFloodProtectionLogParserActivated Properties: ServiceToken: !GetAtt CustomResource.Arn WafLogBucket: !Ref WafLogBucket LogParser: !If [LogParser, !GetAtt LogParser.Arn, !Ref 'AWS::NoValue'] HttpFloodLambdaLogParser: !If [HttpFloodLambdaLogParser, 'yes', 'no'] HttpFloodAthenaLogParser: !If [HttpFloodAthenaLogParser, 'yes', 'no'] GenerateAppLogParserConfFile: Type: 'Custom::GenerateAppLogParserConfFile' Condition: ScannersProbesLambdaLogParser DependsOn: ConfigureAppAccessLogBucket Properties: ServiceToken: !GetAtt CustomResource.Arn StackName: !Ref 'AWS::StackName' AppAccessLogBucket: !Ref AppAccessLogBucket ErrorThreshold: !Ref ErrorThreshold WAFBlockPeriod: !Ref WAFBlockPeriod GenerateWafLogParserConfFile: Type: 'Custom::GenerateWafLogParserConfFile' Condition: HttpFloodLambdaLogParser Properties: ServiceToken: !GetAtt CustomResource.Arn StackName: !Ref 'AWS::StackName' WafAccessLogBucket: !Ref WafLogBucket RequestThreshold: !Ref RequestThreshold WAFBlockPeriod: !Ref WAFBlockPeriod ConfigureWebAcl: Type: 'Custom::ConfigureWebAcl' Properties: ServiceToken: !GetAtt CustomResource.Arn # Stack input params ActivateSqlInjectionProtectionParam: !Ref ActivateSqlInjectionProtectionParam ActivateCrossSiteScriptingProtectionParam: !Ref ActivateCrossSiteScriptingProtectionParam ActivateHttpFloodProtectionParam: !Ref ActivateHttpFloodProtectionParam ActivateScannersProbesProtectionParam: !Ref ActivateScannersProbesProtectionParam ActivateReputationListsProtectionParam: !Ref ActivateReputationListsProtectionParam ActivateBadBotProtectionParam: !Ref ActivateBadBotProtectionParam ApiGatewayBadBotCWRoleParam: !If [ApiGatewayBadBotCWRoleNotExists, 'no', 'yes'] ActivateAWSManagedRulesParam: !Ref ActivateAWSManagedRulesParam ActivateAWSManagedAPParam: !Ref ActivateAWSManagedAPParam ActivateAWSManagedKBIParam: !Ref ActivateAWSManagedKBIParam ActivateAWSManagedIPRParam: !Ref ActivateAWSManagedIPRParam ActivateAWSManagedAIPParam: !Ref ActivateAWSManagedAIPParam ActivateAWSManagedSQLParam: !Ref ActivateAWSManagedSQLParam ActivateAWSManagedLinuxParam: !Ref ActivateAWSManagedLinuxParam ActivateAWSManagedPOSIXParam: !Ref ActivateAWSManagedPOSIXParam ActivateAWSManagedWindowsParam: !Ref ActivateAWSManagedWindowsParam ActivateAWSManagedPHPParam: !Ref ActivateAWSManagedPHPParam ActivateAWSManagedWPParam: !Ref ActivateAWSManagedWPParam KeepDataInOriginalS3Location: !Ref KeepDataInOriginalS3Location IPRetentionPeriodAllowedParam: !Ref IPRetentionPeriodAllowedParam IPRetentionPeriodDeniedParam: !Ref IPRetentionPeriodDeniedParam SNSEmailParam: !If [SNSEmail, 'yes', 'no'] UserDefinedAppAccessLogBucketPrefixParam: !If [UserDefinedAppAccessLogBucketPrefix, 'yes', 'no'] AppAccessLogBucketLoggingStatusParam: !Ref AppAccessLogBucketLoggingStatusParam RequestThresholdByCountryParam: !If [RequestThresholdByCountry, 'yes', 'no'] HTTPFloodAthenaQueryGroupByParam: !Ref HTTPFloodAthenaQueryGroupByParam AthenaQueryRunTimeScheduleParam: !Ref AthenaQueryRunTimeScheduleParam # AWS WAF Web ACL WAFWebACL: !GetAtt WebACLStack.Outputs.WAFWebACL # AWS WAF IP Sets - ID WAFWhitelistSetIPV4: !GetAtt WebACLStack.Outputs.WAFWhitelistSetV4Id WAFBlacklistSetIPV4: !GetAtt WebACLStack.Outputs.WAFBlacklistSetV4Id WAFHttpFloodSetIPV4: !If [HttpFloodProtectionLogParserActivated, !GetAtt WebACLStack.Outputs.WAFHttpFloodSetV4Id, !Ref 'AWS::NoValue'] WAFScannersProbesSetIPV4: !If [ScannersProbesProtectionActivated, !GetAtt WebACLStack.Outputs.WAFScannersProbesSetV4Id, !Ref 'AWS::NoValue'] WAFReputationListsSetIPV4: !If [ReputationListsProtectionActivated, !GetAtt WebACLStack.Outputs.WAFReputationListsSetV4Id, !Ref 'AWS::NoValue'] WAFBadBotSetIPV4: !If [BadBotProtectionActivated, !GetAtt WebACLStack.Outputs.WAFBadBotSetV4Id, !Ref 'AWS::NoValue'] WAFWhitelistSetIPV6: !GetAtt WebACLStack.Outputs.WAFWhitelistSetV6Id WAFBlacklistSetIPV6: !GetAtt WebACLStack.Outputs.WAFBlacklistSetV6Id WAFHttpFloodSetIPV6: !If [HttpFloodProtectionLogParserActivated, !GetAtt WebACLStack.Outputs.WAFHttpFloodSetV6Id, !Ref 'AWS::NoValue'] WAFScannersProbesSetIPV6: !If [ScannersProbesProtectionActivated, !GetAtt WebACLStack.Outputs.WAFScannersProbesSetV6Id, !Ref 'AWS::NoValue'] WAFReputationListsSetIPV6: !If [ReputationListsProtectionActivated, !GetAtt WebACLStack.Outputs.WAFReputationListsSetV6Id, !Ref 'AWS::NoValue'] WAFBadBotSetIPV6: !If [BadBotProtectionActivated, !GetAtt WebACLStack.Outputs.WAFBadBotSetV6Id, !Ref 'AWS::NoValue'] # AWS WAF IP Sets - Name WAFWhitelistSetIPV4Name: !GetAtt WebACLStack.Outputs.NameWAFWhitelistSetV4 WAFBlacklistSetIPV4Name: !GetAtt WebACLStack.Outputs.NameWAFBlacklistSetV4 WAFHttpFloodSetIPV4Name: !If [HttpFloodProtectionLogParserActivated, !GetAtt WebACLStack.Outputs.NameHttpFloodSetV4, !Ref 'AWS::NoValue'] WAFScannersProbesSetIPV4Name: !If [ScannersProbesProtectionActivated, !GetAtt WebACLStack.Outputs.NameScannersProbesSetV4, !Ref 'AWS::NoValue'] WAFReputationListsSetIPV4Name: !If [ReputationListsProtectionActivated, !GetAtt WebACLStack.Outputs.NameReputationListsSetV4, !Ref 'AWS::NoValue'] WAFBadBotSetIPV4Name: !If [BadBotProtectionActivated, !GetAtt WebACLStack.Outputs.NameBadBotSetV4, !Ref 'AWS::NoValue'] WAFWhitelistSetIPV6Name: !GetAtt WebACLStack.Outputs.NameWAFWhitelistSetV6 WAFBlacklistSetIPV6Name: !GetAtt WebACLStack.Outputs.NameWAFBlacklistSetV6 WAFHttpFloodSetIPV6Name: !If [HttpFloodProtectionLogParserActivated, !GetAtt WebACLStack.Outputs.NameHttpFloodSetV6, !Ref 'AWS::NoValue'] WAFScannersProbesSetIPV6Name: !If [ScannersProbesProtectionActivated, !GetAtt WebACLStack.Outputs.NameScannersProbesSetV6, !Ref 'AWS::NoValue'] WAFReputationListsSetIPV6Name: !If [ReputationListsProtectionActivated, !GetAtt WebACLStack.Outputs.NameReputationListsSetV6, !Ref 'AWS::NoValue'] WAFBadBotSetIPV6Name: !If [BadBotProtectionActivated, !GetAtt WebACLStack.Outputs.NameBadBotSetV6, !Ref 'AWS::NoValue'] # Extra Info UUID: !GetAtt CreateUniqueID.UUID Region: !Ref 'AWS::Region' RequestThreshold: !Ref RequestThreshold ErrorThreshold: !Ref ErrorThreshold WAFBlockPeriod: !Ref WAFBlockPeriod Version: "%VERSION%" SendAnonymousUsageData: !FindInMap ["Solution", "Data", "SendAnonymousUsageData"] CustomAddAthenaPartitions: Type: 'Custom::AddAthenaPartitions' Condition: AthenaLogParser Properties: ServiceToken: !GetAtt CustomResource.Arn AddAthenaPartitionsLambda: !GetAtt AddAthenaPartitions.Arn ResourceType: 'CustomResource' GlueAccessLogsDatabase: !GetAtt FirehoseAthenaStack.Outputs.GlueAccessLogsDatabase AppAccessLogBucket: !If [ScannersProbesAthenaLogParser, !Ref AppAccessLogBucket, ''] GlueAppAccessLogsTable: !If [ScannersProbesAthenaLogParser, !GetAtt FirehoseAthenaStack.Outputs.GlueAppAccessLogsTable, ''] GlueWafAccessLogsTable: !If [HttpFloodAthenaLogParser, !GetAtt FirehoseAthenaStack.Outputs.GlueWafAccessLogsTable, ''] WafLogBucket: !If [HttpFloodAthenaLogParser, !Ref WafLogBucket, ''] AthenaWorkGroup: !GetAtt FirehoseAthenaStack.Outputs.WAFAddPartitionAthenaQueryWorkGroup MonitoringDashboard: Type: AWS::CloudWatch::Dashboard DependsOn: CheckRequirements Properties: DashboardName: !Sub '${AWS::StackName}-${AWS::Region}' DashboardBody: !Sub - >- { "widgets": [{ "type": "metric", "x": 0, "y": 0, "width": 15, "height": 10, "properties": { "view": "timeSeries", "stacked": false, "stat": "Sum", "period": 300, "metrics": [ ["WAF", "BlockedRequests", "WebACL", "${WAFWebACLMetricName}", "Rule", "ALL" ${RegionMetric}], ["WAF", "AllowedRequests", "WebACL", "${WAFWebACLMetricName}", "Rule", "ALL" ${RegionMetric}] ], "region": "${RegionProperties}" } }] } - WAFWebACLMetricName: !GetAtt WebACLStack.Outputs.WAFWebACLMetricName RegionMetric: !If [AlbEndpoint, !Sub ', "Region", "${AWS::Region}"', ''] RegionProperties: !If [AlbEndpoint, !Sub '${AWS::Region}', 'us-east-1'] IPRetentionDDBTable: Type: 'AWS::DynamoDB::Table' Condition: IPRetentionPeriod Properties: AttributeDefinitions: - AttributeName: IPSetId AttributeType: S - AttributeName: ExpirationTime AttributeType: N BillingMode: PAY_PER_REQUEST KeySchema: - AttributeName: IPSetId KeyType: HASH - AttributeName: ExpirationTime KeyType: RANGE SSESpecification: SSEEnabled: True SSEType: KMS StreamSpecification: StreamViewType: OLD_IMAGE TimeToLiveSpecification: AttributeName: ExpirationTime Enabled: true Metadata: cfn_nag: rules_to_suppress: - id: W78 reason: "This DynamoDB table constains transactional ip retention data that will be expired by DynamoDB TTL. The data doesn't need to be retained after its lifecycle ends." IPExpirationSNSTopic: Type: AWS::SNS::Topic Condition: SNSEmail Properties: DisplayName: 'Security Automations for AWS WAF IP Expiration Notification' TopicName: !Join ['-', ['AWS-WAF-Security-Automations-IP-Expiration-Notification', !GetAtt CreateUniqueID.UUID]] KmsMasterKeyId: alias/aws/sns IPExpirationEmailNotification: Type: AWS::SNS::Subscription Condition: SNSEmail Properties: Endpoint: !Ref SNSEmailParam Protocol: email TopicArn: !Ref IPExpirationSNSTopic SNSNotificationPolicy: Type: AWS::SNS::TopicPolicy Condition: SNSEmail Metadata: cfn_nag: rules_to_suppress: - id: F18 reason: "Condition restricts permissions to current account." Properties: Topics: - !Ref IPExpirationSNSTopic 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 IPExpirationSNSTopic Condition: StringEquals: AWS:SourceOwner: !Sub ${AWS::AccountId} - Sid: TrustLambdaToPublishEventsToMyTopic Effect: Allow Principal: Service: lambda.amazonaws.com Action: SNS:Publish Resource: !Ref IPExpirationSNSTopic - Sid: AllowPublishThroughSSLOnly Action: SNS:Publish Effect: Deny Resource: - !Ref IPExpirationSNSTopic Condition: Bool: aws:SecureTransport: 'false' Principal: "*" DDBStreamToLambdaESMapping: Type: AWS::Lambda::EventSourceMapping Condition: IPRetentionPeriod Properties: Enabled: true EventSourceArn: !GetAtt IPRetentionDDBTable.StreamArn FunctionName: !GetAtt RemoveExpiredIP.Arn StartingPosition: LATEST # AppRegistry Application Application: Type: AWS::ServiceCatalogAppRegistry::Application Properties: Description: Service Catalog application to track and manage all your resources for the solution WAF Security Automations. The SolutionID is SO0006 and SolutionVersion is %VERSION%. Name: !Join - "-" - - !FindInMap [Solution, AppRegistry, "AppRegistryApplicationName"] - !Ref AWS::Region - !Ref AWS::AccountId - !Ref AWS::StackName Tags: { 'Solutions:SolutionID': !FindInMap [Solution, Data, "SolutionID"], 'Solutions:SolutionVersion': "%VERSION%", 'Solutions:SolutionName': !FindInMap [Solution, AppRegistry, "SolutionName"], 'Solutions:ApplicationType': 'AWS-Solutions', } AppRegistryApplicationStackAssociation: Type: AWS::ServiceCatalogAppRegistry::ResourceAssociation Properties: Application: !GetAtt Application.Id Resource: !Ref AWS::StackId ResourceType: CFN_STACK AppRegistryApplicationStackAssociationNestedStackWebACL: Type: AWS::ServiceCatalogAppRegistry::ResourceAssociation Properties: Application: !GetAtt Application.Id Resource: !Ref WebACLStack ResourceType: CFN_STACK AppRegistryApplicationStackAssociationNestedStackFirehoseAthena: Type: AWS::ServiceCatalogAppRegistry::ResourceAssociation Condition: CreateFirehoseAthenaStack Properties: Application: !GetAtt Application.Id Resource: !Ref FirehoseAthenaStack ResourceType: CFN_STACK DefaultApplicationAttributeGroup: Type: AWS::ServiceCatalogAppRegistry::AttributeGroup Properties: Name: !Sub 'AttrGrp-${AWS::Region}-${AWS::StackName}' Description: Attribute group for solution information. Attributes: { "ApplicationType" : 'AWS-Solutions', "Version": "%VERSION%", "SolutionID": !FindInMap [Solution, Data, "SolutionID"], "SolutionName": !FindInMap [Solution, AppRegistry, "SolutionName"] } AppRegistryApplicationAttributeAssociation: Type: AWS::ServiceCatalogAppRegistry::AttributeGroupAssociation Properties: Application: !GetAtt Application.Id AttributeGroup: !GetAtt DefaultApplicationAttributeGroup.Id Outputs: BadBotHoneypotEndpoint: Description: Bad Bot Honeypot Endpoint Value: !Sub 'https://${ApiGatewayBadBot}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${ApiGatewayBadBotStage}' Condition: BadBotProtectionActivated Export: Name: !Sub "${AWS::StackName}-BadBotHoneypotEndpoint" WAFWebACL: Description: AWS WAF WebACL Value: !GetAtt WebACLStack.Outputs.WAFWebACL Export: Name: !Sub "${AWS::StackName}-WAFWebACL" WAFWebACLArn: Description: AWS WAF WebACL Arn Value: !GetAtt WebACLStack.Outputs.WAFWebACLArn Export: Name: !Sub "${AWS::StackName}-WAFWebACLArn" WafLogBucket: Value: !Ref WafLogBucket Condition: HttpFloodProtectionLogParserActivated Export: Name: !Sub "${AWS::StackName}-WafLogBucket" AppAccessLogBucket: Value: !Ref AppAccessLogBucket Condition: ScannersProbesProtectionActivated Export: Name: !Sub "${AWS::StackName}-AppAccessLogBucket" SolutionVersion: Description: Solution Version Number Value: "%VERSION%" Export: Name: !Sub "${AWS::StackName}-SolutionVersion"