Back to blog
AWSBest PracticesCloud SecurityMonitoring & LoggingOperations & Compliance

No Alarm for AWS Config Changes (CIS 3.9): Why It Matters and How to Fix It

Learn why a missing CloudWatch alarm for AWS Config changes is a serious blind spot, and how to fix CIS 3.9 with CLI, Terraform, and policy-as-code.

TL;DR

This check flags AWS accounts where no CloudWatch alarm watches for changes to AWS Config. Without it, an attacker or careless engineer can disable the very service that records configuration history and you'd never hear about it. Create a metric filter on your CloudTrail log group and wire it to an SNS-backed alarm.

AWS Config is the service that continuously records how your resources are configured and how that configuration changes over time. It's a cornerstone of compliance, drift detection, and incident investigation. So what happens when someone turns it off? If you have no alarm watching for that event, the answer is: nothing. The lights go out quietly.

The No Alarm for AWS Config Changes check (mapped to CIS AWS Foundations Benchmark control 3.9) verifies that you have a CloudWatch metric filter and alarm watching CloudTrail for AWS Config service modifications. This post explains what it catches, why a missing alarm is more dangerous than it sounds, and exactly how to fix it.


What this check detects

The check looks for a CloudWatch Logs metric filter applied to your CloudTrail log group that matches AWS Config control-plane API calls, plus a CloudWatch alarm tied to that filter with an active SNS subscription. Specifically, it expects coverage of events like:

  • StopConfigurationRecorder — stops Config from recording changes
  • DeleteDeliveryChannel — removes where Config sends its data
  • PutDeliveryChannel — changes the delivery destination
  • PutConfigurationRecorder — modifies what the recorder captures

If no metric filter matches these events, or a filter exists but has no alarm, or the alarm has no notification target, the check fails.

Note: This control depends on a multi-region CloudTrail trail that is delivering logs to CloudWatch Logs. If CloudTrail itself isn't sending events to a log group, there is nothing for the metric filter to match against. CIS controls 3.x assume that foundation is already in place (controls 3.1 and 3.2).


Why it matters

AWS Config is often the first thing an attacker disables after gaining access, for the same reason a burglar cuts the camera feed before entering a building. If Config stops recording, your timeline of "what changed and when" goes dark. That blind spot affects three things at once:

  • Detection. Config rules that evaluate compliance stop firing. Open security groups, public S3 buckets, or unencrypted volumes created after the recorder stops won't be flagged.
  • Incident response. When you investigate a breach, the configuration timeline is one of your best forensic tools. A gap in that timeline can hide exactly the steps an attacker took to escalate or persist.
  • Compliance. Frameworks like CIS, PCI DSS, and SOC 2 expect continuous configuration monitoring. A silent gap can turn into a failed audit.

Here is a realistic scenario. An attacker compromises an IAM user with broad permissions. Before making noisy changes, they run StopConfigurationRecorder. With no alarm, that call sits buried in CloudTrail logs nobody is actively reading. The attacker then opens an RDP port, creates a backdoor IAM role, and exfiltrates data over the next several hours. By the time anyone notices, the recorder has been off the whole time, and the change history that would have shown the attack is incomplete.

Warning: A legitimate engineer reorganizing accounts or migrating regions can also trip these events. The alarm isn't only about attackers. It's about knowing when a critical observability control changes for any reason, so you can confirm it was intentional.


How to fix it

The fix has three parts: create an SNS topic with a subscription, add a metric filter to your CloudTrail log group, and attach an alarm. The commands below assume your CloudTrail is already delivering to a CloudWatch Logs group.

Step 1: Create an SNS topic and subscribe to it

aws sns create-topic --name cis-alarms

aws sns subscribe \
  --topic-arn arn:aws:sns:us-east-1:111122223333:cis-alarms \
  --protocol email \
  --notification-endpoint [email protected]

Confirm the subscription from the email AWS sends before moving on, otherwise the alarm has nowhere to deliver.

Step 2: Create the metric filter on your CloudTrail log group

This filter pattern matches all four Config control-plane events the check cares about.

aws logs put-metric-filter \
  --log-group-name "/aws/cloudtrail/management-events" \
  --filter-name "ConfigChanges" \
  --filter-pattern '{ ($.eventSource = config.amazonaws.com) && (($.eventName = StopConfigurationRecorder) || ($.eventName = DeleteDeliveryChannel) || ($.eventName = PutDeliveryChannel) || ($.eventName = PutConfigurationRecorder)) }' \
  --metric-transformations \
      metricName=ConfigChangeCount,metricNamespace=CISBenchmark,metricValue=1

