Skip to main content

Route Table Changes Alarm Should Be Enabled

More Info:

AWS Route Tables configuration changes should be monitored using CloudWatch alarms.

Risk Level

Medium

Address

Security

Compliance Standards

  • APRA CPS 234 (Australia)
  • AWS Well Architected Framework
  • BSI C5 (Germany)
  • Brazil LGPD
  • CCPA / CPRA (California)
  • CIS AWS
  • CIS Critical Security Controls v8
  • CMMC 2.0
  • CSA Cloud Controls Matrix v4
  • Cloudanix Best Practice
  • DPDPA
  • Digital Operational Resilience Act (EU)
  • HIPAA
  • HITRUST CSF
  • ISO 27001
  • ISO/IEC 27017
  • ISO/IEC 27018
  • ISO/IEC 27701
  • KSA PDPL
  • MAS Technology Risk Management (Singapore)
  • MITRE ATT&CK (Cloud)
  • NIS2 Directive
  • NIST
  • NIST CSF
  • NIST SP 800-171
  • NYDFS 23 NYCRR 500
  • SOC2
  • SWIFT Customer Security Controls Framework
  • Sarbanes-Oxley IT General Controls
  • UK NCSC Cyber Assessment Framework

Triage and Remediation

Remediation

Using Console

Below are step‑by‑step instructions (AWS Console only) to create a CloudWatch alarm for route table changes using CloudTrail logs.


Prerequisites

  1. CloudTrail must be enabled and logging management events (at least Write events) for EC2.
  2. CloudTrail must be delivering logs to a CloudWatch Logs log group.
    • If not configured, in the CloudTrail console → Trails → your trail → Edit → CloudWatch Logs and attach/create a log group and an IAM role.

Step 1: Identify/Create the CloudTrail Log Group

  1. Open CloudTrail console.
  2. In the left menu, select Trails.
  3. Click your active trail.
  4. Scroll to CloudWatch Logs section:
    • Note the CloudWatch Logs log group name.
    • If none is configured, enable it:
      • Click Edit.
      • Under CloudWatch Logs, choose or create a log group (e.g., /aws/cloudtrail/management).
      • Choose/create an IAM role as prompted.
      • Save.

Step 2: Create a Metric Filter for Route Table Changes

  1. Open CloudWatch console.

  2. In the left menu, select Logs → Log groups.

  3. Click the CloudTrail log group you identified (e.g., /aws/cloudtrail/management).

  4. Go to the Metric filters tab.

  5. Click Create metric filter.

  6. In Filter pattern, use a pattern that matches route-table–related API calls, for example:

    { ($.eventSource = "ec2.amazonaws.com") &&
    (
    ($.eventName = "CreateRoute") ||
    ($.eventName = "CreateRouteTable") ||
    ($.eventName = "ReplaceRoute") ||
    ($.eventName = "ReplaceRouteTableAssociation") ||
    ($.eventName = "DeleteRoute") ||
    ($.eventName = "DeleteRouteTable") ||
    ($.eventName = "DisassociateRouteTable")
    )
    }
  7. Click Next.

  8. For Filter name, enter something like:
    RouteTableChangesFilter

  9. Under Metric details:

    • Metric namespace: e.g., Security/NetworkChanges
    • Metric name: e.g., RouteTableChanges
    • Metric value: 1
    • Default value: leave blank or 0.
  10. Click Next, then Create metric filter.


Step 3: Create a CloudWatch Alarm on That Metric

  1. After creating the filter, you’ll see it listed.
    Select the filter and click Create alarm (or go to CloudWatch → Alarms → All alarms → Create alarm and select the metric you just created:
    Security/NetworkChanges → RouteTableChanges).
  2. In the Specify metric and conditions step:
    • Statistic: Sum
    • Period: e.g., 5 minutes
    • Threshold type: Static
    • Condition:
      • Greater than
      • Threshold value: 0
    • This means: alarm if at least one route table change occurs in 5 minutes.
  3. Click Next.

