GCP for DevOps — IAM Policies
IAM Policies
An IAM policy is a collection of bindings that associate one or more members with a role on a resource. Policies are the mechanism through which you grant access in GCP.
Policy Structure
┌──────────────────────────────────────────────┐
│ IAM Policy │
│ │
│ binding 1: │
│ role: roles/storage.objectViewer │
│ members: │
│ - user:alice@example.com │
│ - group:devops@example.com │
│ │
│ binding 2: │
│ role: roles/storage.objectAdmin │
│ members: │
│ - serviceAccount:deploy@proj.iam... │
│ condition: │
│ title: "Business hours only" │
│ expression: request.time.getHours( │
│ "America/New_York") >= 9 && │
│ request.time.getHours( │
│ "America/New_York") <= 17 │
│ │
│ etag: BwXyz123= │
│ version: 3 │
└──────────────────────────────────────────────┘
Viewing IAM Policies
# View the IAM policy for a project
$ gcloud projects get-iam-policy my-project --format=yaml
bindings:
- members:
- user:admin@example.com
role: roles/owner
- members:
- serviceAccount:ci-deployer@my-project.iam.gserviceaccount.com
role: roles/container.developer
- members:
- group:devops-team@example.com
role: roles/viewer
etag: BwXyz123=
version: 1
# View policy for a specific resource (bucket)
$ gcloud storage buckets get-iam-policy gs://my-bucket --format=yaml
# View policy for a Compute Engine instance
$ gcloud compute instances get-iam-policy my-vm --zone=us-central1-a
Adding IAM Bindings
The add-iam-policy-binding command is the safest way to add a binding — it’s additive and won’t remove existing bindings.
# Grant a user a role on a project
$ gcloud projects add-iam-policy-binding my-project \
--member="user:bob@example.com" \
--role="roles/compute.viewer"
Updated IAM policy for project [my-project].
# Grant a group a role on a bucket
$ gcloud storage buckets add-iam-policy-binding gs://my-bucket \
--member="group:data-team@example.com" \
--role="roles/storage.objectViewer"
# Grant a service account a role on a resource
$ gcloud projects add-iam-policy-binding my-project \
--member="serviceAccount:app-sa@my-project.iam.gserviceaccount.com" \
--role="roles/cloudsql.client"
Removing IAM Bindings
# Remove a specific binding
$ gcloud projects remove-iam-policy-binding my-project \
--member="user:bob@example.com" \
--role="roles/compute.viewer"
Updated IAM policy for project [my-project].
IAM Conditions
Conditions add contextual restrictions to a binding. They use Common Expression Language (CEL).
# Grant access only during business hours
$ gcloud projects add-iam-policy-binding my-project \
--member="user:dev@example.com" \
--role="roles/compute.instanceAdmin.v1" \
--condition='expression=request.time.getHours("America/New_York") >= 9 && request.time.getHours("America/New_York") <= 17,title=Business hours only,description=Allow access only during business hours EST'
# Grant access only to resources with a specific prefix
$ gcloud projects add-iam-policy-binding my-project \
--member="serviceAccount:ci@my-project.iam.gserviceaccount.com" \
--role="roles/storage.objectAdmin" \
--condition='expression=resource.name.startsWith("projects/_/buckets/my-bucket/objects/deploy/"),title=Deploy bucket only'
Useful Condition Expressions
| Condition | CEL Expression |
|---|---|
| Time-based | request.time.getHours("UTC") >= 9 && request.time.getHours("UTC") <= 17 |
| Resource name prefix | resource.name.startsWith("projects/_/buckets/my-bucket") |
| Resource type | resource.type == "storage.googleapis.com/Bucket" |
| Temporary access | request.time < timestamp("2026-06-30T00:00:00Z") |
| IP-based (VPC SC) | Use VPC Service Controls instead |
Policy Inheritance
IAM policies are inherited down the resource hierarchy. A binding at the organization level applies to all folders, projects, and resources below it.
Organization (roles/viewer → group:auditors@)
│
├── Folder: Production
│ │ (roles/compute.viewer → inherited)
│ │
│ ├── Project: prod-web
│ │ (roles/compute.viewer → inherited from org AND folder)
│ │
│ └── Project: prod-api
│ (roles/compute.viewer → inherited)
│
└── Folder: Development
│
└── Project: dev-sandbox
(roles/compute.viewer → inherited from org)
Key Rule: You cannot remove an inherited binding at a lower level. To restrict access, use deny policies or organization policies.
Deny Policies
Deny policies explicitly block access, regardless of any allow policies. They take precedence over allow bindings.
# Create a deny policy (using REST API / Terraform — no gcloud support yet)
# Example: Deny deletion of production resources
{
"displayName": "Deny production deletion",
"rules": [
{
"denyRule": {
"deniedPrincipals": ["principalSet://goog/group/developers@example.com"],
"deniedPermissions": [
"compute.instances.delete",
"storage.buckets.delete",
"container.clusters.delete"
],
"exceptionPrincipals": ["principal://goog/subject/admin@example.com"],
"denialCondition": {
"expression": "resource.matchTag('env', 'production')"
}
}
}
]
}
Policy Evaluation Order
Request arrives
│
├── 1. Check DENY policies (org → project → resource)
│ If denied → REJECT ❌
│
├── 2. Check ALLOW policies (resource → project → folder → org)
│ If any binding matches → ALLOW ✅
│
└── 3. No match → DENY (default deny) ❌
Best Practices for IAM Policies
| Practice | Description |
|---|---|
| Use groups, not individual users | group:devops@ instead of user:alice@ |
| Grant at the narrowest scope | Project-level instead of org-level |
| Use conditions for temporary access | Set expiration dates |
| Audit with Policy Analyzer | Identify who has access to what |
| Enable Cloud Audit Logs | Track all IAM changes |
| Use IAM Recommender | Remove unused permissions |
Testing IAM Permissions
# Check if a member has specific permissions
$ gcloud asset analyze-iam-policy \
--organization=123456789 \
--identity="user:alice@example.com" \
--full-resource-name="//compute.googleapis.com/projects/my-project/zones/us-central1-a/instances/my-vm" \
--permissions="compute.instances.delete"
# Simpler: test your own permissions
$ gcloud projects test-iam-permissions my-project \
--permissions="compute.instances.create,compute.instances.delete"
permissions:
- compute.instances.create
Common Mistakes
| Mistake | Why It’s Wrong | Fix |
|---|---|---|
Using set-iam-policy to add a binding |
Overwrites the entire policy | Use add-iam-policy-binding |
| Granting roles to individual users | Hard to audit and maintain | Use Google Groups |
Not using etag with set-iam-policy |
Race conditions in concurrent updates | Always include etag |
| Ignoring inherited permissions | Users may have broader access than expected | Audit with Policy Analyzer |
Quick Reference
| Command | Description |
|---|---|
gcloud projects get-iam-policy PROJECT |
View project IAM policy |
gcloud projects add-iam-policy-binding PROJECT --member=M --role=R |
Add a binding |
gcloud projects remove-iam-policy-binding PROJECT --member=M --role=R |
Remove a binding |
gcloud projects add-iam-policy-binding PROJECT --member=M --role=R --condition=EXPR |
Add conditional binding |
gcloud asset analyze-iam-policy --organization=ORG --identity=USER |
Analyze effective access |
Next → Workload Identity