Step 3: Create the alarm and point it at SNS

aws cloudwatch put-metric-alarm \
  --alarm-name "CIS-3.9-ConfigChanges" \
  --alarm-description "Alarm on AWS Config service changes (CIS 3.9)" \
  --metric-name ConfigChangeCount \
  --namespace CISBenchmark \
  --statistic Sum \
  --period 300 \
  --threshold 1 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --evaluation-periods 1 \
  --treat-missing-data notBreaching \
  --alarm-actions arn:aws:sns:us-east-1:111122223333:cis-alarms

Once this is in place, any matching Config API call increments the metric, and CloudWatch fires the alarm within the next evaluation period.

Tip: Reuse one SNS topic across all CIS 3.x alarms instead of creating one per control. You'll have a dozen of these metric filters by the time you finish the benchmark, and a single cis-alarms topic keeps notification routing simple.

Doing it with Terraform

If you manage infrastructure as code, define the filter and alarm declaratively so they can't drift away:

resource "aws_cloudwatch_log_metric_filter" "config_changes" {
  name           = "ConfigChanges"
  log_group_name = "/aws/cloudtrail/management-events"
  pattern        = "{ ($.eventSource = config.amazonaws.com) && (($.eventName = StopConfigurationRecorder) || ($.eventName = DeleteDeliveryChannel) || ($.eventName = PutDeliveryChannel) || ($.eventName = PutConfigurationRecorder)) }"

  metric_transformation {
    name      = "ConfigChangeCount"
    namespace = "CISBenchmark"
    value     = "1"
  }
}

resource "aws_cloudwatch_metric_alarm" "config_changes" {
  alarm_name          = "CIS-3.9-ConfigChanges"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 1
  metric_name         = aws_cloudwatch_log_metric_filter.config_changes.metric_transformation[0].name
  namespace           = "CISBenchmark"
  period              = 300
  statistic           = "Sum"
  threshold           = 1
  treat_missing_data  = "notBreaching"
  alarm_actions       = [aws_sns_topic.cis_alarms.arn]
}

How to prevent it from happening again

Fixing the alarm once is easy. Keeping it in place across dozens of accounts and many engineers is the real challenge. A few approaches keep this control from silently regressing.

Bake it into your account baseline

Whatever provisions new AWS accounts, whether that's Control Tower, an Account Factory pipeline, or a Terraform module, should include the CIS 3.x alarm set by default. New accounts then start compliant instead of being remediated after the fact.

Gate it in CI/CD with policy-as-code

Run a policy check against your Terraform plan before it applies. With Open Policy Agent and conftest, you can fail the build if the alarm resource is missing:

conftest test plan.json --policy policy/cis-alarms.rego

A minimal Rego rule might assert that a metric filter matching config.amazonaws.com exists in the plan, blocking merges that strip it out.

Tip: Pair the deploy-time gate with continuous monitoring. A CI check only sees what's in the pipeline. Someone with console access can still delete an alarm manually, so you need runtime scanning to catch out-of-band drift.

Scan continuously

Lensix runs account_alarm_configchanges on a schedule across every connected AWS account, so a deleted or misconfigured alarm surfaces in hours rather than at your next audit. This closes the gap between "we wrote the Terraform" and "the control is actually live in production right now."


Best practices

  • Cover the whole CIS 3.x set, not just this one. Config changes are one of about a dozen alarms CIS recommends. Root account usage, IAM policy changes, CloudTrail config changes, and security group changes are all in the same family. Deploy them together.
  • Use a multi-region trail. An attacker can disable Config in a region you aren't watching. Your CloudTrail trail and the downstream filters should cover all regions.
  • Protect the alarm pipeline itself. The SNS topic, log group, and CloudTrail trail are all targets. Apply restrictive resource policies and consider an SCP that denies cloudtrail:StopLogging and config:StopConfigurationRecorder outside a break-glass role.
  • Route alarms somewhere people read. An email nobody checks is no better than no alarm. Forward SNS to your on-call system or a monitored Slack channel.
  • Test the alarm. Once a quarter, stop and restart the Config recorder in a non-production account and confirm the notification arrives. Untested alerts have a way of being silently broken.

Danger: Don't test by stopping the recorder in production. If the test fails for an unrelated reason and you forget to restart it, you'll leave production with no configuration recording, which is the exact failure this check exists to prevent. Use a sandbox account.

The Config changes alarm is cheap to set up and easy to forget. Treat it as part of your account foundation, enforce it in code, and verify it keeps working. When the day comes that someone touches your configuration recorder, you'll want to be the first to know, not the last.