IAM policies still using the primitive Owner, Editor, or Viewer roles
What does ZopNight detect here?
Primitive roles (roles/owner, roles/editor, roles/viewer) predate GCP's granular IAM and grant project-wide power; Editor alone can modify nearly every resource in the project. ZopNight raises high-severity RC-1250 whenever a discovered role row carries the primitive_role=true label, and the finding closes once no binding uses the 3 roles.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-1250 |
| Category | compliance |
| Severity | high |
| Metric | none — pure configuration read |
| Source | iam_primitive_roles.go |
Leftovers from before IAM was granular
Owner, Editor, and Viewer are the three “basic” (historically, primitive) roles GCP shipped before per-service IAM existed. They are not scoped to a service, a resource, or a purpose. They are scoped to everything. Editor grants modify access across nearly every API in the project: a CI service account bound as Editor “because deploys kept failing” can also read every Cloud SQL export, rewrite firewall rules, and delete buckets it has never touched. Owner adds IAM administration on top, which turns any compromise into full, self-escalating project control. Viewer is the mildest of the three and still hands out read access to every dataset, secret-adjacent config, and log line in the project at once.
Why the fix is replacement, not narrowing
Unlike a project-level roles/storage.admin grant, which can be re-scoped to a bucket,
a primitive role has no smaller version of itself. Remediation is always substitution:
work out what the member actually does, then bind the matching predefined roles
(roles/cloudsql.client, roles/run.developer, roles/storage.objectViewer, …) or a
custom role, and remove the primitive binding. The workable sequence for a live system:
- Pull 30–90 days of Cloud Audit Logs for the member to see which APIs it really calls.
- Grant the narrow roles alongside the primitive one.
- Watch for permission-denied errors, then drop Owner/Editor/Viewer last.
Enumerate the bindings in one command
gcloud projects get-iam-policy PROJECT_ID \ --flatten="bindings[].members" \ --filter="bindings.role:(roles/owner OR roles/editor OR roles/viewer)" \ --format="table(bindings.role, bindings.members)"Where ZopNight’s signal comes from
The discoverer walks the project IAM policy and emits one synthetic iam-role row per
unique role it finds, labeling primitive ones with primitive_role=true; the rule fires
purely on that label and abstains for any row without it. Multi-project organizations get
one row (and one finding) per project per role, so a fleet-wide Editor habit surfaces as
a countable list rather than a single vague warning. Severity is high with $0 attached:
nothing about the finding saves money, and no automation edits your policy. Once the last
Owner/Editor/Viewer binding is gone, the next discovery cycle stops emitting the label and
the finding closes on its own.