GCP Custom Roles: When and How to Use Them
Introduction
Google Cloud’s IAM system offers three role types—primitive, predefined, and custom—to balance convenience with security. While predefined roles cover 80% of use cases, custom roles give you surgical control over permissions. In this post, we’ll explore:
- The key differences between role types
- When custom roles are worth the overhead
- How to implement them securely at scale
The Three Role Types in GCP IAM
1. Primitive Roles (Avoid These)
The legacy “big three” roles that grant overly broad access:
roles/viewer(Read-only)roles/editor(Edit rights)roles/owner(Full control + billing)
❌ Never use these in production—they violate least-privilege principles.
📚 GCP Docs: Primitive Roles
2. Predefined Roles (Use These First)
Google-curated roles with focused permissions:
roles/storage.objectViewer(Read GCS objects)roles/cloudfunctions.invoker(Trigger Functions)roles/bigquery.dataViewer(Query BQ datasets)
✅ Best for common patterns—but may include unused permissions.
📚 GCP Predefined Roles List
3. Custom Roles (When You Need Precision)
Handcrafted roles with exact permissions:
resource "google_project_iam_custom_role" "log_analyzer" {
role_id = "logAnalyzer"
title = "Log Analysis Only"
description = "Read access to logs but no resource modifications"
permissions = [
"logging.logEntries.list",
"logging.logs.list",
"resourcemanager.projects.get"
]
stage = "GA" # Options: ALPHA, BETA, GA, DEPRECATED
}
🔍 Perfect for compliance—zero permission waste.
📚 Creating Custom Roles
When to Create Custom Roles
| Scenario | Predefined Role Problem | Custom Role Solution |
|---|---|---|
| CI/CD Pipelines | roles/cloudbuild.builds.editor grants unnecessary delete access | Create role with only create and get permissions |
| Auditors | roles/viewer gives access to unrelated services | Limit to specific APIs like bigquery.tables.get |
| Cross-Project Access | Different projects need identical minimal access | Reuse the same custom role definition |
Implementing Custom Roles: Terraform Best Practices
1. Centralize Role Definitions
Store roles in a shared module:
# modules/iam_custom_roles/main.tf
variable "project_id" {}
variable "custom_roles" {
type = map(list(string))
default = {
"gcsAuditor" = [
"storage.buckets.get",
"storage.objects.list"
]
}
}
resource "google_project_iam_custom_role" "roles" {
for_each = var.custom_roles
project = var.project_id
role_id = each.key
title = title(replace(each.key, "_", " "))
permissions = each.value
}
2. Enforce Naming Conventions
locals {
# Enforce <resourceType>.<action> pattern
valid_permissions = [
for p in var.permissions : p
if can(regex("^[a-z]+\\.[a-zA-Z]+$", p))
]
}
3. Automated Testing
Use Google’s IAM Policy Simulator to verify roles before deployment.
Security Best Practices
-
Permission Auditing
Regularly review roles with:gcloud iam roles describe --project YOUR_PROJECT roles/YOUR_CUSTOM_ROLE -
Least Privilege Enforcement
- Start with 0 permissions
- Add only what’s proven necessary
-
Group-Based Assignment
Bind roles to Google Groups, not individuals:resource "google_project_iam_binding" "log_analysts" { project = "your-project" role = google_project_iam_custom_role.log_analyzer.id members = ["group:log-analysis-team@your-domain.com"] } -
Lifecycle Management
- Deprecate unused roles (
stage = "DEPRECATED") - Version roles via description fields
- Deprecate unused roles (
When Not to Use Custom Roles
- Temporary Access: Predefined roles are faster for one-off needs
- Rapid Prototyping: Wait until workflows stabilize
- Google-Managed Services: Some services (like GKE) require predefined roles
Conclusion
Custom roles are your scalpel for GCP IAM—powerful but requiring careful handling. By:
- Starting with predefined roles
- Creating custom roles only for proven needs
- Automating their management
You’ll achieve the perfect balance of security and maintainability.
Related Posts