Step 4: Configure Alarm Notifications

  1. In Configure actions:
    • Alarm state trigger: In alarm
    • Under Notification, choose an SNS topic to send alerts to:
      • Select an existing topic (e.g., security-alerts), or
      • Click Create new topic, provide:
        • Topic name: e.g., route-table-changes-alerts
        • Email endpoint(s) (security, ops, etc.).
    • Confirm email subscription(s) if prompted.
  2. (Optional) Add additional actions (e.g., OpsCenter, EC2 action – normally not needed here).
  3. Click Next.

Step 5: Name and Create the Alarm

  1. Provide:
    • Alarm name: RouteTableChangesAlarm
    • Alarm description: e.g., Alerts on any changes to VPC route tables using CloudTrail events.
  2. Review all settings.
  3. Click Create alarm.

  1. Make a test route table change (e.g., add a non-impactful route in a test VPC and remove it).
  2. Wait for the metric to update (typically a few minutes).
  3. Confirm:
    • Alarm transitions to ALARM state.
    • Notification (email/SNS) is received.

This completes enabling a CloudWatch alarm for route table changes using the AWS Console.

Using CLI

Below is a minimal, end‑to‑end way to enable an alarm for Route Table changes in AWS using the AWS CLI.

Assumptions:

  • You already have a CloudTrail trail sending events to a CloudWatch Logs log group (if not, steps 1–2 cover it).
  • Replace all placeholder values (e.g., MY_TRAIL, my-log-group, my-route-table-changes-metric, my-route-table-changes-alarm, etc.) with your own.

1. (If needed) Configure CloudTrail to send logs to CloudWatch Logs

# 1.1 Create a log group for CloudTrail if you don’t have one
aws logs create-log-group --log-group-name /aws/cloudtrail/management

# 1.2 Attach a role/policy for CloudTrail to write to CloudWatch Logs
# Create an IAM role and policy separately (if not already present).
# Example role name: CloudTrail_CloudWatchLogs_Role

# 1.3 Update / create the trail to send logs to CloudWatch Logs
aws cloudtrail update-trail \
--name MY_TRAIL \
--cloud-watch-logs-log-group-arn arn:aws:logs:REGION:ACCOUNT_ID:log-group:/aws/cloudtrail/management \
--cloud-watch-logs-role-arn arn:aws:iam::ACCOUNT_ID:role/CloudTrail_CloudWatchLogs_Role

2. Create a CloudWatch Logs Metric Filter for Route Table changes

The filter will match API calls that modify Route Tables:

  • CreateRouteTable
  • DeleteRouteTable
  • AssociateRouteTable
  • DisassociateRouteTable
  • ReplaceRouteTableAssociation
  • ReplaceRouteTableRoute
  • CreateRoute
  • DeleteRoute
  • ReplaceRoute
aws logs put-metric-filter \
--log-group-name /aws/cloudtrail/management \
--filter-name route-table-changes-filter \
--filter-pattern '{ ($.eventName = "CreateRouteTable") || ($.eventName = "DeleteRouteTable") || ($.eventName = "AssociateRouteTable") || ($.eventName = "DisassociateRouteTable") || ($.eventName = "ReplaceRouteTableAssociation") || ($.eventName = "ReplaceRouteTableRoute") || ($.eventName = "CreateRoute") || ($.eventName = "DeleteRoute") || ($.eventName = "ReplaceRoute") }' \
--metric-transformations \
metricName=my-route-table-changes-metric,metricNamespace=Security,metricValue=1

This will emit a metric Security/my-route-table-changes-metric with value 1 for every matched event.


3. Create an SNS topic for notifications (if you don’t have one)

aws sns create-topic --name route-table-changes-topic

# Capture the TopicArn from the output, e.g.:
# arn:aws:sns:REGION:ACCOUNT_ID:route-table-changes-topic

# Optionally subscribe an email
aws sns subscribe \
--topic-arn arn:aws:sns:REGION:ACCOUNT_ID:route-table-changes-topic \
--protocol email \
--notification-endpoint you@example.com

4. Create a CloudWatch Alarm on the metric

Alarm if at least 1 route table change occurs in a 5-minute period:

