Skip to main content

VPC Changes Alarm Should Be Enabled

More Info:

AWS VPCs 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 the exact AWS Console steps to enable a “VPC Changes” alarm in CloudWatch.


Prerequisite: Make sure CloudTrail is sending logs to CloudWatch Logs

  1. In the AWS Console, go to CloudTrail.
  2. In the left menu, select Trails.
  3. Click your trail name (or create one if none exist).
  4. Under CloudWatch Logs, ensure:
    • CloudWatch Logs log group is set (e.g., /aws/cloudtrail/logs).
    • If not set:
      • Click Edit.
      • In CloudWatch Logs, choose Enabled.
      • Select or create a Log group.
      • Choose or create an IAM role as prompted.
      • Save the changes.

Step 1: Go to CloudWatch Logs

  1. Open CloudWatch in the AWS Console.
  2. In the left menu, choose LogsLog groups.
  3. Click the log group used by CloudTrail (e.g., /aws/cloudtrail/logs).

Step 2: Create a Metric Filter for VPC Changes

  1. Inside the log group, go to the Metric filters tab.

  2. Click Create metric filter.

  3. For Filter pattern, use a VPC-change pattern, for example:

    { ($.eventName = "CreateVpc") || ($.eventName = "DeleteVpc") || ($.eventName = "ModifyVpcAttribute") || ($.eventName = "CreateSubnet") || ($.eventName = "DeleteSubnet") || ($.eventName = "CreateRoute") || ($.eventName = "DeleteRoute") || ($.eventName = "ReplaceRoute") || ($.eventName = "CreateRouteTable") || ($.eventName = "DeleteRouteTable") || ($.eventName = "AssociateRouteTable") || ($.eventName = "DisassociateRouteTable") || ($.eventName = "CreateInternetGateway") || ($.eventName = "DeleteInternetGateway") || ($.eventName = "AttachInternetGateway") || ($.eventName = "DetachInternetGateway") || ($.eventName = "CreateNatGateway") || ($.eventName = "DeleteNatGateway") || ($.eventName = "CreateNetworkAcl") || ($.eventName = "DeleteNetworkAcl") || ($.eventName = "CreateNetworkAclEntry") || ($.eventName = "DeleteNetworkAclEntry") || ($.eventName = "ReplaceNetworkAclEntry") || ($.eventName = "ReplaceNetworkAclAssociation") }

    (You can reduce or expand this list depending on exactly which VPC events you want.)

  4. Click Next.

  5. Under Assign metric, configure:

    • Filter name: VPC-Changes-Filter
    • Metric namespace: Security/VPC
    • Metric name: VPCChanges
    • Metric value: 1
  6. Click Next, then Create metric filter.


Step 3: Create a CloudWatch Alarm for This Metric

  1. Still in CloudWatch, in the left menu, select AlarmsAll alarms.
  2. Click Create alarm.
  3. Click Select metric.
  4. Navigate to the metric:
    • Custom namespaces → choose Security/VPC.
    • Select the VPCChanges metric.
  5. Click Select metric.

Step 4: Configure Alarm Conditions

  1. Under Specify metric and conditions:
    • Statistic: Sum
    • Period: 5 minutes (or your preferred window).
    • Threshold type: Static.
    • Whenever VPCChanges is: >= 1.
  2. Click Next.

Step 5: Configure Notification (SNS)

  1. Under Notification:
    • For Alarm state trigger: In alarm.
    • Select an SNS topic:
      • Choose an existing topic (e.g., security-alerts)
        or
      • Click Create new topic, name it (e.g., VPC-Changes-Topic), and enter one or more email addresses.
  2. Click Next.

Step 6: Name and Create the Alarm

  1. Alarm name: VPC-Changes-Alarm.
  2. Alarm description: Alerts when VPC configuration changes are detected via CloudTrail.
  3. Review the settings and click Create alarm.

Once this is in place, any matching VPC-related CloudTrail event will increment the VPCChanges metric and trigger the CloudWatch alarm, sending a notification via SNS.

Using CLI

