Skip to main content

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:PutMetricFilter
  • logs:DescribeLogGroups
  • logs:DescribeMetricFilters
  • cloudwatch:PutMetricAlarm
  • cloudwatch:DescribeAlarms
  • sns: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:

  1. Go to CloudTrail Console in us-east-1
  2. Click Trails and select your trail
  3. Check the CloudWatch Logs section for a configured log group
  4. If not configured, see the cloudtrail_cloudwatch_logging_enabled remediation guide

AWS Console Method

  1. Open CloudWatch in the AWS Console

  2. 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>)
  3. Create a Metric Filter

    • Click the Metric filters tab
    • Click Create metric filter
  4. Define the Filter Pattern

    • In the Filter pattern field, enter:
      { ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }
    • Click Next
  5. Assign a Metric

    • Filter name: NACLChangesFilter
    • Metric namespace: CISBenchmark
    • Metric name: NACLChanges
    • Metric value: 1
    • Click Next, then Create metric filter
  6. Create an Alarm from the Metric Filter

    • On the metric filter you just created, click Create alarm
  7. Configure the Alarm

    • Statistic: Sum
    • Period: 5 minutes
    • Threshold type: Static
    • Condition: Greater than or equal to 1
    • Click Next
  8. Set Up Notifications

    • Select an existing SNS topic or create a new one
    • Add your email address to receive alerts
    • Click Next
  9. Name the Alarm

    • Alarm name: NACLChangesAlarm
    • Description: Alarm when Network ACL changes are detected
    • Click Next, review settings, then Create alarm
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:

  1. In the AWS Console:

    • Go to CloudWatch > Log groups and select your CloudTrail log group
    • Click the Metric filters tab and confirm NACLChangesFilter exists
    • Go to CloudWatch > Alarms and confirm NACLChangesAlarm is listed with state OK or Insufficient data
  2. 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 ALARM state
    • 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

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 data state 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.