GCP for DevOps — IAM & Security Exercises

4 minute read

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.objectViewer on the bucket (not the project) for tighter scope
  • roles/secretmanager.secretAccessor only allows reading secret values — not creating or managing secrets
  • Never use roles/editor when 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=ALPHA or --stage=BETA while testing before promoting to GA
  • 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.time refers to the time of the API call
  • Multiple conditions are combined with && (AND)
  • The condition title is 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

Updated: