AWS account has a CloudWatch Logs metric filter and alarm for VPC changes
CloudTrail events for VPC configuration changes are captured in CloudWatch Logs with a metric filter and an associated alarm. The filter targets actions like CreateVpc, DeleteVpc, ModifyVpcAttribute, and VPC peering operations to surface when network topology is altered.
Risk
Without alerting on VPC changes, unauthorized or accidental edits to routes, peering, or attributes can go unnoticed, exposing private networks and enabling data exfiltration (C), lateral movement and traffic tampering (I), and outages from misrouted or bridged networks (A).
Run this check with Prowler
Run in Prowler Cloudprowler aws --checks cloudwatch_changes_to_vpcs_alarm_configuredRecommendation
Create a CloudWatch Logs metric filter and alarm on CloudTrail for critical VPC change events, and notify responders. Apply least privilege to network changes, require change approvals, and use defense in depth (segmentation, route controls) to prevent and contain unauthorized modifications.
Remediation
Terraform
# Metric filter for VPC changes
resource "aws_cloudwatch_log_metric_filter" "<example_resource_name>" {
name = "<example_resource_name>"
log_group_name = "<example_log_group_name>"
pattern = "{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }" # Critical: matches VPC change events
metric_transformation {
name = "<example_resource_name>" # Critical: metric created by the filter
namespace = "CISBenchmark"
value = "1"
}
}
# Alarm on the VPC changes metric
resource "aws_cloudwatch_metric_alarm" "<example_resource_name>" {
metric_name = "<example_resource_name>" # Critical: alarm monitors the filter's metric
namespace = "CISBenchmark"
statistic = "Sum"
period = 300
evaluation_periods = 1
threshold = 1
comparison_operator = "GreaterThanOrEqualToThreshold"
}NativeIaC
# CloudFormation: Create a metric filter and alarm for VPC changes
Resources:
VPCChangesMetricFilter:
Type: AWS::Logs::MetricFilter
Properties:
LogGroupName: <example_log_group_name>
FilterPattern: '{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }' # Critical: matches VPC change events
MetricTransformations:
- MetricName: vpc_changes_metric
MetricNamespace: CISBenchmark
MetricValue: "1" # Critical: emits a metric on matching events
VPCChangesAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
MetricName: vpc_changes_metric # Critical: alarm monitors the metric above
Namespace: CISBenchmark
Statistic: Sum
Period: 300
EvaluationPeriods: 1
Threshold: 1
ComparisonOperator: GreaterThanOrEqualToThresholdOther
Check Code
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.cloudtrail.cloudtrail_client import (
cloudtrail_client,
)
from prowler.providers.aws.services.cloudwatch.cloudwatch_client import (
cloudwatch_client,
)
from prowler.providers.aws.services.cloudwatch.lib.metric_filters import (
build_metric_filter_pattern,
check_cloudwatch_log_metric_filter,
)
from prowler.providers.aws.services.cloudwatch.logs_client import logs_client
class cloudwatch_changes_to_vpcs_alarm_configured(Check):
"""AWS account has a CloudWatch Logs metric filter and alarm for VPC changes.
Looks for a CloudWatch Logs metric filter matching the expected pattern on a
log group used by a CloudTrail trail, with at least one alarm on its metric.
- PASS: A matching metric filter with an associated alarm exists.
- FAIL: No matching metric filter, or a filter without an alarm, was found.
- MANUAL: CloudTrail trails, log groups, metric filters or alarms could
not be listed in at least one region, so the absence of a filter/alarm
cannot be asserted.
"""
def execute(self) -> list[Check_Report_AWS]:
"""Evaluate the metric filter and alarm coverage for the account.
Returns:
list[Check_Report_AWS]: A single report for the account.
"""
pattern = build_metric_filter_pattern(
event_names=[
"CreateVpc",
"DeleteVpc",
"ModifyVpcAttribute",
"AcceptVpcPeeringConnection",
"CreateVpcPeeringConnection",
"DeleteVpcPeeringConnection",
"RejectVpcPeeringConnection",
"AttachClassicLinkVpc",
"DetachClassicLinkVpc",
"DisableVpcClassicLink",
"EnableVpcClassicLink",
],
)
findings = []
report = check_cloudwatch_log_metric_filter(
pattern,
cloudtrail_client.trails,
logs_client.metric_filters,
cloudwatch_client.metric_alarms,
self.metadata(),
)
inventory_unavailable = (
cloudtrail_client.trails_unavailable
or logs_client.log_groups_unavailable
or logs_client.metric_filters_unavailable
or cloudwatch_client.metric_alarms_unavailable
)
if report is None:
report = Check_Report_AWS(metadata=self.metadata(), resource={})
report.status = "FAIL"
report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated."
report.region = logs_client.region
report.resource_id = logs_client.audited_account
report.resource_arn = logs_client.log_group_arn_template
report.resource_tags = []
# A denied listing in any region means the inventory is incomplete: a
# PASS is still backed by a real filter and alarm, but a FAIL (nothing
# found, or a filter found without its alarm) cannot be trusted.
if report.status == "FAIL" and inventory_unavailable:
report.status = "MANUAL"
report.status_extended = "Cannot evaluate CloudWatch metric filters and alarms: CloudTrail trails, log groups, metric filters or alarms could not be listed in at least one region. Verify that the scanning credentials are allowed to call cloudtrail:DescribeTrails, logs:DescribeLogGroups, logs:DescribeMetricFilters and cloudwatch:DescribeAlarms."
findings.append(report)
return findings