Skip to main content
compliance · gcp

Service account keys older than 90 days

resource types
1
rule IDs covered
1
severity
medium

What does ZopNight detect here?

Service account keys older than 90 days trip ZopNight's RC-132, a threshold aligned with CIS GCP and NIST rotation guidance. Age derives from the oldest USER_MANAGED key's validAfterTime, read via iam.serviceAccountKeys.list; the rule abstains when key_age_days is absent or unparseable rather than assuming staleness.

Signal and threshold

How ZopNight evaluates Service account keys older than 90 days.
Field Value
Rule IDsRC-132
Categorycompliance
Severitymedium
Metricnone — pure configuration read
Sourceiam_key_rotation.go

What key age actually measures

Rotation is not a ritual. Key age is a proxy for exposure window. A user-managed key is a file, and files travel: developer laptops, CI logs, tarballs, chat threads. If the key was copied somewhere unfortunate at any point in its life, the leak stays exploitable until the key object is deleted. A key created 400 days ago has had 400 days of opportunities to escape, and rotating it is the only act that retroactively cuts off every copy at once. The 90-day line the rule draws (keyRotationMaxAgeDays = 90) matches the window common security benchmarks (CIS GCP among them) converge on: long enough to be operationally sane, short enough that a silent leak does not grant a year of access.

The oldest key sets the verdict

Accounts often accumulate keys: one per environment, one from an abandoned migration, one nobody remembers. The discoverer computes key_age_days from the validAfterTime of the oldest USER_MANAGED key on the account, so a fresh key added yesterday does not mask the 3-year-old one still valid beside it. That is the right bias for a security signal: the attacker gets to pick which key to use, so the finding tracks the worst one.

Reproduce the age math

Terminal window
gcloud iam service-accounts keys list \
--iam-account=SA_NAME@PROJECT_ID.iam.gserviceaccount.com \
--managed-by=user --format="table(name.basename(), validAfterTime)"

Any validAfterTime more than 90 days in the past reproduces the finding.

Rotating without an outage

The zero-downtime sequence is create-migrate-delete, in that order: mint a new key, update every consumer (secret manager entries, CI variables, deployed configs), confirm from audit logs that authentication has moved to the new key ID, then delete the old key. Deleting first is how rotation becomes an incident. Teams that do this more than twice usually automate the cadence, or skip the treadmill entirely by moving the workload to Workload Identity Federation, after which the companion rule RC-131 stops firing too, because no key file exists to age.

When the rule says nothing

Evaluation is fail-closed at every step: rows without the serviceAccount: UID prefix are ignored (human users are never scored), and if key_age_days is missing or does not parse as an integer the rule abstains instead of guessing. An account with zero user-managed keys therefore produces no rotation finding at all. The only permission behind the signal is the read iam.serviceAccountKeys.list, carried by roles/iam.securityReviewer; rotation itself is always performed by you, not by ZopNight.

See it fire on your bill.

Connect an account read-only. The first findings land in minutes.

417 rule families across 353 resource types on 22 platforms. Every threshold, metric, and IAM action is documented on these pages before you grant anything.

417 rule families documented
353 resource types covered
read-only default access level
Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console· Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console·