SCS Lab 03 - Alarm on root-account console login
Scaffold: 3/5. The log group, the metric filter, the alarm, and the SNS topic are all built and wired. What is missing is the one thing that carries the security decision: the metric-filter pattern that recognises a root-account console sign-in in the CloudTrail JSON. You write that; everything else is done.
The scenario
Someone signs in to the AWS console as the account root. Not an IAM user, not a
role: the root identity itself, the one that can do anything and cannot be
constrained by IAM. That should be a rare, deliberate, break-glass event, and
security should hear about it within seconds. In this account the raw evidence is
already there. CloudTrail records the ConsoleLogin, the record lands in a log
group, and it sits in the logs saying userIdentity.type is Root. Nothing is
watching for it. The login happens, the log line is written, and no one is paged.
The fix is the CIS benchmark’s root-usage monitor: a metric filter that recognises the root sign-in on the CloudTrail JSON, a custom metric that counts it, and an alarm that pages an SNS topic on the first hit. Root usage is a red flag, so there is no burst threshold to clear. One matching event is enough.
The gap
Open src/template.yaml. The metric filter ships with a placeholder pattern:
FilterPattern: '{ $.userIdentity.type = "NoSuchType" }'
It is valid CloudWatch Logs metric-filter syntax, so the stack deploys cleanly,
but it selects on a userIdentity.type value no CloudTrail record ever carries.
RootAccountUsageCount never moves, and a root console login sails straight
past. Your task is to replace it with the pattern that matches a root-account
console sign-in. On the CloudTrail JSON, that is the record where:
$.userIdentity.typeequals"Root"(the account root, not an IAM user or role),$.userIdentity.invokedBydoes not exist (a human at the console, not an AWS service acting on root’s behalf), and$.eventTypeis not"AwsServiceEvent"(exclude events AWS itself raises).
A JSON metric-filter selector is wrapped in { } and joins its terms with &&.
Leave the metric transformations alone; only the FilterPattern needs work.
Run it
# Defaults: stack scs-lab-03, region ap-southeast-2.
./scripts/deploy.sh # deploys src/template.yaml
./scripts/test.sh # writes CloudTrail-shaped events and watches the alarm
./scripts/teardown.sh # deletes everything
test.sh never launches compute, so it stays cheap. It writes CloudTrail-shaped
log events with put-log-events and polls describe-alarms. First it writes an
ordinary IAM-user ConsoleLogin and confirms the alarm ignores it, because an
everyday login is not a page; then it writes a root ConsoleLogin and confirms
the alarm fires. Metric filters and alarms take a minute or three to react, so
the test polls patiently and prints each reading.
What success looks like
./scripts/test.sh ends with:
t+90s alarm=ALARM
PASS: the ordinary login was ignored and the root console login paged.
Note that the IAM-user event alone would never trip this alarm -- only
the root sign-in does, which is exactly the red flag you want to catch.
If it fails
- The alarm fired on the ordinary IAM-user login. The pattern is matching too
broadly. It should select
$.userIdentity.type = "Root", not any login. - The alarm never fired, even after the root login. The
FilterPatternis not matching the root sign-in, soRootAccountUsageCountnever moves. Check that the pattern is wrapped in{ }, that the field paths start with$., and thatNOT EXISTSis applied to$.userIdentity.invokedBy. - The alarm sits in
INSUFFICIENT_DATA. The metric filter is producing no data. Confirm the metric transformation keeps itsDefaultValue: 0, which reports zero through the quiet periods so the alarm settles to OK.
Reveal the solution
Deploy the complete reference template without editing anything:
SRC=solution ./scripts/deploy.sh && ./scripts/test.sh
What you just learned
- CloudWatch Logs metric filters can select on structured JSON, not just plain
text. A pattern wrapped in
{ }addresses fields by path ($.userIdentity.type), compares with=and!=, tests presence withNOT EXISTS, and joins terms with&&. That is exactly what turns a CloudTrail log group into an alarm source. - This is one alarm from the CIS AWS Foundations Benchmark’s monitoring set, which pairs a metric filter with an alarm for events like root usage, console logins without MFA, IAM policy changes, and unauthorised API calls. The shape is always the same: filter the trail, count the metric, alarm on the count.
- Root usage is a red flag on its own. Unlike a noisy application metric that needs a burst threshold, one root console login is enough to page, because the root account should be locked away behind hardware MFA and used almost never.
Next
The rest of the hands-on lab tracks are listed in
labs/README-scs.md.