Below is one way to remediate “VPC Changes Alarm Should Be Enabled” using AWS CLI by:

  1. ensuring CloudTrail is logging to CloudWatch Logs
  2. creating a metric filter for VPC changes
  3. creating a CloudWatch alarm on that metric.

Adjust names/regions as needed.


0. Prerequisites

  • You have a CloudTrail trail sending logs to a CloudWatch Logs log group (say /aws/cloudtrail/logs).
  • You know the region and account ID.

If you don’t yet send CloudTrail to CloudWatch Logs, create or update a trail first.


1. Confirm / Set CloudTrail → CloudWatch Logs

If you already have a trail with CloudWatchLogsLogGroupArn set, you can skip to step 2.

Example: create a new trail that sends to /aws/cloudtrail/logs:

aws logs create-log-group \
--log-group-name /aws/cloudtrail/logs \
--region us-east-1

# Create an IAM role for CloudTrail to write to CloudWatch Logs (one‑time)
aws iam create-role \
--role-name CloudTrail_CloudWatchLogs_Role \
--assume-role-policy-document file://cloudtrail-cw-trust.json

cloudtrail-cw-trust.json (example):

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}

Attach a policy to allow writes to that log group:

aws iam put-role-policy \
--role-name CloudTrail_CloudWatchLogs_Role \
--policy-name CloudTrail_CloudWatchLogs_Policy \
--policy-document file://cloudtrail-cw-policy.json

cloudtrail-cw-policy.json (replace region/account/log-group if needed):

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:PutLogEvents",
"logs:CreateLogStream",
"logs:DescribeLogStreams"
],
"Resource": "arn:aws:logs:us-east-1:123456789012:log-group:/aws/cloudtrail/logs:*"
}
]
}

Now create the trail:

aws cloudtrail create-trail \
--name cloudtrail-with-cw \
--s3-bucket-name my-cloudtrail-bucket \
--cloud-watch-logs-log-group-arn arn:aws:logs:us-east-1:123456789012:log-group:/aws/cloudtrail/logs \
--cloud-watch-logs-role-arn arn:aws:iam::123456789012:role/CloudTrail_CloudWatchLogs_Role \
--is-multi-region-trail

Enable logging:

aws cloudtrail start-logging --name cloudtrail-with-cw

2. Create a CloudWatch Logs Metric Filter for VPC Changes

Pick the relevant VPC events. Common ones include:

  • CreateVpc, DeleteVpc, ModifyVpcAttribute
  • CreateSubnet, DeleteSubnet, ModifySubnetAttribute
  • CreateRouteTable, DeleteRouteTable, AssociateRouteTable, DisassociateRouteTable, ReplaceRouteTableAssociation
  • CreateRoute, DeleteRoute, ReplaceRoute
  • CreateInternetGateway, DeleteInternetGateway, AttachInternetGateway, DetachInternetGateway
  • CreateNatGateway, DeleteNatGateway
  • CreateVpcPeeringConnection, DeleteVpcPeeringConnection, etc.

Sample filter pattern (you can extend):

METRIC_FILTER_NAME="VpcChangeEvents"
LOG_GROUP_NAME="/aws/cloudtrail/logs"
NAMESPACE="Security"
METRIC_NAME="VpcChanges"

aws logs put-metric-filter \
--log-group-name "$LOG_GROUP_NAME" \
--filter-name "$METRIC_FILTER_NAME" \
--filter-pattern '{ ($.eventName = "CreateVpc") || ($.eventName = "DeleteVpc") || ($.eventName = "ModifyVpcAttribute") || ($.eventName = "CreateSubnet") || ($.eventName = "DeleteSubnet") || ($.eventName = "ModifySubnetAttribute") || ($.eventName = "CreateRoute") || ($.eventName = "DeleteRoute") || ($.eventName = "ReplaceRoute") || ($.eventName = "CreateRouteTable") || ($.eventName = "DeleteRouteTable") || ($.eventName = "AssociateRouteTable") || ($.eventName = "DisassociateRouteTable") || ($.eventName = "ReplaceRouteTableAssociation") || ($.eventName = "CreateInternetGateway") || ($.eventName = "DeleteInternetGateway") || ($.eventName = "AttachInternetGateway") || ($.eventName = "DetachInternetGateway") }' \
--metric-transformations \
metricName="$METRIC_NAME",metricNamespace="$NAMESPACE",metricValue=1

