GCP IAM is designed to grant only the necessary permissions to principals (users, service accounts, groups) for specific resources. Applying least-privilege policies is crucial for security, preventing accidental or malicious actions by limiting what a principal can do.
Let’s look at a common scenario: a developer needs to deploy an application to a GKE cluster in project-a and access a Cloud SQL instance in project-b.
Current State (Overly Permissive):
Imagine the developer’s service account, dev-sa@project-a.iam.gserviceaccount.com, has the Editor role on both project-a and project-b. This grants them broad administrative access, far beyond what’s needed.
Desired State (Least Privilege):
- GKE Deployment: The service account needs permissions to create and manage Deployments, Pods, Services, and other Kubernetes resources within the GKE cluster in
project-a. - Cloud SQL Access: The service account needs permissions to connect to and query the Cloud SQL instance in
project-b.
Applying Least-Privilege IAM Policies:
Instead of broad roles like Editor, we’ll use more granular roles.
Step 1: GKE Permissions for Deployment
For deploying to GKE, the service account needs permissions at the Kubernetes API level, not just GCP IAM roles on the GKE cluster itself. GCP IAM roles on the GKE cluster control GCP-level operations (like creating/deleting clusters), while Kubernetes RBAC controls within-cluster operations.
-
GCP IAM Role: The service account needs permissions to authenticate to the GKE cluster. The
roles/container.developerrole is a good starting point. This role allows a principal to interact with GKE resources at a developer level.- Diagnosis: Check existing roles on the service account or project.
gcloud iam service-accounts get-iam-policy dev-sa@project-a.iam.gserviceaccount.com --project=project-a gcloud projects get-iam-policy project-a --flatten="bindings[].members" --filter="bindings.members:serviceAccount:dev-sa@project-a.iam.gserviceaccount.com" - Fix: Grant
roles/container.developerto the service account onproject-a.gcloud projects add-iam-policy-binding project-a \ --member="serviceAccount:dev-sa@project-a.iam.gserviceaccount.com" \ --role="roles/container.developer" - Why it works: This GCP IAM role allows the service account to obtain credentials that can then be used to authenticate to the GKE cluster’s Kubernetes API server.
- Diagnosis: Check existing roles on the service account or project.
-
Kubernetes RBAC: Within the cluster, the service account needs specific permissions to create Deployments, Pods, Services, etc. This is managed by Kubernetes RBAC, using
ClusterRolesandRoleBindings(orRolesandRoleBindingsfor namespace-specific permissions).- Diagnosis: Check existing
ClusterRolesandRoleBindingsin the GKE cluster.# First, get kubectl configured for the cluster gcloud container clusters get-credentials my-gke-cluster --zone=us-central1-a --project=project-a # Then, check bindings kubectl auth can-i --list -n default # For default namespace kubectl get clusterroles kubectl get clusterrolebindings - Fix: Create a
ClusterRolethat defines the necessary permissions and bind it to the service account.# clusterrole-developer.yaml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: app-deployer rules: - apiGroups: ["apps", "extensions", "batch"] resources: ["deployments", "statefulsets", "jobs", "cronjobs", "replicasets"] verbs: ["create", "get", "list", "watch", "update", "patch", "delete"] - apiGroups: [""] resources: ["pods", "services", "configmaps", "secrets", "persistentvolumeclaims", "replicationcontrollers", "endpoints"] verbs: ["create", "get", "list", "watch", "update", "patch", "delete"] - apiGroups: ["networking.k8s.io"] resources: ["ingresses"] verbs: ["create", "get", "list", "watch", "update", "patch", "delete"]
Correction: Thekubectl apply -f clusterrole-developer.yaml # Bind this ClusterRole to the GCP Service Account (using its Kubernetes representation) # The Kubernetes service account name is usually the GCP service account name without the project.gserviceaccount.com suffix. # E.g., dev-sa@project-a.iam.gserviceaccount.com becomes dev-sa kubectl create clusterrolebinding app-deployer-binding \ --clusterrole=app-deployer \ --user="system:serviceaccount:gke-system:dev-sa" \ --project=project-a # This --project flag is NOT for kubectl, it's just for context here.--userfield inkubectl create clusterrolebindingexpects a Kubernetes-style service account name. For GCP-managed service accounts used with GKE, the format issystem:serviceaccount:<namespace>:<service-account-name>. Often, for GKE, the namespace isgke-systemor inferred. A more robust way is to use the GCP IAM binding to Kubernetes RBAC:# This is typically handled implicitly or via GKE's Workload Identity setup. # If Workload Identity is enabled, the GCP SA is directly mapped. # If not, you'd create a Kubernetes SA and bind the GCP SA to it. # Assuming Workload Identity is enabled, the GCP SA 'dev-sa@project-a.iam.gserviceaccount.com' # is directly usable as a Kubernetes identity. # The correct binding would look like this: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: app-deployer-binding subjects: - kind: User name: dev-sa@project-a.iam.gserviceaccount.com # Use the full GCP SA email apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole name: app-deployer apiGroup: rbac.authorization.k8s.iokubectl apply -f - <<EOF apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: app-deployer-binding subjects: - kind: User name: dev-sa@project-a.iam.gserviceaccount.com apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole name: app-deployer apiGroup: rbac.authorization.k8s.io EOF - Why it works: This grants the service account the exact Kubernetes permissions needed to manage application workloads, without allowing it to modify the cluster’s infrastructure or other users’ resources.
- Diagnosis: Check existing
Step 2: Cloud SQL Access for the Service Account
The service account needs to connect to the Cloud SQL instance in project-b.
- Diagnosis: Check existing roles on the service account
dev-sa@project-a.iam.gserviceaccount.cominproject-b.gcloud projects get-iam-policy project-b --flatten="bindings[].members" --filter="bindings.members:serviceAccount:dev-sa@project-a.iam.gserviceaccount.com" - Fix: Grant the
roles/cloudsql.clientrole to the service account onproject-b. This role allows principals to connect to Cloud SQL instances.gcloud projects add-iam-policy-binding project-b \ --member="serviceAccount:dev-sa@project-a.iam.gserviceaccount.com" \ --role="roles/cloudsql.client" - Why it works: The
roles/cloudsql.clientIAM role authorizes the service account to establish a secure connection to the Cloud SQL instance, enabling applications to interact with the database.
Summary of Changes:
- Removed
Editorrole fromdev-sa@project-a.iam.gserviceaccount.comonproject-aandproject-b. - Added
roles/container.developertodev-sa@project-a.iam.gserviceaccount.comonproject-a. - Configured Kubernetes RBAC within the GKE cluster to grant specific deployment permissions to the service account.
- Added
roles/cloudsql.clienttodev-sa@project-a.iam.gserviceaccount.comonproject-b.
Next Potential Issue:
After implementing these changes, if the application running in GKE needs to write logs to Cloud Logging, the service account will likely encounter a PERMISSION_DENIED error when trying to write logs, requiring the roles/logging.logWriter role to be added to its IAM policy for project-a.