Your weekly dose of actionable cloud wisdom to start the week right
The Problem
Your GCP project has dozens of users with “Editor” or “Owner” roles because it was easier than figuring out granular permissions. Now someone accidentally deleted a production database, your security team is asking uncomfortable questions, and you’re realising that “we’ll fix permissions later” has become a security nightmare that could cost you your job.
The Solution
Implement proper GCP IAM using the principle of least privilege with practical, real-world role assignments that actually work in production environments. Most IAM problems stem from taking shortcuts early on, but it’s never too late to secure your cloud properly.
Essential IAM Security Practices:
1. Replace Basic Roles with Predefined Roles
# NEVER do this (too permissive)
gcloud projects add-iam-policy-binding my-project \
--member="user:developer@company.com" \
--role="roles/editor"
# DO this instead (principle of least privilege)
gcloud projects add-iam-policy-binding my-project \
--member="user:developer@company.com" \
--role="roles/compute.instanceAdmin.v1"
gcloud projects add-iam-policy-binding my-project \
--member="user:developer@company.com" \
--role="roles/storage.objectViewer"
2. Create Custom Roles for Specific Needs
# custom-developer-role.yaml
title: "Custom Developer Role"
description: "Limited permissions for application developers"
stage: "GA"
includedPermissions:
# Compute permissions
- compute.instances.get
- compute.instances.list
- compute.instances.start
- compute.instances.stop
# Storage permissions (read-only)
- storage.objects.get
- storage.objects.list
- storage.buckets.get
# Monitoring (essential for debugging)
- monitoring.timeSeries.list
- logging.entries.list
# Service accounts (limited)
- iam.serviceAccounts.get
- iam.serviceAccounts.list
# Create the custom role
gcloud iam roles create customDeveloperRole \
--project=my-project \
--file=custom-developer-role.yaml
# Assign to users
gcloud projects add-iam-policy-binding my-project \
--member="user:dev-team@company.com" \
--role="projects/my-project/roles/customDeveloperRole"
3. Service Account Security Best Practices
# Create service accounts with descriptive names
gcloud iam service-accounts create api-backend-service \
--display-name="API Backend Service Account" \
--description="Service account for backend API with minimal permissions"
# Grant only necessary permissions
gcloud projects add-iam-policy-binding my-project \
--member="serviceAccount:api-backend-service@my-project.iam.gserviceaccount.com" \
--role="roles/datastore.user"
gcloud projects add-iam-policy-binding my-project \
--member="serviceAccount:api-backend-service@my-project.iam.gserviceaccount.com" \
--role="roles/storage.objectViewer"
# Enable service account key rotation
gcloud iam service-accounts keys create key.json \
--iam-account=api-backend-service@my-project.iam.gserviceaccount.com \
--key-file-type=json
4. Implement IAM Conditions for Time-Based Access
# Grant temporary access with conditions
gcloud projects add-iam-policy-binding my-project \
--member="user:contractor@external.com" \
--role="roles/compute.viewer" \
--condition='expression=request.time < timestamp("2025-12-31T23:59:59Z"),title=Temporary Access,description=Access expires end of year'
# Location-based conditions
gcloud projects add-iam-policy-binding my-project \
--member="user:admin@company.com" \
--role="roles/compute.admin" \
--condition='expression=origin.ip in ["203.0.113.0/24", "198.51.100.0/24"],title=Office IP Only,description=Admin access only from office networks'
5. IAM Security Monitoring and Auditing
# Enable Cloud Audit Logs (essential for security)
gcloud logging sinks create iam-audit-sink \
bigquery.googleapis.com/projects/my-project/datasets/security_audit \
--log-filter='protoPayload.serviceName="iam.googleapis.com"'
# Query suspicious IAM activities
bq query --use_legacy_sql=false '
SELECT
timestamp,
protoPayload.authenticationInfo.principalEmail,
protoPayload.methodName,
protoPayload.resourceName
FROM `my-project.security_audit.cloudaudit_googleapis_com_activity_*`
WHERE protoPayload.serviceName = "iam.googleapis.com"
AND protoPayload.methodName LIKE "%setIamPolicy%"
AND timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
ORDER BY timestamp DESC
'
Role Assignment Templates by Job Function:
6. Developer Role Template
#!/bin/bash
# Developer IAM setup script
PROJECT_ID="my-project"
DEVELOPER_EMAIL="developer@company.com"
# Core development permissions
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="user:$DEVELOPER_EMAIL" \
--role="roles/compute.instanceAdmin.v1"
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="user:$DEVELOPER_EMAIL" \
--role="roles/storage.objectViewer"
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="user:$DEVELOPER_EMAIL" \
--role="roles/cloudsql.client"
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="user:$DEVELOPER_EMAIL" \
--role="roles/logging.viewer"
echo "Developer permissions configured for $DEVELOPER_EMAIL"
7. Production Support Role Template
#!/bin/bash
# Production support IAM setup script
PROJECT_ID="my-project"
SUPPORT_EMAIL="support@company.com"
# Read-only access with limited operational capabilities
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="user:$SUPPORT_EMAIL" \
--role="roles/viewer"
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="user:$SUPPORT_EMAIL" \
--role="roles/logging.viewer"
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="user:$SUPPORT_EMAIL" \
--role="roles/monitoring.viewer"
# Limited operational permissions
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="user:$SUPPORT_EMAIL" \
--role="roles/compute.instanceAdmin.v1"
echo "Production support permissions configured for $SUPPORT_EMAIL"
IAM Security Automation
# Python script for regular IAM auditing
from google.cloud import asset_v1
from google.cloud import iam_v1
import json
def audit_excessive_permissions(project_id):
"""Identify users with overly broad permissions"""
client = asset_v1.AssetServiceClient()
parent = f"projects/{project_id}"
# Get all IAM policies
response = client.search_all_iam_policies(request={"scope": parent})
risky_roles = [
"roles/owner",
"roles/editor",
"roles/iam.securityAdmin",
"roles/resourcemanager.projectIamAdmin"
]
findings = []
for policy in response:
for binding in policy.policy.bindings:
if binding.role in risky_roles:
for member in binding.members:
findings.append({
"resource": policy.resource,
"member": member,
"role": binding.role,
"risk_level": "HIGH"
})
return findings
# Run the audit
project_id = "my-project"
risky_assignments = audit_excessive_permissions(project_id)
for finding in risky_assignments:
print(f"⚠️ {finding['member']} has {finding['role']} on {finding['resource']}")
8. Service Account Key Rotation Automation
#!/bin/bash
# Automated service account key rotation
SERVICE_ACCOUNT="api-backend-service@my-project.iam.gserviceaccount.com"
KEY_FILE="new-service-account-key.json"
# Create new key
gcloud iam service-accounts keys create $KEY_FILE \
--iam-account=$SERVICE_ACCOUNT
# Get old key IDs (keep only newest 2 keys)
OLD_KEYS=$(gcloud iam service-accounts keys list \
--iam-account=$SERVICE_ACCOUNT \
--format="value(name)" \
--sort-by="~validAfterTime" | tail -n +3)
# Delete old keys
for key in $OLD_KEYS; do
echo "Deleting old key: $key"
gcloud iam service-accounts keys delete $key \
--iam-account=$SERVICE_ACCOUNT \
--quiet
done
echo "Key rotation completed. Update your applications with the new key file: $KEY_FILE"
Why It Matters
- Security Posture: Principle of least privilege reduces attack surface by 80%+
- Compliance: Auditors love proper IAM documentation and access controls
- Incident Response: Granular permissions make breach containment much easier
- Operational Excellence: Clear role separation reduces human error risk
Try This Week
- Audit current permissions – List all users with Editor/Owner roles
- Create one custom role – Replace a basic role with granular permissions
- Enable audit logging – Set up IAM activity monitoring
- Implement service account rotation – Schedule regular key updates
Quick IAM Audit Script
#!/bin/bash
# Quick IAM security audit
PROJECT_ID="my-project"
echo "=== IAM Security Audit for $PROJECT_ID ==="
echo
echo "Users with broad permissions:"
gcloud projects get-iam-policy $PROJECT_ID \
--flatten="bindings[].members" \
--format="table(bindings.role,bindings.members)" \
--filter="bindings.role:(roles/owner OR roles/editor OR roles/iam.securityAdmin)"
echo
echo "Service accounts with keys:"
gcloud iam service-accounts list --format="value(email)" | while read SA; do
KEY_COUNT=$(gcloud iam service-accounts keys list --iam-account=$SA --format="value(name)" | wc -l)
if [ $KEY_COUNT -gt 2 ]; then
echo "⚠️ $SA has $KEY_COUNT keys (consider rotation)"
fi
done
echo
echo "Custom roles in project:"
gcloud iam roles list --project=$PROJECT_ID --format="value(name,title)"
Common IAM Mistakes to Avoid
- Using basic roles: Owner/Editor/Viewer are too broad for production
- Service account key sprawl: Keys stored in source code or long-lived keys
- No IAM conditions: Missing time, location, or resource-based restrictions
- Lack of monitoring: No alerts on privilege escalation or suspicious access
- Emergency access planning: No break-glass procedures for critical incidents
Advanced IAM Patterns
- Workload Identity: Avoid service account keys entirely for GKE workloads
- IAM recommender: Use GCP’s AI to suggest permission rightsizing
- Policy troubleshooter: Debug permission issues systematically
- Organization policies: Implement guardrails at the organizational level
Pro Tip: Use gcloud auth application-default login for local development instead of downloading service account keys. It’s more secure and prevents accidental key exposure in source control.
Implementing IAM at your organisation and hitting roadblocks? Drop me a line – I’ve helped teams secure their GCP environments without breaking existing workflows!