This will increment the metric Security/VpcChanges by 1 every time a matching event appears.


3. Create an SNS Topic for Alarm Notifications

TOPIC_NAME="VpcChangesAlarmTopic"

aws sns create-topic --name "$TOPIC_NAME"

Get the ARN:

TOPIC_ARN=$(aws sns list-topics \
--query "Topics[?ends_with(TopicArn,':$TOPIC_NAME')].TopicArn" \
--output text)

Subscribe an email (or other endpoint):

aws sns subscribe \
--topic-arn "$TOPIC_ARN" \
--protocol email \
--notification-endpoint your-email@example.com

Confirm the subscription from your email.


4. Create the CloudWatch Alarm on the Metric

Example: alarm whenever at least 1 VPC change event occurs in a 5‑minute period.

ALARM_NAME="VpcChangesDetected"
NAMESPACE="Security"
METRIC_NAME="VpcChanges"
REGION="us-east-1"

aws cloudwatch put-metric-alarm \
--alarm-name "$ALARM_NAME" \
--alarm-description "Alert on AWS VPC configuration changes" \
--metric-name "$METRIC_NAME" \
--namespace "$NAMESPACE" \
--statistic Sum \
--period 300 \
--threshold 1 \
--comparison-operator GreaterThanOrEqualToThreshold \
--evaluation-periods 1 \
--treat-missing-data notBreaching \
--alarm-actions "$TOPIC_ARN" \
--region "$REGION"

5. Validate

  • Make a test VPC change (e.g., create or modify a subnet).
  • Confirm:
    • CloudTrail logs the event into the log group.
    • The metric Security/VpcChanges increments.
    • The VpcChangesDetected alarm goes into ALARM state.
    • An SNS notification is received.

These steps implement an AWS CloudWatch–based alarm that detects VPC configuration changes and satisfies the “VPC Changes Alarm Should Be Enabled” requirement via AWS CLI.

Using Python

To remediate “VPC Changes Alarm Should Be Enabled” in AWS using Python, you typically need:

  1. A CloudTrail that logs management events.
  2. That CloudTrail delivering logs to a CloudWatch Logs log group.
  3. A CloudWatch Logs metric filter that detects VPC-change API calls.
  4. A CloudWatch alarm on that metric.

Below is a minimal, end‑to‑end example using boto3.


1. Prerequisites

  • Python 3.x
  • boto3 installed:
    pip install boto3
  • AWS credentials configured (via aws configure, environment variables, or instance profile).
  • An SNS topic ARN to receive the alarm notifications (or create one via console/CLI).

Assume:

  • Region: us-east-1
  • CloudTrail log group: /aws/cloudtrail/vpc-changes
  • Metric name: VPCChangeCount
  • Namespace: SecurityMonitoring
  • Alarm name: VPC_Changes_Alarm
  • SNS topic ARN: arn:aws:sns:us-east-1:123456789012:security-alerts

2. Ensure CloudTrail Is Sending to a CloudWatch Logs Log Group

If you already have an org/tenant-wide CloudTrail that sends management events to CloudWatch Logs, you can reuse that log group and skip creating a new trail. Otherwise:

import boto3

region = "us-east-1"
logs_group_name = "/aws/cloudtrail/vpc-changes"

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

# 2.1 Create CloudWatch Logs log group (idempotent)
try:
logs.create_log_group(logGroupName=logs_group_name)
except logs.exceptions.ResourceAlreadyExistsException:
pass

# 2.2 Create IAM role for CloudTrail to write to CloudWatch Logs (if needed)
role_name = "CloudTrail_CloudWatchLogs_Role"
assume_role_policy = {
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "cloudtrail.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}

try:
role = iam.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=json.dumps(assume_role_policy),
Description="Role for CloudTrail to write to CloudWatch Logs"
)["Role"]
except iam.exceptions.EntityAlreadyExistsException:
role = iam.get_role(RoleName=role_name)["Role"]

