DeployU
Interviews / Cloud & DevOps / A Cloud Function can't access Cloud Storage. Debug the IAM permission denied error.

A Cloud Function can't access Cloud Storage. Debug the IAM permission denied error.

debugging IAM & Security Interactive Quiz Code Examples

The Scenario

Your Cloud Function needs to read files from a Cloud Storage bucket, but it’s failing with:

Error: Permission denied on resource project my-project
googleapi: Error 403: my-function@my-project.iam.gserviceaccount.com does not have
storage.objects.get access to the Google Cloud Storage object., forbidden

The function worked in development but fails in production. You’ve already granted the Storage Object Viewer role to the service account.

The Challenge

Debug the IAM permission issue and implement proper service account configuration. Explain the principle of least privilege and GCP’s IAM model.

Wrong Approach

A junior engineer might grant Project Owner role to fix it quickly, use the default Compute Engine service account, or add permissions at the organization level. These approaches violate least privilege, create security risks, and don't solve the root cause.

Right Approach

A senior engineer systematically checks: 1) Which service account the function actually uses, 2) What permissions that account has, 3) Whether permissions are at the right scope (bucket vs object), 4) If there are deny policies or VPC Service Controls blocking access. They implement a dedicated service account with minimal permissions.

Step 1: Identify Which Service Account the Function Uses

# Check the Cloud Function's service account
gcloud functions describe my-function \
  --region=us-central1 \
  --format="value(serviceAccountEmail)"

# Output might show:
# my-project@appspot.gserviceaccount.com  (App Engine default)
# 123456789-compute@developer.gserviceaccount.com  (Compute default)
# my-function@my-project.iam.gserviceaccount.com  (Custom SA)

# Common mistake: You granted permissions to the wrong service account!

Step 2: Check Current IAM Permissions

# Check bucket-level permissions
gcloud storage buckets get-iam-policy gs://my-bucket

# Check if the service account has the expected role
gcloud projects get-iam-policy my-project \
  --flatten="bindings[].members" \
  --filter="bindings.members:my-function@my-project.iam.gserviceaccount.com" \
  --format="table(bindings.role)"

# Test the service account's access directly
gcloud auth activate-service-account \
  --key-file=service-account-key.json

gcloud storage ls gs://my-bucket/

Step 3: Understand IAM Scope

# Project-level role (applies to ALL buckets in project)
gcloud projects add-iam-policy-binding my-project \
  --member="serviceAccount:my-function@my-project.iam.gserviceaccount.com" \
  --role="roles/storage.objectViewer"

# Bucket-level role (applies to specific bucket only - preferred!)
gcloud storage buckets add-iam-policy-binding gs://my-bucket \
  --member="serviceAccount:my-function@my-project.iam.gserviceaccount.com" \
  --role="roles/storage.objectViewer"

# Object-level (IAM conditions for specific prefixes)
gcloud storage buckets add-iam-policy-binding gs://my-bucket \
  --member="serviceAccount:my-function@my-project.iam.gserviceaccount.com" \
  --role="roles/storage.objectViewer" \
  --condition="expression=resource.name.startsWith('projects/_/buckets/my-bucket/objects/allowed-prefix/'),title=AllowedPrefixOnly"

Step 4: Create Dedicated Service Account

# Create a dedicated service account (best practice)
gcloud iam service-accounts create cloud-function-storage \
  --display-name="Cloud Function Storage Access" \
  --description="Service account for Cloud Function to access specific GCS bucket"

# Grant minimal permissions at bucket level
gcloud storage buckets add-iam-policy-binding gs://my-bucket \
  --member="serviceAccount:cloud-function-storage@my-project.iam.gserviceaccount.com" \
  --role="roles/storage.objectViewer"

# Deploy function with the dedicated service account
gcloud functions deploy my-function \
  --region=us-central1 \
  --runtime=nodejs18 \
  --service-account=cloud-function-storage@my-project.iam.gserviceaccount.com \
  --source=.

Step 5: Check for Blocking Policies

# Check VPC Service Controls (common in enterprise)
gcloud access-context-manager perimeters list

# Check Organization Policy constraints
gcloud resource-manager org-policies list --project=my-project

# Check IAM deny policies
gcloud iam policies list --attachment-point=projects/my-project

# If VPC-SC is blocking, you need to add the function to the perimeter
# or configure ingress/egress rules

Step 6: Verify with IAM Policy Troubleshooter

# Use Policy Troubleshooter to test permissions
gcloud policy-troubleshoot iam \
  //storage.googleapis.com/projects/_/buckets/my-bucket/objects/test.txt \
  --principal-email=my-function@my-project.iam.gserviceaccount.com \
  --permission=storage.objects.get

Terraform Implementation

# Create dedicated service account
resource "google_service_account" "function_sa" {
  account_id   = "cloud-function-storage"
  display_name = "Cloud Function Storage Access"
  description  = "Dedicated SA for Cloud Function GCS access"
}

# Grant bucket-level access only
resource "google_storage_bucket_iam_member" "function_access" {
  bucket = google_storage_bucket.data.name
  role   = "roles/storage.objectViewer"
  member = "serviceAccount:${google_service_account.function_sa.email}"
}

# Deploy function with dedicated SA
resource "google_cloudfunctions_function" "my_function" {
  name        = "my-function"
  runtime     = "nodejs18"
  region      = "us-central1"

  service_account_email = google_service_account.function_sa.email

  # Other config...
}

Common IAM Gotchas

IssueCauseFix
Permission granted but still deniedWrong service accountVerify function’s actual SA
Works in dev, fails in prodDifferent projects/SAsUse dedicated SA per environment
Intermittent failuresIAM propagation delayWait 60s after granting roles
Access denied on new bucketUniform bucket-level accessUse IAM, not ACLs
Blocked by VPC-SCService perimeterAdd to perimeter or configure rules

Service Account Best Practices

# 1. One service account per function/service
# 2. Use bucket-level, not project-level permissions
# 3. Enable audit logging for service accounts

gcloud projects add-iam-policy-binding my-project \
  --member="serviceAccount:cloud-function-storage@my-project.iam.gserviceaccount.com" \
  --role="roles/storage.objectViewer" \
  --condition=None

# 4. Set up alerts for service account usage
# 5. Rotate keys regularly (or use Workload Identity)

IAM Hierarchy in GCP

Organization

    ├── Folder (departments)
    │       │
    │       └── Project
    │               │
    │               └── Resource (bucket, instance, etc.)

    └── Permissions flow DOWN (inheritance)
        Restrictions flow UP (deny policies)

Predefined vs Custom Roles

Role TypeExampleUse Case
Basicroles/viewerNever use in production
Predefinedroles/storage.objectViewerMost common, well-scoped
Customprojects/x/roles/myRoleWhen predefined is too broad

Practice Question

Why is granting roles at the bucket level preferred over project level for Cloud Storage access?