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
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
- CloudTrail must be enabled and logging management events (at least Write events) for EC2.
- 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
- Open CloudTrail console.
- In the left menu, select Trails.
- Click your active trail.
- 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
-
Open CloudWatch console.
-
In the left menu, select Logs → Log groups.
-
Click the CloudTrail log group you identified (e.g.,
/aws/cloudtrail/management). -
Go to the Metric filters tab.
-
Click Create metric filter.
-
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"))} -
Click Next.
-
For Filter name, enter something like:
RouteTableChangesFilter -
Under Metric details:
- Metric namespace: e.g.,
Security/NetworkChanges - Metric name: e.g.,
RouteTableChanges - Metric value:
1 - Default value: leave blank or
0.
- Metric namespace: e.g.,
-
Click Next, then Create metric filter.
Step 3: Create a CloudWatch Alarm on That Metric
- 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). - 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.
- Statistic:
- Click Next.
Step 4: Configure Alarm Notifications
- 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.).
- Topic name: e.g.,
- Select an existing topic (e.g.,
- Confirm email subscription(s) if prompted.
- Alarm state trigger:
- (Optional) Add additional actions (e.g., OpsCenter, EC2 action – normally not needed here).
- Click Next.
Step 5: Name and Create the Alarm
- Provide:
- Alarm name:
RouteTableChangesAlarm - Alarm description: e.g.,
Alerts on any changes to VPC route tables using CloudTrail events.
- Alarm name:
- Review all settings.
- Click Create alarm.
Step 6: Test the Alarm (Optional but Recommended)
- Make a test route table change (e.g., add a non-impactful route in a test VPC and remove it).
- Wait for the metric to update (typically a few minutes).
- 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:
CreateRouteTableDeleteRouteTableAssociateRouteTableDisassociateRouteTableReplaceRouteTableAssociationReplaceRouteTableRouteCreateRouteDeleteRouteReplaceRoute
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
boto3installed. - 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,ReplaceRouteCreateRouteTable,DeleteRouteTableAssociateRouteTable,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.