CloudWatch Network ACL Changes Alarm
Overview
This check verifies that you have a CloudWatch alarm configured to detect changes to Network Access Control Lists (NACLs) in your AWS account. NACLs are stateless firewalls that control traffic at the subnet level. Monitoring changes to them helps you detect unauthorized network policy modifications recorded by CloudTrail.
The check looks for a CloudWatch metric filter and alarm that captures these NACL-related API calls:
- CreateNetworkAcl
- CreateNetworkAclEntry
- DeleteNetworkAcl
- DeleteNetworkAclEntry
- ReplaceNetworkAclEntry
- ReplaceNetworkAclAssociation
Risk
Without monitoring for NACL changes, you lose visibility into network security modifications. This creates risks including:
- Confidentiality loss: An attacker could open ingress or egress rules to exfiltrate data
- Network integrity degradation: Unauthorized changes could enable lateral movement or bypass network segmentation
- Availability impact: Misconfigured rules could create traffic blackholes or lock out legitimate users
- Delayed incident response: Without alerts, malicious changes may go unnoticed for hours or days
Remediation Steps
Prerequisites
You need:
- AWS Console access with permissions to create CloudWatch metric filters and alarms
- A CloudTrail trail that sends logs to a CloudWatch Logs log group
- An SNS topic for alarm notifications (optional but recommended)
Required IAM permissions (for administrators)
Your IAM user or role needs these permissions:
logs:PutMetricFilterlogs:DescribeLogGroupslogs:DescribeMetricFilterscloudwatch:PutMetricAlarmcloudwatch:DescribeAlarmssns:CreateTopic(if creating a new SNS topic)sns:Subscribe(if adding subscriptions)
Prerequisite: Ensure CloudTrail sends logs to CloudWatch
Before creating the alarm, verify your CloudTrail trail is integrated with CloudWatch Logs:
- Go to CloudTrail Console in us-east-1
- Click Trails and select your trail
- Check the CloudWatch Logs section for a configured log group
- If not configured, see the
cloudtrail_cloudwatch_logging_enabledremediation guide
AWS Console Method
-
Open CloudWatch in the AWS Console
- Go to CloudWatch Console in us-east-1
-
Navigate to Log Groups
- In the left sidebar, expand Logs and click Log groups
- Find and click on your CloudTrail log group (often named
/aws/cloudtrail/<trail-name>)
-
Create a Metric Filter
- Click the Metric filters tab
- Click Create metric filter
-
Define the Filter Pattern
- In the Filter pattern field, enter:
{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) } - Click Next
- In the Filter pattern field, enter:
-
Assign a Metric
- Filter name:
NACLChangesFilter - Metric namespace:
CISBenchmark - Metric name:
NACLChanges - Metric value:
1 - Click Next, then Create metric filter
- Filter name:
-
Create an Alarm from the Metric Filter
- On the metric filter you just created, click Create alarm
-
Configure the Alarm
- Statistic: Sum
- Period: 5 minutes
- Threshold type: Static
- Condition: Greater than or equal to 1
- Click Next
-
Set Up Notifications
- Select an existing SNS topic or create a new one
- Add your email address to receive alerts
- Click Next
-
Name the Alarm
- Alarm name:
NACLChangesAlarm - Description:
Alarm when Network ACL changes are detected - Click Next, review settings, then Create alarm
- Alarm name:
AWS CLI (optional)
Step 1: Identify your CloudTrail log group
Find the log group associated with your CloudTrail trail:
aws cloudtrail describe-trails \
--region us-east-1 \
--query 'trailList[*].{Name:Name,LogGroup:CloudWatchLogsLogGroupArn}'
Note the log group name (e.g., /aws/cloudtrail/my-trail).
Step 2: Create the metric filter
Replace <log-group-name> with your CloudTrail log group name:
aws logs put-metric-filter \
--log-group-name <log-group-name> \
--filter-name NACLChangesFilter \
--filter-pattern '{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }' \
--metric-transformations metricName=NACLChanges,metricNamespace=CISBenchmark,metricValue=1 \
--region us-east-1
Step 3: Create an SNS topic for notifications (if needed)
aws sns create-topic \
--name NACLChangesAlarmTopic \
--region us-east-1
Subscribe your email to the topic (replace <your-account-id> and <your-email>):
aws sns subscribe \
--topic-arn arn:aws:sns:us-east-1:<your-account-id>:NACLChangesAlarmTopic \
--protocol email \
--notification-endpoint <your-email> \
--region us-east-1
Step 4: Create the CloudWatch alarm
Replace <your-account-id> with your AWS account ID:
aws cloudwatch put-metric-alarm \
--alarm-name NACLChangesAlarm \
--alarm-description "Alarm when Network ACL changes are detected" \
--metric-name NACLChanges \
--namespace CISBenchmark \
--statistic Sum \
--period 300 \
--evaluation-periods 1 \
--threshold 1 \
--comparison-operator GreaterThanOrEqualToThreshold \
--alarm-actions arn:aws:sns:us-east-1:<your-account-id>:NACLChangesAlarmTopic \
--treat-missing-data notBreaching \
--region us-east-1
CloudFormation (optional)
This template creates the metric filter, SNS topic, and CloudWatch alarm:
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudWatch alarm for Network ACL changes monitoring
Parameters:
CloudTrailLogGroupName:
Type: String
Description: Name of the CloudTrail CloudWatch Logs log group
Default: /aws/cloudtrail/my-trail
NotificationEmail:
Type: String
Description: Email address for alarm notifications
Resources:
NACLChangesTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: NACLChangesAlarmTopic
NACLChangesTopicSubscription:
Type: AWS::SNS::Subscription
Properties:
TopicArn: !Ref NACLChangesTopic
Protocol: email
Endpoint: !Ref NotificationEmail
NACLChangesMetricFilter:
Type: AWS::Logs::MetricFilter
Properties:
LogGroupName: !Ref CloudTrailLogGroupName
FilterName: NACLChangesFilter
FilterPattern: '{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }'
MetricTransformations:
- MetricName: NACLChanges
MetricNamespace: CISBenchmark
MetricValue: '1'
NACLChangesAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: NACLChangesAlarm
AlarmDescription: Alarm when Network ACL changes are detected
MetricName: NACLChanges
Namespace: CISBenchmark
Statistic: Sum
Period: 300
EvaluationPeriods: 1
Threshold: 1
ComparisonOperator: GreaterThanOrEqualToThreshold
AlarmActions:
- !Ref NACLChangesTopic
TreatMissingData: notBreaching
Outputs:
AlarmArn:
Description: ARN of the NACL changes alarm
Value: !GetAtt NACLChangesAlarm.Arn
TopicArn:
Description: ARN of the SNS topic for notifications
Value: !Ref NACLChangesTopic
Deploy with:
aws cloudformation deploy \
--template-file nacl-changes-alarm.yaml \
--stack-name nacl-changes-monitoring \
--parameter-overrides \
CloudTrailLogGroupName=/aws/cloudtrail/my-trail \
NotificationEmail=your-email@example.com \
--region us-east-1
Terraform (optional)
# Variables
variable "cloudtrail_log_group_name" {
description = "Name of the CloudTrail CloudWatch Logs log group"
type = string
default = "/aws/cloudtrail/my-trail"
}
variable "notification_email" {
description = "Email address for alarm notifications"
type = string
}
# SNS Topic for notifications
resource "aws_sns_topic" "nacl_changes" {
name = "NACLChangesAlarmTopic"
}
resource "aws_sns_topic_subscription" "nacl_changes_email" {
topic_arn = aws_sns_topic.nacl_changes.arn
protocol = "email"
endpoint = var.notification_email
}
# Metric filter for NACL changes
resource "aws_cloudwatch_log_metric_filter" "nacl_changes" {
name = "NACLChangesFilter"
log_group_name = var.cloudtrail_log_group_name
pattern = "{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }"
metric_transformation {
name = "NACLChanges"
namespace = "CISBenchmark"
value = "1"
}
}
# CloudWatch alarm for NACL changes
resource "aws_cloudwatch_metric_alarm" "nacl_changes" {
alarm_name = "NACLChangesAlarm"
alarm_description = "Alarm when Network ACL changes are detected"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = 1
metric_name = "NACLChanges"
namespace = "CISBenchmark"
period = 300
statistic = "Sum"
threshold = 1
treat_missing_data = "notBreaching"
alarm_actions = [aws_sns_topic.nacl_changes.arn]
depends_on = [aws_cloudwatch_log_metric_filter.nacl_changes]
}
# Outputs
output "alarm_arn" {
description = "ARN of the NACL changes alarm"
value = aws_cloudwatch_metric_alarm.nacl_changes.arn
}
output "sns_topic_arn" {
description = "ARN of the SNS topic for notifications"
value = aws_sns_topic.nacl_changes.arn
}
Deploy with:
terraform init
terraform plan -var="notification_email=your-email@example.com"
terraform apply -var="notification_email=your-email@example.com"
Verification
After setting up the alarm, verify it is working:
-
In the AWS Console:
- Go to CloudWatch > Log groups and select your CloudTrail log group
- Click the Metric filters tab and confirm
NACLChangesFilterexists - Go to CloudWatch > Alarms and confirm
NACLChangesAlarmis listed with stateOKorInsufficient data
-
Test the alarm (optional):
- Create a temporary NACL in a test VPC
- Wait 5-10 minutes for the event to flow through CloudTrail
- Check that the alarm transitions to
ALARMstate - Delete the test NACL when done
CLI verification commands
Check that the metric filter exists:
aws logs describe-metric-filters \
--log-group-name <log-group-name> \
--filter-name-prefix NACLChanges \
--region us-east-1
Check the alarm configuration:
aws cloudwatch describe-alarms \
--alarm-names NACLChangesAlarm \
--region us-east-1
The output should show the alarm with the correct metric name, namespace, and threshold.
Additional Resources
- AWS Documentation: Creating CloudWatch Alarms for CloudTrail Events
- AWS Documentation: Network ACLs
- CIS AWS Foundations Benchmark
- AWS Documentation: Filter and Pattern Syntax
Notes
- CloudTrail prerequisite: This alarm requires CloudTrail to be sending logs to CloudWatch Logs. If that is not configured, set it up first (see
cloudtrail_cloudwatch_logging_enabled). - Event delay: CloudTrail events can take 5-15 minutes to appear in CloudWatch Logs. Your alarm may not trigger immediately after a NACL change.
- Alarm state: A new alarm starts in
Insufficient datastate until enough data points are collected. This is normal. - SNS confirmation: If you created a new SNS subscription, check your email and confirm the subscription to receive notifications.
- Cost considerations: CloudWatch alarms and log storage incur costs. Review CloudWatch pricing for your expected usage.
- Multiple regions: If you have NACLs in multiple regions, ensure your CloudTrail trail is multi-region or set up monitoring in each region.