aws cloudwatch put-metric-alarm \
--alarm-name my-route-table-changes-alarm \
--alarm-description "Alarm when VPC Route Tables are changed" \
--metric-name my-route-table-changes-metric \
--namespace Security \
--statistic Sum \
--period 300 \
--threshold 1 \
--comparison-operator GreaterThanOrEqualToThreshold \
--evaluation-periods 1 \
--alarm-actions arn:aws:sns:REGION:ACCOUNT_ID:route-table-changes-topic \
--treat-missing-data notBreaching

Once done:

  • Any change to Route Tables (via the listed API calls) will be logged by CloudTrail into the log group.
  • The metric filter will emit a metric per event.
  • The CloudWatch alarm will trigger and send a notification via SNS whenever at least one change is detected in the evaluation period.
Using Python

Below is one straightforward way to remediate this in AWS using Python/boto3:

Goal:

  • Detect route table changes via CloudTrail
  • Send them to CloudWatch Logs
  • Use a metric filter and CloudWatch Alarm to trigger notifications (e.g., via SNS)

Assumptions:

  • You have AWS credentials configured.
  • You have boto3 installed.
  • You have (or will create) an SNS topic and subscription for notifications.

1. Enable/Verify CloudTrail With CloudWatch Logs

CloudTrail must log management events and send them to a CloudWatch Logs log group.

import boto3

region = "us-east-1"
trail_name = "org-route-table-changes-trail"
log_group_name = "/aws/cloudtrail/route-table-changes"

logs = boto3.client("logs", region_name=region)
ct = boto3.client("cloudtrail", region_name=region)

# 1a. Create log group if it doesn't exist
def ensure_log_group():
try:
logs.create_log_group(logGroupName=log_group_name)
except logs.exceptions.ResourceAlreadyExistsException:
pass

# 1b. Create or update a trail that sends logs to CloudWatch Logs
def ensure_trail():
# Replace with an existing S3 bucket used for CloudTrail
s3_bucket_name = "my-cloudtrail-logs-bucket"

# Create or update trail
trails = ct.describe_trails(trailNameList=[trail_name])["trailList"]
if not trails:
ct.create_trail(
Name=trail_name,
S3BucketName=s3_bucket_name,
IsMultiRegionTrail=True,
IncludeGlobalServiceEvents=True,
IsOrganizationTrail=False,
CloudWatchLogsLogGroupArn=f"arn:aws:logs:{region}:YOUR_ACCOUNT_ID:log-group:{log_group_name}",
CloudWatchLogsRoleArn="arn:aws:iam::YOUR_ACCOUNT_ID:role/CloudTrail_CloudWatchLogs_Role",
)
else:
ct.update_trail(
Name=trail_name,
CloudWatchLogsLogGroupArn=f"arn:aws:logs:{region}:YOUR_ACCOUNT_ID:log-group:{log_group_name}",
CloudWatchLogsRoleArn="arn:aws:iam::YOUR_ACCOUNT_ID:role/CloudTrail_CloudWatchLogs_Role",
)

# Ensure logging is on
ct.start_logging(Name=trail_name)


ensure_log_group()
ensure_trail()

Notes:

  • You must have an IAM role (CloudTrail_CloudWatchLogs_Role) with the proper trust and permissions for CloudTrail → CloudWatch Logs.
  • Replace YOUR_ACCOUNT_ID, bucket name, role ARN as needed.

2. Create a Metric Filter for Route Table Changes

Filter CloudTrail events for route-table–related eventNames on EC2.

Common relevant events:

  • CreateRoute, DeleteRoute, ReplaceRoute
  • CreateRouteTable, DeleteRouteTable
  • AssociateRouteTable, DisassociateRouteTable, ReplaceRouteTableAssociation
metric_filter_name = "RouteTableChangesFilter"
metric_namespace = "SecurityMonitoring"
metric_name = "RouteTableChangeCount"

