GCP for DevOps — IAM & Security Exercises
Practice Exercises — IAM & Security
Test your understanding of GCP IAM concepts with these hands-on exercises.
Exercise 1 — Create a Least-Privilege Service Account
Task: Create a service account named app-reader that can only read objects from a Cloud Storage bucket named gs://my-data-bucket and read secrets from Secret Manager.
Show Answer
# Step 1: Create the service account
$ gcloud iam service-accounts create app-reader \
--display-name="Application Reader" \
--description="Read-only access to GCS and Secret Manager"
# Step 2: Grant storage object viewer on the specific bucket
$ gcloud storage buckets add-iam-policy-binding gs://my-data-bucket \
--member="serviceAccount:app-reader@my-project.iam.gserviceaccount.com" \
--role="roles/storage.objectViewer"
# Step 3: Grant secret accessor at project level
$ gcloud projects add-iam-policy-binding my-project \
--member="serviceAccount:app-reader@my-project.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"
# Step 4: Verify the bindings
$ gcloud projects get-iam-policy my-project \
--flatten="bindings[].members" \
--filter="bindings.members:app-reader@" \
--format="table(bindings.role,bindings.members)"
Key Points:
- Grant
roles/storage.objectVieweron the bucket (not the project) for tighter scope roles/secretmanager.secretAccessoronly allows reading secret values — not creating or managing secrets- Never use
roles/editorwhen specific predefined roles exist
Exercise 2 — Set Up Workload Identity for a GKE Pod
Task: Configure Workload Identity so a pod in the backend namespace using a Kubernetes service account api-ksa can write to Cloud Storage.
Show Answer
# Step 1: Ensure Workload Identity is enabled on the cluster
$ gcloud container clusters update my-cluster \
--zone=us-central1-a \
--workload-pool=my-project.svc.id.goog
# Step 2: Create a Google Service Account
$ gcloud iam service-accounts create api-gsa \
--display-name="API Backend GSA"
# Step 3: Grant GCS write access to the GSA
$ gcloud projects add-iam-policy-binding my-project \
--member="serviceAccount:api-gsa@my-project.iam.gserviceaccount.com" \
--role="roles/storage.objectAdmin"
# Step 4: Create the Kubernetes service account
$ kubectl create namespace backend
$ kubectl create serviceaccount api-ksa --namespace=backend
# Step 5: Allow the KSA to impersonate the GSA
$ gcloud iam service-accounts add-iam-policy-binding \
api-gsa@my-project.iam.gserviceaccount.com \
--role="roles/iam.workloadIdentityUser" \
--member="serviceAccount:my-project.svc.id.goog[backend/api-ksa]"
# Step 6: Annotate the KSA
$ kubectl annotate serviceaccount api-ksa \
--namespace=backend \
iam.gke.io/gcp-service-account=api-gsa@my-project.iam.gserviceaccount.com
# Step 7: Verify
$ kubectl run wi-test \
--image=google/cloud-sdk:slim \
--serviceaccount=api-ksa \
--namespace=backend \
--restart=Never \
--command -- sleep 3600
$ kubectl exec -it wi-test -n backend -- gcloud auth list
# Should show: api-gsa@my-project.iam.gserviceaccount.com
$ kubectl delete pod wi-test -n backend
Key Points:
- The member format in the binding is
serviceAccount:PROJECT.svc.id.goog[NAMESPACE/KSA] - The namespace must match exactly
- Workload Identity Federation is different — it’s for external (non-GCP) workloads
Exercise 3 — Create a Custom Role
Task: Create a custom role called logReader that can only view logs and list log entries. It should not be able to delete or write logs.
Show Answer
# Step 1: Find the available logging permissions
$ gcloud iam roles describe roles/logging.viewer
includedPermissions:
- logging.buckets.get
- logging.buckets.list
- logging.exclusions.get
- logging.exclusions.list
- logging.links.get
- logging.links.list
- logging.logEntries.list
- logging.logMetrics.get
- logging.logMetrics.list
- logging.logServiceIndexes.list
- logging.logServices.list
- logging.logs.list
- logging.queries.get
- logging.queries.list
- logging.sinks.get
- logging.sinks.list
- logging.views.get
- logging.views.list
# Step 2: Create a custom role with only the permissions we need
$ gcloud iam roles create logReader \
--project=my-project \
--title="Log Reader" \
--description="Can only view and list log entries" \
--permissions="logging.logEntries.list,logging.logs.list,logging.logServices.list" \
--stage=GA
# Step 3: Grant the custom role to a user
$ gcloud projects add-iam-policy-binding my-project \
--member="user:dev@example.com" \
--role="projects/my-project/roles/logReader"
# Step 4: Verify the custom role
$ gcloud iam roles describe logReader --project=my-project
includedPermissions:
- logging.logEntries.list
- logging.logServices.list
- logging.logs.list
name: projects/my-project/roles/logReader
stage: GA
title: Log Reader
Key Points:
- Custom roles can be created at the project or organization level
- Use
--stage=ALPHAor--stage=BETAwhile testing before promoting toGA - Custom roles have a limit of 300 per organization or project
- Some permissions cannot be used in custom roles — check the permission support level
Exercise 4 — IAM Conditional Access
Task: Grant a contractor temporary access to view Compute Engine instances, but only until June 30, 2026, and only during business hours (9 AM–5 PM UTC).
Show Answer
# Grant conditional access
$ gcloud projects add-iam-policy-binding my-project \
--member="user:contractor@external.com" \
--role="roles/compute.viewer" \
--condition='expression=request.time < timestamp("2026-06-30T00:00:00Z") && request.time.getHours("UTC") >= 9 && request.time.getHours("UTC") <= 17,title=Temporary contractor access,description=Access expires June 30 2026 and limited to business hours UTC'
Updated IAM policy for project [my-project].
# Verify the condition was applied
$ gcloud projects get-iam-policy my-project \
--flatten="bindings[].members" \
--filter="bindings.members:contractor@external.com" \
--format="yaml(bindings.role,bindings.condition)"
bindings:
condition:
description: Access expires June 30 2026 and limited to business hours UTC
expression: request.time < timestamp("2026-06-30T00:00:00Z") && request.time.getHours("UTC")
>= 9 && request.time.getHours("UTC") <= 17
title: Temporary contractor access
role: roles/compute.viewer
Key Points:
- Conditions use Common Expression Language (CEL)
request.timerefers to the time of the API call- Multiple conditions are combined with
&&(AND) - The condition
titleis required — it identifies the binding - To remove a conditional binding, you must specify the condition title
Exercise 5 — Organization Policy for Security
Task: Write organization policies that (a) restrict resource creation to us-central1 and us-east1 only, and (b) disable service account key creation.
Show Answer
Policy A — Restrict resource locations:
# restrict-locations.yaml
constraint: constraints/gcp.resourceLocations
listPolicy:
allowedValues:
- in:us-central1-locations
- in:us-east1-locations
$ gcloud org-policies set-policy restrict-locations.yaml --organization=123456789
# Verify
$ gcloud org-policies describe gcp.resourceLocations \
--organization=123456789 --effective
constraint: constraints/gcp.resourceLocations
listPolicy:
allowedValues:
- in:us-central1-locations
- in:us-east1-locations
Policy B — Disable SA key creation:
# disable-sa-keys.yaml
constraint: constraints/iam.disableServiceAccountKeyCreation
booleanPolicy:
enforced: true
$ gcloud org-policies set-policy disable-sa-keys.yaml --organization=123456789
# Verify
$ gcloud org-policies describe iam.disableServiceAccountKeyCreation \
--organization=123456789 --effective
booleanPolicy:
enforced: true
constraint: constraints/iam.disableServiceAccountKeyCreation
# Test: attempt to create a key (should fail)
$ gcloud iam service-accounts keys create key.json \
--iam-account=test@my-project.iam.gserviceaccount.com
ERROR: (gcloud.iam.service-accounts.keys.create) FAILED_PRECONDITION:
Key creation is not allowed on this service account.
Organization policy constraint iam.disableServiceAccountKeyCreation is enforced.
Key Points:
- Location values use the prefix
in:followed by the location group - Boolean constraints use
enforced: true/false - Always test with dry-run first before enforcing org-wide
- Existing resources are not retroactively affected — only new operations are blocked
Cleanup
# Remove the test resources created during exercises
$ gcloud iam service-accounts delete app-reader@my-project.iam.gserviceaccount.com --quiet
$ gcloud iam service-accounts delete api-gsa@my-project.iam.gserviceaccount.com --quiet
$ gcloud iam roles delete logReader --project=my-project
$ gcloud projects remove-iam-policy-binding my-project \
--member="user:contractor@external.com" \
--role="roles/compute.viewer" \
--condition='title=Temporary contractor access'
Next Chapter → Compute