Service accounts granted Owner, Editor, or *.admin roles
What does ZopNight detect here?
Service accounts holding roles/owner, roles/editor, or a predefined *.admin role turn 1 leaked credential into full control of the affected scope. ZopNight's high-severity RC-130 fires on service-account principals whose discovered metadata records has_admin_role=true, telling robots apart from humans by the serviceAccount: prefix on the principal UID.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-130 |
| Category | compliance |
| Severity | high |
| Metric | none — pure configuration read |
| Source | iam_admin_sa.go |
Where it applies
Machine identities with human-sized power
A service account is credentials plus permissions and nothing else: no MFA prompt, no
login anomaly detection, no person who notices their own account acting strangely. When one
holds an admin-level role, whoever obtains its credential inherits everything the role
grants, silently and at machine speed. The scope depends on the role: basic roles
(roles/owner, roles/editor) mean project-wide control, while a predefined *.admin
role such as roles/storage.admin means full control of one service, which in that example is every bucket and
every object in the project. Attackers who land on a VM or in a container
read the metadata server first precisely because the attached service account so often
turns out to be over-privileged.
How the detection tells robots from people
The discoverer’s IAM listing emits both humans and service accounts as iam-user rows, so
the rule needs a discriminator: service-account principals keep their serviceAccount:
member prefix on the row UID, and only rows with that prefix are evaluated, so a human with
Owner never triggers this rule. The admin verdict itself is the producer’s
has_admin_role=true flag, read from metadata with a fallback to the legacy tags sink for
rows written before the migration. If neither sink says true, the rule abstains; absence of
evidence is never treated as a finding.
List the grants worth worrying about
gcloud projects get-iam-policy PROJECT_ID \ --flatten="bindings[].members" \ --filter="bindings.members ~ ^serviceAccount: AND bindings.role ~ (roles/owner|roles/editor|admin)" \ --format="table(bindings.members, bindings.role)"Each line is a machine identity one credential leak away from admin control of its scope.
Right-sizing a workload’s role
The remediation question is never “which admin role is safer” but “which 3 or 4 permissions
does this workload actually use”. Cloud Audit Logs answer it empirically: filter for the
service account’s calls over a representative window, map the methods to the narrowest
predefined roles, bind those, then remove the admin binding and watch for denials. Uploader
workloads typically land on roles/storage.objectAdmin scoped to one bucket rather than
project-wide roles/storage.admin; deployers land on service-specific developer roles.
ZopNight only surfaces the finding. Rebinding is yours to do, and the finding closes after
a discovery cycle no longer sees an admin role on the account.