This check flags AWS accounts that have no CloudWatch alarm watching for CloudTrail configuration changes. Without it, an attacker can disable or tamper with your audit trail and nobody gets notified. Fix it by creating a metric filter on your CloudTrail log group and wiring an alarm to an SNS topic.
CloudTrail is the system of record for almost everything that happens in an AWS account. Every API call, every console action, every IAM change passes through it. So the first thing a competent attacker does after gaining access is try to blind you by stopping the trail, deleting it, or removing the S3 bucket it writes to. If no alarm fires when that happens, the tampering goes unnoticed until you go looking for logs that no longer exist.
This Lensix check, account_alarm_cloudtrailchanges, maps to CIS AWS Foundations Benchmark 3.5 and verifies that you have a metric filter and CloudWatch alarm configured to detect changes to your CloudTrail configuration.
What this check detects
The check looks for a CloudWatch Logs metric filter and a corresponding alarm that match CloudTrail configuration change events. Specifically, it expects monitoring for these API calls:
CreateTrailUpdateTrailDeleteTrailStartLoggingStopLogging
For the check to pass, three things have to line up:
- A multi-region CloudTrail trail is delivering events to a CloudWatch Logs group.
- A metric filter on that log group matches the events above.
- An alarm is attached to that metric and points at an SNS topic with at least one subscriber.
Note: CloudTrail logs going to S3 alone is not enough for this check. The alarm pipeline runs through CloudWatch Logs, so your trail needs a CloudWatch Logs group configured as a delivery target in addition to S3.
Why it matters
An audit trail you cannot trust is worse than no audit trail, because it gives you false confidence. The whole point of CloudTrail is to answer "what happened and who did it" during an incident. If an attacker can quietly turn it off, that answer disappears.
A realistic attack chain
- An attacker phishes credentials or finds a leaked access key with broad permissions.
- Before doing anything noisy, they call
StopLoggingon your trail, orDeleteTrailoutright. - They now operate without leaving a record, exfiltrate data, create backdoor IAM users, spin up crypto-mining instances, whatever the goal is.
- By the time billing anomalies or a customer report tips you off, the forensic timeline of the initial intrusion is gone.
The alarm in this check is what breaks that chain. A StopLogging event triggers a notification within minutes, giving your on-call team a chance to respond while the attacker is still in the account rather than weeks later.
Warning: Compliance frameworks like CIS, PCI DSS, and SOC 2 all expect tamper detection on audit logs. A missing CloudTrail change alarm is a common audit finding that can hold up a certification or a customer security review.
How to fix it
Remediation has a prerequisite and three steps: make sure CloudTrail delivers to CloudWatch Logs, create an SNS topic for notifications, add the metric filter, then attach the alarm.
Step 0: Confirm CloudTrail delivers to a CloudWatch Logs group
Check whether your trail already has a log group target:
aws cloudtrail describe-trails \
--query 'trailList[].{Name:Name,LogGroup:CloudWatchLogsLogGroupArn}' \
--output table
If LogGroup is empty, create a log group and connect it. CloudTrail also needs an IAM role to write to the group:
# Create the log group
aws logs create-log-group --log-group-name /aws/cloudtrail/management-events
# Then attach it to your trail (role ARN must allow logs:PutLogEvents)
aws cloudtrail update-trail \
--name my-management-trail \
--cloud-watch-logs-log-group-arn arn:aws:logs:us-east-1:111122223333:log-group:/aws/cloudtrail/management-events:* \
--cloud-watch-logs-role-arn arn:aws:iam::111122223333:role/CloudTrail_CloudWatchLogs_Role
Step 1: Create an SNS topic and subscribe to it
aws sns create-topic --name cloudtrail-security-alarms
aws sns subscribe \
--topic-arn arn:aws:sns:us-east-1:111122223333:cloudtrail-security-alarms \
--protocol email \
--notification-endpoint [email protected]
Confirm the subscription from the email you receive. For real on-call routing, point the subscription at PagerDuty or Opsgenie instead of a plain mailbox.
Step 2: Create the metric filter
This filter matches any of the five CloudTrail configuration change events and increments a custom metric:
aws logs put-metric-filter \
--log-group-name /aws/cloudtrail/management-events \
--filter-name CloudTrailConfigChanges \
--filter-pattern '{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }' \
--metric-transformations \
metricName=CloudTrailConfigChangeCount,metricNamespace=CISBenchmark,metricValue=1,defaultValue=0
Step 3: Create the alarm
Danger: Do not test this alarm by running StopLogging on a production trail. Stopping logging creates a real gap in your audit record. Validate the alarm by manually publishing a test datapoint to the metric, or test on a throwaway trail in a sandbox account.
aws cloudwatch put-metric-alarm \
--alarm-name CloudTrailConfigChanges \
--alarm-description "Alarm when CloudTrail configuration is changed (CIS 3.5)" \
--namespace CISBenchmark \
--metric-name CloudTrailConfigChangeCount \
--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:cloudtrail-security-alarms
Once the alarm transitions to OK, the check passes. Any matching event now bumps the metric above the threshold and fires the SNS notification.
Tip: Set this up once in a delegated logging account and centralize CloudTrail from an Organizations trail. You then maintain a single alarm pipeline instead of replicating filters across dozens of accounts.
Do it once with Terraform
Clicking through the console for every account does not scale, and it drifts. Define the whole pipeline as code so every account gets the same alarm:
resource "aws_sns_topic" "security_alarms" {
name = "cloudtrail-security-alarms"
}
resource "aws_sns_topic_subscription" "email" {
topic_arn = aws_sns_topic.security_alarms.arn
protocol = "email"
endpoint = "[email protected]"
}
resource "aws_cloudwatch_log_metric_filter" "cloudtrail_changes" {
name = "CloudTrailConfigChanges"
log_group_name = "/aws/cloudtrail/management-events"
pattern = "{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }"
metric_transformation {
name = "CloudTrailConfigChangeCount"
namespace = "CISBenchmark"
value = "1"
default_value = "0"
}
}
resource "aws_cloudwatch_metric_alarm" "cloudtrail_changes" {
alarm_name = "CloudTrailConfigChanges"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = 1
metric_name = aws_cloudwatch_log_metric_filter.cloudtrail_changes.metric_transformation[0].name
namespace = "CISBenchmark"
period = 300
statistic = "Sum"
threshold = 1
treat_missing_data = "notBreaching"
alarm_actions = [aws_sns_topic.security_alarms.arn]
alarm_description = "CloudTrail configuration change detected (CIS 3.5)"
}
How to prevent it from coming back
Alarms have a habit of disappearing when someone refactors a stack or deletes a "test" log group. A few guardrails keep this check green for good:
- Bake the alarm into a baseline module. Every new account provisioned through your landing zone or Control Tower customizations should inherit the metric filter and alarm automatically.
- Add a policy-as-code gate in CI. Run a Terraform plan scan with a tool like Checkov, tfsec, or OPA Conftest, and fail the pipeline if the CloudTrail change alarm resources are missing or modified.
- Protect the trail itself. Use an Organizations trail with
StopLoggingandDeleteTraildenied by a service control policy, so even a compromised account admin cannot turn it off. - Continuously verify with Lensix. Drift happens between deploys. Let the platform re-run
account_alarm_cloudtrailchangeson a schedule so a removed alarm shows up as a finding within hours, not at your next audit.
Here is an example SCP that blocks anyone in the org from tampering with the central trail:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyCloudTrailTampering",
"Effect": "Deny",
"Action": [
"cloudtrail:StopLogging",
"cloudtrail:DeleteTrail",
"cloudtrail:UpdateTrail"
],
"Resource": "arn:aws:cloudtrail:*:111122223333:trail/org-management-trail"
}
]
}
Note: An SCP and an alarm are complementary, not redundant. The SCP prevents tampering from inside the org, while the alarm catches anything the SCP does not cover, including changes made before the SCP was in place or via paths you did not anticipate.
Best practices
- Cover the whole CIS 3.x family. CloudTrail change alerting is one of about a dozen CIS monitoring controls. Pair it with alarms for root account usage, IAM policy changes, unauthorized API calls, and console sign-in failures so your detection coverage is consistent.
- Route alarms to a real on-call destination. An email that lands in a shared inbox nobody watches is not detection. Send security alarms to a paging tool with an escalation policy.
- Keep the response window short. A five-minute period and a single evaluation period give you fast notification. Avoid long evaluation windows that delay the alert.
- Enable log file validation. Turn on
--enable-log-file-validationon your trail so you can prove logs were not altered, complementing the change alarm. - Centralize, then enforce. One Organizations trail, one logging account, one alarm pipeline, enforced by SCP. This is far easier to keep compliant than per-account trails that drift independently.
CloudTrail change detection is cheap to set up and pays for itself the first time someone, attacker or careless engineer, touches your audit configuration. Get the alarm in place, enforce it as code, and let continuous checks confirm it stays in place.

