Skip to main content

Enable AWS WAFv2 Web ACL Logging

Overview

This check verifies that logging is enabled for your AWS WAFv2 Web ACLs. WAF logging captures detailed information about all web requests that are analyzed by your Web ACL, including which requests were blocked or allowed and why.

Risk

Without WAF logging enabled, you lose visibility into web traffic patterns and potential attacks. This makes it difficult to:

  • Detect SQL injection, cross-site scripting, and other web attacks
  • Identify credential stuffing or bot activity
  • Troubleshoot false positives from WAF rules
  • Investigate security incidents after they occur
  • Meet compliance requirements for security monitoring

Remediation Steps

Prerequisites

You need permission to modify WAFv2 resources and create logging destinations. Your logging destination name must start with aws-waf-logs-.

Supported logging destinations

WAFv2 supports three logging destinations:

  1. Amazon CloudWatch Logs - Best for real-time monitoring and alerting
  2. Amazon S3 - Best for long-term storage and compliance
  3. Amazon Kinesis Data Firehose - Best for streaming to analytics tools

The destination resource name must begin with aws-waf-logs- (for example, aws-waf-logs-my-webacl).

AWS Console Method

  1. Open the AWS WAF & Shield console at https://console.aws.amazon.com/wafv2/
  2. In the navigation pane, choose Web ACLs
  3. Select the Region where your Web ACL is deployed (use Global (CloudFront) for CloudFront distributions)
  4. Click on the Web ACL name you want to configure
  5. Select the Logging and metrics tab
  6. Click Enable logging
  7. Choose your logging destination:
    • For CloudWatch Logs: Select an existing log group or create one (name must start with aws-waf-logs-)
    • For S3: Select an existing bucket (name must start with aws-waf-logs-)
    • For Kinesis Data Firehose: Select an existing delivery stream (name must start with aws-waf-logs-)
  8. (Optional) Configure Redacted fields to exclude sensitive data from logs
  9. (Optional) Set up Log filtering to log only specific requests
  10. Click Save
AWS CLI (optional)

List your Web ACLs to get the ARN:

For regional resources (ALB, API Gateway):

aws wafv2 list-web-acls \
--scope REGIONAL \
--region us-east-1

For CloudFront distributions:

aws wafv2 list-web-acls \
--scope CLOUDFRONT \
--region us-east-1

Create a CloudWatch Logs log group (name must start with aws-waf-logs-):

aws logs create-log-group \
--log-group-name aws-waf-logs-my-webacl \
--region us-east-1

Enable logging for the Web ACL:

aws wafv2 put-logging-configuration \
--logging-configuration '{
"ResourceArn": "<WEB_ACL_ARN>",
"LogDestinationConfigs": [
"arn:aws:logs:us-east-1:<ACCOUNT_ID>:log-group:aws-waf-logs-my-webacl"
]
}' \
--region us-east-1

Replace:

  • <WEB_ACL_ARN> with your Web ACL ARN
  • <ACCOUNT_ID> with your AWS account ID

Optional: Enable logging with field redaction:

aws wafv2 put-logging-configuration \
--logging-configuration '{
"ResourceArn": "<WEB_ACL_ARN>",
"LogDestinationConfigs": [
"arn:aws:logs:us-east-1:<ACCOUNT_ID>:log-group:aws-waf-logs-my-webacl"
],
"RedactedFields": [
{"SingleHeader": {"Name": "authorization"}},
{"SingleHeader": {"Name": "cookie"}}
]
}' \
--region us-east-1
CloudFormation (optional)

This template creates a CloudWatch Logs log group and enables WAF logging for an existing Web ACL:

AWSTemplateFormatVersion: '2010-09-09'
Description: Enable logging for WAFv2 Web ACL

Parameters:
WebACLArn:
Type: String
Description: The ARN of the WAFv2 Web ACL to enable logging for

LogGroupName:
Type: String
Description: Name for the CloudWatch Logs log group (must start with aws-waf-logs-)
Default: aws-waf-logs-webacl

Resources:
WAFLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Ref LogGroupName
RetentionInDays: 90