filter_pattern = """
{ ($.eventSource = "ec2.amazonaws.com") &&
(
$.eventName = "CreateRoute" ||
$.eventName = "DeleteRoute" ||
$.eventName = "ReplaceRoute" ||
$.eventName = "CreateRouteTable" ||
$.eventName = "DeleteRouteTable" ||
$.eventName = "AssociateRouteTable" ||
$.eventName = "DisassociateRouteTable" ||
$.eventName = "ReplaceRouteTableAssociation"
)
}
"""

logs.put_metric_filter(
logGroupName=log_group_name,
filterName=metric_filter_name,
filterPattern=filter_pattern.strip(),
metricTransformations=[
{
"metricName": metric_name,
"metricNamespace": metric_namespace,
"metricValue": "1",
}
],
)

3. Create an SNS Topic (If Needed)

sns = boto3.client("sns", region_name=region)
topic_name = "route-table-changes-topic"

topic_arn = sns.create_topic(Name=topic_name)["TopicArn"]

# Optionally subscribe an email
sns.subscribe(
TopicArn=topic_arn,
Protocol="email",
Endpoint="security-team@example.com",
)
print("Confirm the email subscription from your inbox.")

4. Create a CloudWatch Alarm on the Metric

Alarm when at least 1 route table change occurs in a 5-minute period.

cw = boto3.client("cloudwatch", region_name=region)

alarm_name = "RouteTableChangesAlarm"

cw.put_metric_alarm(
AlarmName=alarm_name,
AlarmDescription="Alarm when any VPC route table is modified.",
Namespace=metric_namespace,
MetricName=metric_name,
Statistic="Sum",
Period=300, # 5 minutes
EvaluationPeriods=1,
Threshold=1.0,
ComparisonOperator="GreaterThanOrEqualToThreshold",
TreatMissingData="notBreaching",
AlarmActions=[topic_arn],
)

5. Validate

  • Make a test change to a route table.
  • Wait a few minutes.
  • Verify:
    • CloudTrail shows the event.
    • Metric in CloudWatch (SecurityMonitoring/RouteTableChangeCount) increments.
    • Alarm transitions to ALARM and sends an SNS notification.

This configuration ensures “Route Table Changes Alarm” is enabled via CloudWatch and fully managed by Python/boto3.

Using Terraform
# CloudWatch Logs metric filter for route table changes
resource "aws_cloudwatch_log_metric_filter" "route_table_changes" {
name = "RouteTableChangesFilter"
log_group_name = "CLOUDTRAIL_LOG_GROUP_NAME" # replace with your CloudTrail log group name

pattern = "{ ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName = DisassociateRouteTable) }"

metric_transformation {
name = "RouteTableChangesMetric"
namespace = "CloudTrailMetrics"
value = "1"
}
}

# Optional: SNS topic for alarm notifications (or use an existing one)
resource "aws_sns_topic" "route_table_changes_notifications" {
name = "RouteTableChangeNotifications"
}

# CloudWatch alarm for the route table changes metric
resource "aws_cloudwatch_metric_alarm" "route_table_changes" {
alarm_name = "RouteTableChangesAlarm"
alarm_description = "Monitors for route table changes"
namespace = "CloudTrailMetrics"
metric_name = "RouteTableChangesMetric"
statistic = "Sum"
period = 300
evaluation_periods = 1
threshold = 1
comparison_operator = "GreaterThanOrEqualToThreshold"

alarm_actions = [
aws_sns_topic.route_table_changes_notifications.arn
# or replace with an existing SNS topic ARN:
# "ROUTE_TABLE_CHANGES_ALARM_SNS_TOPIC_ARN"
]

depends_on = [aws_cloudwatch_log_metric_filter.route_table_changes]
}

This change will create (or, if the same names already exist and are imported, update) the metric filter RouteTableChangesFilter and alarm RouteTableChangesAlarm; if they are currently unmanaged by Terraform, importing or replacing them may overwrite existing settings. After adding this, terraform plan should show one aws_cloudwatch_log_metric_filter and one aws_cloudwatch_metric_alarm (and optionally aws_sns_topic) to be created or updated with the exact pattern, metric, and threshold described above.

Additional Reading: