Questions
A Cloud Function can't access Cloud Storage. Debug the IAM permission denied error.
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.
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.
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 rulesStep 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.getTerraform 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
| Issue | Cause | Fix |
|---|---|---|
| Permission granted but still denied | Wrong service account | Verify function’s actual SA |
| Works in dev, fails in prod | Different projects/SAs | Use dedicated SA per environment |
| Intermittent failures | IAM propagation delay | Wait 60s after granting roles |
| Access denied on new bucket | Uniform bucket-level access | Use IAM, not ACLs |
| Blocked by VPC-SC | Service perimeter | Add 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 Type | Example | Use Case |
|---|---|---|
| Basic | roles/viewer | Never use in production |
| Predefined | roles/storage.objectViewer | Most common, well-scoped |
| Custom | projects/x/roles/myRole | When predefined is too broad |
Practice Question
Why is granting roles at the bucket level preferred over project level for Cloud Storage access?