WAFLogGroupResourcePolicy:
Type: AWS::Logs::ResourcePolicy
Properties:
PolicyName: !Sub '${LogGroupName}-policy'
PolicyDocument: !Sub |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "delivery.logs.amazonaws.com"
},
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${LogGroupName}:*",
"Condition": {
"ArnLike": {
"aws:SourceArn": "${WebACLArn}"
},
"StringEquals": {
"aws:SourceAccount": "${AWS::AccountId}"
}
}
}
]
}

WAFLoggingConfiguration:
Type: AWS::WAFv2::LoggingConfiguration
DependsOn:
- WAFLogGroup
- WAFLogGroupResourcePolicy
Properties:
ResourceArn: !Ref WebACLArn
LogDestinationConfigs:
- !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${LogGroupName}'

Outputs:
LogGroupArn:
Description: ARN of the CloudWatch Logs log group for WAF logs
Value: !GetAtt WAFLogGroup.Arn

Deploy the template:

aws cloudformation deploy \
--template-file waf-logging.yaml \
--stack-name waf-logging \
--parameter-overrides \
WebACLArn=<WEB_ACL_ARN> \
LogGroupName=aws-waf-logs-my-webacl \
--region us-east-1
Terraform (optional)
variable "web_acl_arn" {
description = "The ARN of the WAFv2 Web ACL to enable logging for"
type = string
}

variable "log_group_name" {
description = "Name for the CloudWatch Logs log group (must start with aws-waf-logs-)"
type = string
default = "aws-waf-logs-webacl"
}

variable "retention_days" {
description = "Number of days to retain WAF logs"
type = number
default = 90
}

data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

resource "aws_cloudwatch_log_group" "waf_logs" {
name = var.log_group_name
retention_in_days = var.retention_days
}

resource "aws_cloudwatch_log_resource_policy" "waf_logs_policy" {
policy_name = "${var.log_group_name}-policy"
policy_document = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "delivery.logs.amazonaws.com"
}
Action = [
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "${aws_cloudwatch_log_group.waf_logs.arn}:*"
Condition = {
ArnLike = {
"aws:SourceArn" = var.web_acl_arn
}
StringEquals = {
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
}
}
}
]
})
}

resource "aws_wafv2_web_acl_logging_configuration" "main" {
resource_arn = var.web_acl_arn
log_destination_configs = [aws_cloudwatch_log_group.waf_logs.arn]

depends_on = [aws_cloudwatch_log_resource_policy.waf_logs_policy]
}

output "log_group_arn" {
description = "ARN of the CloudWatch Logs log group for WAF logs"
value = aws_cloudwatch_log_group.waf_logs.arn
}

Apply the configuration:

terraform apply -var="web_acl_arn=<WEB_ACL_ARN>"

Verification

After enabling logging, verify the configuration is active:

  1. In the AWS WAF console, navigate to your Web ACL
  2. Select the Logging and metrics tab
  3. Confirm that logging shows as Enabled with your destination
CLI verification
aws wafv2 get-logging-configuration \
--resource-arn <WEB_ACL_ARN> \
--region us-east-1

A successful response shows the logging configuration details. If logging is not enabled, you will receive a WAFNonexistentItemException error.

Verify logs are being written (CloudWatch Logs):

aws logs describe-log-streams \
--log-group-name aws-waf-logs-my-webacl \
--order-by LastEventTime \
--descending \
--limit 5 \
--region us-east-1

Additional Resources

Notes

  • Naming requirement: All logging destination names must start with aws-waf-logs-
  • One destination per Web ACL: Each Web ACL can have only one logging destination configured
  • Regional vs. Global: For CloudFront Web ACLs, use --scope CLOUDFRONT and always specify --region us-east-1
  • Cost considerations: WAF logging incurs charges for the logging destination (CloudWatch Logs, S3, or Kinesis). Consider using log filtering to reduce costs by logging only specific traffic
  • Sensitive data: Use field redaction to exclude sensitive headers like Authorization or Cookie from logs
  • Permissions: WAF automatically creates the necessary IAM roles and resource policies when you enable logging through the console. When using CLI or IaC, you may need to create these manually