log_group_arn = f"arn:aws:logs:{region}:{role['Arn'].split(':')[4]}:log-group:{logs_group_name}:*"

policy_doc = {
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": log_group_arn
}]
}

iam.put_role_policy(
RoleName=role_name,
PolicyName="CloudTrail_CloudWatchLogs_Policy",
PolicyDocument=json.dumps(policy_doc)
)

# 2.3 Create or update a CloudTrail trail that logs to this log group
trail_name = "vpc-changes-trail"
account_id = role["Arn"].split(":")[4]

try:
cloudtrail.create_trail(
Name=trail_name,
IsMultiRegionTrail=True,
IsOrganizationTrail=False,
S3BucketName=f"{account_id}-cloudtrail-logs", # must exist or be created
CloudWatchLogsLogGroupArn=f"arn:aws:logs:{region}:{account_id}:log-group:{logs_group_name}",
CloudWatchLogsRoleArn=role["Arn"],
IncludeGlobalServiceEvents=True,
EnableLogFileValidation=True
)
except cloudtrail.exceptions.TrailAlreadyExistsException:
cloudtrail.update_trail(
Name=trail_name,
CloudWatchLogsLogGroupArn=f"arn:aws:logs:{region}:{account_id}:log-group:{logs_group_name}",
CloudWatchLogsRoleArn=role["Arn"]
)

# Start logging (if not already)
cloudtrail.start_logging(Name=trail_name)

If you already have a central CloudTrail and log group, just set logs_group_name to that existing log group and skip trail creation.


3. Create a Metric Filter for VPC Changes

The filter pattern will match CloudTrail events for VPC‑related API calls.

Common VPC‑change events:

  • CreateVpc, DeleteVpc, ModifyVpcAttribute
  • CreateSubnet, DeleteSubnet, ModifySubnetAttribute
  • CreateRouteTable, DeleteRouteTable, AssociateRouteTable, DisassociateRouteTable, ReplaceRouteTableAssociation
  • CreateRoute, DeleteRoute, ReplaceRoute
  • CreateInternetGateway, AttachInternetGateway, DetachInternetGateway, DeleteInternetGateway
  • CreateNatGateway, DeleteNatGateway
  • CreateVpcPeeringConnection, DeleteVpcPeeringConnection, AcceptVpcPeeringConnection, RejectVpcPeeringConnection
  • CreateNetworkAcl, DeleteNetworkAcl, CreateNetworkAclEntry, DeleteNetworkAclEntry, ReplaceNetworkAclEntry
  • etc.

Example pattern (trim or extend as required):

import json
import boto3

region = "us-east-1"
logs_group_name = "/aws/cloudtrail/vpc-changes"

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

metric_filter_name = "VPCChangesFilter"
metric_namespace = "SecurityMonitoring"
metric_name = "VPCChangeCount"

filter_pattern = (
'{ ($.eventSource = "ec2.amazonaws.com") && '
'($.eventName = "CreateVpc" || $.eventName = "DeleteVpc" || '
'$.eventName = "ModifyVpcAttribute" || '
'$.eventName = "CreateSubnet" || $.eventName = "DeleteSubnet" || '
'$.eventName = "ModifySubnetAttribute" || '
'$.eventName = "CreateRouteTable" || $.eventName = "DeleteRouteTable" || '
'$.eventName = "AssociateRouteTable" || $.eventName = "DisassociateRouteTable" || '
'$.eventName = "ReplaceRouteTableAssociation" || '
'$.eventName = "CreateRoute" || $.eventName = "DeleteRoute" || '
'$.eventName = "ReplaceRoute" || '
'$.eventName = "CreateInternetGateway" || $.eventName = "DeleteInternetGateway" || '
'$.eventName = "AttachInternetGateway" || $.eventName = "DetachInternetGateway" || '
'$.eventName = "CreateNatGateway" || $.eventName = "DeleteNatGateway" || '
'$.eventName = "CreateVpcPeeringConnection" || '
'$.eventName = "DeleteVpcPeeringConnection" || '
'$.eventName = "AcceptVpcPeeringConnection" || '
'$.eventName = "RejectVpcPeeringConnection" || '
'$.eventName = "CreateNetworkAcl" || $.eventName = "DeleteNetworkAcl" || '
'$.eventName = "CreateNetworkAclEntry" || $.eventName = "DeleteNetworkAclEntry" || '
'$.eventName = "ReplaceNetworkAclEntry") }'
)

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

