Predefined roles granted on a whole GCP project that could be granted on single resources
What does ZopNight detect here?
A predefined role such as `roles/storage.admin` or `roles/cloudsql.admin` granted on a GCP project is inherited by every matching resource in that project, not just the one bucket or instance the member needs. ZopNight flags project-level bindings of roles that have a resource-level equivalent, and skips basic, billing and project-administration roles that cannot be scoped down.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-1251 |
| Category | compliance |
| Severity | medium |
| Metric | none — pure configuration read |
| Threshold | role bound in the project policy that has a resource-level equivalent |
| Source | ZopNight |
| Permissions used | resourcemanager.projects.getIamPolicy |
Why project scope grants more than it says
IAM policies are inherited down the hierarchy. The
resource hierarchy access control page
states that roles granted at the project level are inherited by resources within that project, and
that a resource’s effective policy is the union of its own policy and what it inherits.
So roles/storage.admin granted on the project is admin on every bucket there today and every
bucket created tomorrow.
Many services support roles on individual resources: Google’s examples include Pub/Sub topics and Compute Engine instances, alongside Cloud Storage and BigQuery. When a member needs one bucket, a binding on that bucket gives them exactly that, and a new bucket holding payroll exports does not silently become theirs.
Reviewing project-level bindings
gcloud projects get-iam-policy PROJECT_ID \ --flatten=bindings \ --format="table(bindings.role, bindings.members.list())"For each predefined role, ask whether its members need it on every resource of that type or only on a few.
How the rule chooses which roles to report
ZopNight reads the project IAM policy and represents each distinct role in it as a record for that project. Every such record is by definition a project-level binding. The rule then skips roles that have nothing smaller to be scoped to, and fires on the rest:
roles/owner,roles/editor,roles/viewer, which are reported with the right fix (replace, not re-scope) by GCP IAM Primitive Role in Use.- Billing-account roles:
roles/billing.admin,roles/billing.user,roles/billing.viewer,roles/billing.costsManager. - Project and organization administration roles:
roles/resourcemanager.projectIamAdmin,roles/resourcemanager.organizationAdmin,roles/orgpolicy.policyAdmin,roles/iam.organizationRoleAdmin. - Service Usage roles:
roles/serviceusage.serviceUsageAdminandroles/serviceusage.serviceUsageConsumer.
Where it deliberately draws the line
Any role outside that list is reported, since it plausibly has a resource-level equivalent. The rule does not check whether members actually use the access, or whether they need it on every resource, which is why the false-positive note exists. Bindings on folders or the organization are out of scope. One finding is produced per role per project, however many members hold it.
Least privilege, not a saving
The finding carries no saving. The risk is reach: a compromised or careless member with a project-wide service admin role can read, change or delete every resource of that kind in the project.
Moving a binding down to the resource
- Identify which resources each member of the role actually works with.
- Grant the role on those resources, for example
gcloud storage buckets add-iam-policy-binding gs://BUCKET --member=MEMBER --role=roles/storage.admin. - Remove the project binding with
gcloud projects remove-iam-policy-binding PROJECT_ID --member=MEMBER --role=ROLE. - Check the applications still work. The finding closes on the next inventory once the project-level binding is gone.