Control: 5.13 Ensure route table changes are monitored
Description
Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs or an external Security Information and Event Management (SIEM) environment, and establishing corresponding metric filters and alarms. Routing tables are used to route network traffic between subnets and to network gateways.
It is recommended that a metric filter and alarm be established for changes to route tables.
Remediation
If you are using CloudTrail trails and CloudWatch, perform the following steps to set up the metric filter, alarm, SNS topic, and subscription:
- Create a metric filter based on the provided filter pattern that checks for route table changes and uses the <trail-log-group-name>taken from audit step 1:
aws logs put-metric-filter --log-group-name <trail-log-group-name> --filter-pattern '{ ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName = DisassociateRouteTable) }'
Note: You can choose your own metricName and metricNamespace strings. Using the same metricNamespace for all Foundations Benchmark metrics will group them together.
- Create an SNS topic that the alarm will notify:
aws sns create-topic --name <sns-topic-name>
Note: You can execute this command once and then reuse the same topic for all monitoring alarms.
Note: Capture the TopicArn that is displayed when creating the SNS topic in step 2.
- Create an SNS subscription for the topic created in step 2:
aws sns subscribe --topic-arn <sns-topic-arn> --protocol <sns-protocol> --notification-endpoint <sns-subscription-endpoints>
Note: You can execute this command once and then reuse the same subscription for all monitoring alarms
- Create an alarm that is associated with the CloudWatch Logs metric filter created in step 1 and the SNS topic created in step 2:
aws cloudwatch put-metric-alarm --alarm-name <route-table-changesalarm> --metric-name <route-table-changes-metric> --statistic Sum --period 300 --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold --evaluation-periods 1 --namespace 'CISBenchmark' --alarm-actions <sns-topic-arn>
Default Value:
By default, CloudTrail logs route table events (e.g., CreateRoute, DeleteRouteTable), but no CloudWatch metric filters or alarms exist. These changes are captured but not actively monitored unless configured.
Usage
Run the control in your terminal:
powerpipe control run aws_compliance.control.cis_v600_5_13Snapshot and share results via Turbot Pipes:
powerpipe loginpowerpipe control run aws_compliance.control.cis_v600_5_13 --shareSQL
This control uses a named query:
with trails as (  select    trail.account_id,    trail.name as trail_name,    trail.is_logging,    split_part(trail.log_group_arn, ':', 7) as log_group_name  from    aws_cloudtrail_trail as trail,    jsonb_array_elements(trail.event_selectors) as se  where    trail.is_multi_region_trail is true    and trail.is_logging    and se ->> 'ReadWriteType' = 'All'    and trail.log_group_arn is not null  order by    trail_name),alarms as (  select    metric_name,    action_arn as topic_arn  from    aws_cloudwatch_alarm,    jsonb_array_elements_text(aws_cloudwatch_alarm.alarm_actions) as action_arn  order by    metric_name),topic_subscriptions as (  select    subscription_arn,    topic_arn  from    aws_sns_topic_subscription  order by    subscription_arn),metric_filters as (  select    filter.name as filter_name,    filter_pattern,    log_group_name,    metric_transformation_name  from    aws_cloudwatch_log_metric_filter as filter  where    filter.filter_pattern ~ '\s*\$\.eventName\s*=\s*CreateRoute.+\$\.eventName\s*=\s*CreateRouteTable.+\$\.eventName\s*=\s*ReplaceRoute.+\$\.eventName\s*=\s*ReplaceRouteTableAssociation.+\$\.eventName\s*=\s*DeleteRouteTable.+\$\.eventName\s*=\s*DeleteRoute.+\$\.eventName\s*=\s*DisassociateRouteTable'  order by    filter_name),filter_data as (  select    t.account_id,    t.trail_name,    f.filter_name  from    trails as t  join    metric_filters as f on f.log_group_name = t.log_group_name  join    alarms as alarm on alarm.metric_name = f.metric_transformation_name  join    topic_subscriptions as subscription on subscription.topic_arn = alarm.topic_arn)select  distinct 'arn:' || a.partition || ':::' || a.account_id as resource,  case    when f.trail_name is null then 'alarm'    else 'ok'  end as status,  case    when f.trail_name is null then 'No log metric filter and alarm exist for route table changes.'    else filter_name || ' forwards events for route table changes.'  end as reason  , a.account_idfrom  aws_account as a  left join filter_data as f on a.account_id = f.account_id;