4. Create the CloudWatch Alarm on the Metric

Trigger if at least 1 VPC change occurs in a 5‑minute period:

import boto3

region = "us-east-1"
cw = boto3.client("cloudwatch", region_name=region)

metric_namespace = "SecurityMonitoring"
metric_name = "VPCChangeCount"
alarm_name = "VPC_Changes_Alarm"
sns_topic_arn = "arn:aws:sns:us-east-1:123456789012:security-alerts"

cw.put_metric_alarm(
AlarmName=alarm_name,
AlarmDescription="Alarm when any VPC configuration change is detected via CloudTrail",
Namespace=metric_namespace,
MetricName=metric_name,
Statistic="Sum",
Period=300, # 5 minutes
EvaluationPeriods=1,
Threshold=1.0,
ComparisonOperator="GreaterThanOrEqualToThreshold",
ActionsEnabled=True,
AlarmActions=[sns_topic_arn],
TreatMissingData="notBreaching"
)

5. Quick Validation

  1. Make a small VPC change (e.g., create a test subnet).
  2. Wait a few minutes.
  3. Check:
    • CloudWatch Logs: confirm CloudTrail events are arriving.
    • CloudWatch Metrics → SecurityMonitoring/VPCChangeCount shows datapoints.
    • CloudWatch Alarms: VPC_Changes_Alarm moves to ALARM state and sends SNS notification.

This fully remediates the “VPC Changes Alarm Should Be Enabled” requirement using Python.

Using Terraform
# SNS topic to receive VPC change notifications
resource "aws_sns_topic" "vpc_changes_topic" {
name = "VPCChangesTopic"
}

# Email subscription to the SNS topic
resource "aws_sns_topic_subscription" "vpc_changes_email" {
topic_arn = aws_sns_topic.vpc_changes_topic.arn
protocol = "email"
endpoint = "YOUR_EMAIL_ADDRESS" # replace with the email to receive alerts
}

# CloudWatch Logs metric filter on the CloudTrail log group
resource "aws_cloudwatch_log_metric_filter" "vpc_changes_filter" {
name = "VPCChangesFilter"
log_group_name = "CLOUDTRAIL_LOG_GROUP_NAME" # replace with your CloudTrail 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)}"

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

# CloudWatch alarm that triggers on VPC change events
resource "aws_cloudwatch_metric_alarm" "vpc_changes_alarm" {
alarm_name = "VPCChangesAlarm"
namespace = "CloudTrailMetrics"
metric_name = "VPCChanges"
statistic = "Sum"
period = 300
evaluation_periods = 1
threshold = 1
comparison_operator = "GreaterThanOrEqualToThreshold"

alarm_actions = [
aws_sns_topic.vpc_changes_topic.arn
]

depends_on = [
aws_cloudwatch_log_metric_filter.vpc_changes_filter
]
}

This remediation creates new resources (SNS topic, SNS email subscription, log metric filter, and CloudWatch alarm); existing resources are not replaced. You must replace YOUR_EMAIL_ADDRESS and CLOUDTRAIL_LOG_GROUP_NAME with your actual values and confirm the SNS email subscription manually from your inbox.

To verify, terraform plan should show 4 resources to add (aws_sns_topic, aws_sns_topic_subscription, aws_cloudwatch_log_metric_filter, and aws_cloudwatch_metric_alarm) and no changes/destroys for existing resources.

Additional Reading: