Most GCP organisations know they have an over-privilege problem. IAM Recommender has been telling them so for years; surfacing recommendations to replace roles/editor with three narrower roles, flagging service accounts that use 8 of 2,400 granted permissions, highlighting permissions unused for 90+ days.
And yet, standing privilege persists. Teams apply the recommendation, the role gets narrower, and six months later the principal has accumulated new bindings to compensate. The problem is not a lack of diagnosis. The problem is a lack of enforcement.
IAM Recommender answers one question: “Which roles are broader than they need to be?” It cannot answer the harder question: “Should this access exist right now?” That second question is the domain of Just-In-Time (JIT) access: a model where privilege is granted on demand, for a defined duration, with approval and audit, and revoked automatically when the task is complete.
Together, these two capabilities form the complete least-privilege implementation for GCP. IAM Recommender is the diagnostic layer. JIT access is the enforcement layer. This guide walks through both “how they work, where each stops, and how to combine them into an operational system that prevents privilege accumulation structurally rather than procedurally”.
Who this is for: GCP platform engineers, cloud architects, and DevSecOps teams building toward zero-standing-privilege. You should be comfortable with gcloud CLI, GCP IAM concepts (roles, bindings, conditions), and Cloud Audit Logs.
Why Least Privilege Fails in GCP Without Active Enforcement
GCP’s IAM model is expressive and powerful. It is also, by default, permissive in ways that compound over time.
The Hierarchy Problem
GCP’s resource hierarchy (Organisation → Folder → Project → Resource) is elegant for organising workloads. It is dangerous for privilege management because role bindings are inherited downward. Grant roles/editor at a folder level, and every project within that folder inherits it. A single binding at the wrong level silently grants write access to hundreds of resources across dozens of projects. Most teams discover this during their first serious IAM audit, not before.
The Primitive Roles Trap
roles/editor grants write permissions to nearly every GCP service over 5,000 individual permissions as of 2026. Teams default to it because creating custom roles requires understanding exactly which permissions a workload needs, and during the “move fast” phase, nobody does that analysis. The result: developers granted Editor during onboarding retain it permanently. The role is never scoped, never reviewed, never revoked.
roles/owner is worse: it includes IAM policy administration, meaning the principal can escalate their own privileges or grant access to others. Both primitive roles explicitly violate the principle of least privilege, and both are pervasive in production GCP environments.
Service Account Key Sprawl
GCP workloads rely heavily on service accounts for identity. The expedient path (downloading a JSON key and embedding it in a CI/CD pipeline, a .env file, or a startup script) creates permanent, unrotated credentials that grant whatever roles are bound to that service account. These keys never expire unless manually deleted. They are frequently shared, committed to repositories, or left in plaintext configuration files. A single leaked key with roles/editor on a production project is an unrestricted backdoor.
The CI/CD and AI Agent Surface
Jenkins, GitHub Actions, Cloud Build, and Terraform pipelines commonly run with service account keys that grant far more access than the pipeline needs. The reasoning is always the same: “it’s easier to grant Editor than to figure out the exact permissions required.” Now multiply this by the newest surface: AI coding agents (Claude Code, Cursor, Kiro, Copilot) that use GCP credentials from the developer’s machine. These agents inherit whatever permissions the developer’s service account or ADC (Application Default Credentials) provides often full project access.
The Compliance Gap
SOC 2 (CC6.3) requires that access is removed when no longer needed. ISO 27001 (A.9) mandates formal processes for provisioning and revocation of access rights. Cyber insurers now explicitly ask: “Do you have JIT or time-bound access controls?” Permanent IAM bindings “even narrowly scoped ones” cannot satisfy these requirements. Auditors want evidence that privilege is time-bound and reviewed, not just that roles are reasonably sized.
The Bottom Line
Least privilege is not a one-time role reduction exercise. It is an operational discipline. You can right-size every role in your GCP org today, and within months, privilege accumulates again through legitimate requests, emergency grants that never get revoked, and new service accounts provisioned with default Editor access. Without an enforcement mechanism (one that structurally prevents privilege from persisting beyond the task that requires it) least privilege degrades continuously.
GCP IAM Recommender: What It Does and Where It Stops
How IAM Recommender Works
IAM Recommender is part of GCP’s Policy Intelligence suite. It uses machine learning to analyse Cloud Audit Log data (specifically Admin Activity and Data Access logs) over the preceding 90 days or since the role was granted, if shorter. Based on observed permission usage, it generates per-principal recommendations to replace overly broad roles with narrower alternatives.
The logic is straightforward: if a principal holds roles/editor but has only exercised permissions consistent with roles/bigquery.dataViewer and roles/storage.objectViewer over 90 days, Recommender suggests replacing Editor with those two predefined roles. If no predefined role matches the usage pattern precisely, it may recommend creating a custom role scoped to the exact permissions observed.
Recommender covers both human users and service accounts. It operates at project, folder, and organisation levels, and integrates with Security Command Center for centralised visibility.
Listing recommendations via CLI:
gcloud recommender recommendations list \
--project=my-production-project \
--location=global \
--recommender=google.iam.policy.Recommender \
--format="table(name, primaryImpact.category, stateInfo.state, description)"
Complementary tool: Policy Analyzer: While Recommender identifies over-privilege, Policy Analyzer lets you query who has access to specific resources across your organisation hierarchy. Run it to answer questions like “who can access my production Cloud SQL instance?” or “which principals have iam.serviceAccountKeys.create anywhere in the org?” Together, Recommender and Policy Analyzer form the discovery layer for your least-privilege programme.
Where IAM Recommender Stops
Recommender is an excellent diagnostic tool. It is not an enforcement mechanism. Understanding its boundaries is critical for building a complete least-privilege system.
- It recommends role reduction and does not eliminate standing access. Even after applying every recommendation, the principal still holds the reduced role permanently. A right-sized role is still a standing role. The principal has
roles/cloudsql.editor24 hours a day, 7 days a week, even if they only need it during monthly maintenance windows. - No enforcement mechanism. Recommendations sit in the console (or in API responses) until someone manually acts on them. Without automation or governance workflows, recommendations age and become stale. Many organisations have hundreds of unactioned recommendations spanning months.
- No time-bounding. Recommender has no concept of “this principal needs access for 2 hours.” It analyses the last 90 days of usage and optimises the role for what was observed. It cannot distinguish between a principal who uses a permission daily and one who uses it once a quarter.
- No approval workflow. There is no audit trail of why someone has access, who approved it, or when it should be revisited. The binding exists; Recommender either flags it or doesn’t. There is no governance layer.
- Context-blind. Recommender does not know whether access is needed right now. A service account used only for annual disaster recovery testing still shows as “active” in the 90-day window if DR was tested within that period.
- Scope limitations. IAM Recommender has zero visibility into:
- Database-level access (Cloud SQL sessions, AlloyDB connections, query-level activity)
- GKE RBAC bindings (ClusterRoleBindings, Namespace-scoped RoleBindings)
- VM-level SSH/OS Login sessions
- Non-human identity credential hygiene (JSON key age, rotation status)
- AI coding agent credential usage
- Single-cloud only. If you run GCP alongside AWS or Azure (and most mid-market and enterprise organisations do) Recommender sees nothing outside GCP.
- The transition: IAM Recommender answers “what should be reduced?” It cannot answer “should this access exist right now, and for how long?” That is the JIT question.
JIT Access: The Enforcement Layer IAM Recommender Needs
What JIT Means Operationally in GCP
Just-In-Time access inverts the default. Instead of “access granted permanently until manually revoked,” the default state is zero standing privilege. Every privilege elevation follows a lifecycle:
- Request: The principal states what role they need, on which resource scope, why, and for how long.
- Approve: A designated approver (human or policy-based auto-approval for low-risk requests) validates the request.
- Grant: The IAM binding is applied with a TTL. The principal has access.
- Use: Every action during the session is logged with identity attribution in Cloud Audit Logs.
- Auto-Revoke: When the TTL expires, the binding is removed. No manual cleanup, no “forgot to revoke” drift.
Privilege becomes a transaction, not a property. The IAM binding exists only for the duration of the task that requires it.
GCP Cloud PAM: The Native Option
Google Cloud Privileged Access Manager (Cloud PAM) reached General Availability in 2024, establishing GCP’s native JIT capability. The core concepts:
- Entitlements define what JIT access is available: which IAM roles, on which resource scope (org, folder, or project), maximum grant duration, who can request, and who must approve.
- Grants are individual requests made against an entitlement. Upon approval, the specified roles are temporarily bound to the principal for the approved duration, then automatically revoked.
Cloud PAM integrates natively with Cloud Audit Logs, meaning every request, approval, activation, and expiry is a logged event. It supports both human users and service accounts.
Where Cloud PAM fits and where it stops:
| Strength | Limitation |
|---|---|
| Native to GCP; no third-party dependency | GCP-only; no multi-cloud governance |
| Integrated with Cloud Audit Logs | No database-layer JIT (Cloud SQL, AlloyDB, self-managed DBs) |
| Covers IAM role elevation for humans and service accounts | No VM-layer keyless SSH with session recording |
| Built-in approval workflows | No GKE RBAC JIT |
| Works at org, folder, and project levels | Limited approval UX (no Slack/Teams-native workflows) |
| No additional cost | No coverage for AI coding agent credentials |
| No correlation to broader security posture (attack paths, misconfigs) |
Cloud PAM is a solid starting point for teams that operate exclusively in GCP and need JIT only for IAM role elevation. For teams that need JIT across the full privilege surface (databases, VMs, Kubernetes, AI agents, and multiple clouds) it covers one dimension of a multi-dimensional problem.
Cloudanix JIT: Full-Surface JIT for GCP and Beyond
Cloudanix JIT extends the JIT principle to every privilege surface in a GCP environment and across clouds for multi-cloud teams:
- Cloud JIT: Time-bound IAM role elevation in GCP projects, with Slack/Teams-native approval workflows, configurable TTLs, justification requirements, and full audit trails. Same surface as Cloud PAM, with richer governance and integration options.
- Database JIT: Time-bound, identity-stamped access to Cloud SQL, AlloyDB, or self-managed PostgreSQL/MySQL on GCE. Engineers connect from their existing IDE (DBeaver, DataGrip, TablePlus, pgAdmin) without seeing credentials. Every query is logged. Dynamic PII masking is applied at query time. Destructive queries (DROP, TRUNCATE) can be blocked or require secondary approval.
- VM JIT: Keyless SSH/RDP into GCE instances. Time-bound, session-recorded, auto-revoked. Eliminates persistent SSH keys and standing OS Login grants.
- Kubernetes JIT: Time-bound RBAC elevation for GKE cluster operations. Convert standing
cluster-adminClusterRoleBindings to JIT grants with approval workflows. - Agentic JIT: Short-lived, scoped GCP credentials for AI coding agents (Claude Code, Cursor, Kiro, Copilot) issued via MCP. The agent receives a credential that expires in minutes, not a permanent service account key.
- Non-human identity governance: Service accounts and CI/CD pipelines receive ephemeral credentials via Workload Identity Federation integration, replacing downloaded JSON keys.
All JIT requests and approvals route through Slack or Microsoft Teams ‘the collaboration tools your team already uses’. Audit data lands in the customer’s own GCS bucket for full data sovereignty. And for multi-cloud teams, the same policy, the same approval workflow, and the same audit trail govern JIT across GCP, AWS, and Azure.
The Combined Workflow: Recommender + JIT in Practice
Here is the implementation playbook. Four phases, each building on the last.
Phase 1: Discover and Right-Size (IAM Recommender)
Step 1: Ensure audit log coverage. IAM Recommender accuracy depends on Data Access log availability. Enable Data Access logs for all critical services across your projects. Admin Activity logs are always on; Data Access logs must be explicitly enabled.
# Check current audit log configuration for a project
gcloud projects get-iam-policy my-production-project \
--format="json" | jq '.auditConfigs'
Step 2: Pull recommendations across the org. Start with production projects and work outward.
# List recommendations for a specific project
gcloud recommender recommendations list \
--project=my-production-project \
--location=global \
--recommender=google.iam.policy.Recommender
# For org-wide visibility, iterate across projects or use
# Security Command Center's IAM findings
Step 3: Run Policy Analyzer for sensitive resources. Map who can access your most critical assets.
# Who can access a specific Cloud SQL instance?
gcloud asset analyze-iam-policy \
--organization=ORG_ID \
--full-resource-name="//sqladmin.googleapis.com/projects/my-project/instances/prod-db" \
--permissions="cloudsql.instances.connect"
Step 4: Apply recommendations; primitives first. Replace roles/editor and roles/owner bindings with the recommended predefined roles. Track every change in a change ticket for audit trail.
# Remove overly broad binding
gcloud projects remove-iam-policy-binding my-production-project \
--member="user:engineer@company.com" \
--role="roles/editor"
# Add recommended narrower roles
gcloud projects add-iam-policy-binding my-production-project \
--member="user:engineer@company.com" \
--role="roles/cloudsql.editor"
gcloud projects add-iam-policy-binding my-production-project \
--member="user:engineer@company.com" \
--role="roles/storage.objectAdmin"
Step 5: Create custom roles where needed. For principals with very narrow observed usage, create custom roles with only the permissions actually exercised.
Phase 2: Categorize and Convert to JIT
Step 6: Tier roles by risk.
| Tier | Examples | JIT Policy |
|---|---|---|
| Tier 1 (Critical) | Organization Admin, Owner, Security Admin, IAM Admin, Billing Admin | JIT-only with mandatory human approval. Max TTL: 1 hour. |
| Tier 2 (Privileged) | Editor, DB Admin, GKE Admin, Network Admin, Compute Admin | JIT with configurable approval. Max TTL: 4 hours. |
| Tier 3 (Operational) | Viewer, Log Reader, Monitoring Viewer | May remain standing; reviewed quarterly. |
Step 7: Remove standing Tier 1 and Tier 2 bindings. Principals retain only the minimum standing role needed for day-to-day non-privileged work. Typically roles/viewer or a custom read-only role. All privileged access moves exclusively to JIT.
Step 8: Define JIT entitlements. For each Tier 1 and Tier 2 role, define:
- Which principals can request it
- Maximum TTL per request
- Approval routing (who approves, SLA for response)
- Justification requirements (free text, ticket number, or both)
- Escalation path if the approver doesn’t respond within SLA
Phase 3: Extend Beyond IAM
This is where you move past what Cloud PAM (and IAM Recommender) can see.
Step 9: Database access. Replace standing Cloud SQL/AlloyDB credentials with JIT Database Access. Engineers request time-bound connections; every query during the session is identity-stamped and logged. PII fields are dynamically masked unless the user’s role explicitly requires plaintext.
Step 10: VM SSH access. Replace persistent SSH keys and standing OS Login grants with JIT VM access. Keyless, time-bound, session-recorded. When the session ends, the access is gone.
Step 11: GKE RBAC. Convert standing cluster-admin ClusterRoleBindings to JIT Kubernetes grants. A platform engineer needing to debug a pod gets time-bound admin access to a specific namespace, not permanent cluster-admin.
Step 12: Service accounts and CI/CD. Replace downloaded JSON keys with Workload Identity Federation for workloads that can use it. For pipelines that genuinely need elevated IAM roles, implement JIT elevation within the pipeline. The service account requests elevation at the start of the job and access is revoked when the job completes.
Step 13: AI coding agents. If developers use Claude Code, Cursor, Kiro, or Copilot with GCP credentials, replace long-lived service account keys in .envrc or ADC with Agentic JIT. The agent receives scoped, short-lived credentials via MCP; enough to complete the task, expired before any potential exfiltration can leverage them.
Phase 4: Instrument and Monitor
Step 14: Sink JIT events to BigQuery.
# Create a log sink for all IAM policy changes (JIT grants/revokes)
gcloud logging sinks create jit-audit-sink \
bigquery.googleapis.com/projects/my-project/datasets/jit_audit \
--log-filter='protoPayload.methodName="SetIamPolicy" OR
protoPayload.serviceName="privilegedaccessmanager.googleapis.com"'
All JIT session events (Grant, Use, Revoke) land in BigQuery for long-term analytics, compliance reporting, and anomaly detection.
Step 15: Alert on anomalies. Configure alerts for:
- JIT session accessing resources outside its declared scope
- JIT grant requested for an unusual role or unusual time
- JIT grant duration significantly exceeding typical task time
- Any
SetIamPolicycall that does not originate from the JIT system (rogue manual grants)
Step 16: Quarterly entitlement review. Review the JIT entitlement catalog quarterly:
- Remove stale entitlements for roles no longer needed
- Adjust TTLs based on observed session durations
- Validate that no standing Tier 1/2 bindings have crept back in (a single BigQuery query answers this)
- Update approval routing for team structure changes
The Surfaces IAM Recommender Cannot See
IAM Recommender is excellent for GCP IAM bindings at the project and org level. But privilege exists on surfaces it has zero visibility into. Each of these surfaces needs its own least-privilege enforcement.
Database Access (Cloud SQL, AlloyDB, Self-Managed)
IAM Recommender has no insight into who connects to your databases, what queries they execute, whether they access PII, or whether they run destructive operations. A principal may have roles/cloudsql.client (the permission to connect) Recommender sees that binding. But what happens inside the database session is invisible to it.
JIT Database Access closes this gap: time-bound credentials issued per session, identity-stamped query-level audit, dynamic PII masking at query time, and destructive-query prevention (DROP, TRUNCATE, bulk DELETE blocked or requiring approval). This is particularly relevant for DPDPA, GDPR (Article 25 Data Protection by Default), and SOC 2 (CC6.1 Logical Access to Sensitive Data).
VM / SSH Access
OS Login provides identity-based SSH to GCE instances, but it does not time-bound sessions or record what happens during them. A developer with roles/compute.osAdminLogin has permanent root SSH access to every instance in the project. JIT VM Access replaces this with keyless, time-bound SSH that is session-recorded and auto-revoked. No persistent keys, no standing OS Login grants, no bastion host credential management.
Kubernetes (GKE) RBAC
GKE RBAC bindings (ClusterRoleBindings and RoleBindings) exist in the Kubernetes layer, entirely outside IAM Recommender’s scope. Standing cluster-admin bindings are the Kubernetes equivalent of roles/editor in the GCP IAM layer: far too broad, rarely reviewed, and an attractive lateral-movement target. JIT Kubernetes grants time-bound RBAC elevation scoped to the specific cluster, namespace, and role needed for the task at hand.
Non-Human Identities and AI Coding Agents
Service accounts with downloaded JSON keys are IAM Recommender’s blind spot in practice. Recommender can flag that the service account’s role is over-broad, but it cannot eliminate the key itself a permanent, downloadable credential that exists outside GCP’s control once exported.
AI coding agents compound this problem. Claude Code, Cursor, Kiro, and Copilot running on developer machines inherit whatever GCP credentials exist in the local environment often Application Default Credentials backed by a service account with Editor-level access. These agents make API calls, read resources, and generate code with full access to whatever the credential permits. A compromised or misbehaving agent has the same blast radius as the credential it holds.
Agentic JIT addresses this by brokering scoped, short-lived credentials to AI agents via MCP. The agent receives only the permissions needed for the current task, for a duration measured in minutes. The Coding Agent Firewall adds on-host DLP that blocks secret and PII exfiltration before any token leaves the developer’s machine.
Multi-Cloud Environments
If you run GCP + AWS or Azure (and most organisations at the mid-market and enterprise level do) you face a fragmentation problem: IAM Recommender for GCP, IAM Access Analyzer for AWS, and Entra ID Access Reviews for Azure. Three separate tools, three separate recommendation formats, three separate remediation workflows, zero cross-cloud correlation.
A unified JIT layer provides one policy, one approval workflow, and one audit trail across all clouds. A platform engineer who needs temporary elevated access in GCP and AWS for a cross-cloud migration task makes one request, gets one approval, and has both grants time-bound and auto-revoked simultaneously.
Measuring Success: Metrics That Matter
You cannot manage what you do not measure. A least-privilege programme backed by JIT needs clear success metrics.
| Metric | What It Measures | Target |
|---|---|---|
| % principals with zero standing privilege (Tier 1 & 2) | Completeness of JIT rollout | 100% |
| Mean JIT session TTL | How tightly access is time-bound | <2 hours average |
| IAM Recommender acceptance rate | How many recommendations are actually applied | >90% |
| Standing privilege age | How long unused/over-privileged bindings persist | <7 days before remediation |
| Mean time to approve (MTTA) | Friction in the JIT workflow | <5 min for Tier 2; <15 min for Tier 1 |
| JIT session anomaly rate | How often sessions deviate from declared scope | Trending toward 0% |
| Compliance evidence generation time | Audit-prep effort for SOC 2, ISO 27001 | Minutes, not weeks |
| Rogue binding detection | Manual SetIamPolicy calls outside the JIT system | Zero tolerance; alert immediately |
Organisations that have implemented full-surface JIT report measurable outcomes: 100% reduction in privileged access exposure, audit prep moving from weeks to hours, and standing privilege eliminated as a structural possibility rather than managed as a policy aspiration.
The goal is not “JIT exists somewhere in our environment.” The goal is “standing privilege is structurally impossible for Tier 1 and Tier 2 roles.”
Bringing It Together
The two-layer model is simple:
- IAM Recommender continuously diagnoses over-privilege. It tells you what to shrink, which principals are over-permissioned, and where role bindings exceed observed usage.
- JIT access continuously prevents privilege accumulation. It ensures that even correctly-sized roles are only active when genuinely needed, with approval, audit, and automatic revocation.
Neither layer alone is sufficient. Recommender without JIT means roles get right-sized but remain permanent — and accumulate again within months. JIT without Recommender means you are time-bounding access to roles that may still be far broader than necessary. The combination gives you correctly-scoped access that exists only when needed.
The practical starting point: Run gcloud recommender recommendations list on your GCP org this week. Identify your top 10 over-privileged principals. Apply the safe recommendations. Then ask yourself: after I right-size these roles, will they stay right-sized in 6 months without enforcement?
If the answer is no (and for most organisations it will be) that is the signal to implement JIT. Convert your Tier 1 and Tier 2 roles to JIT-only entitlements. Extend JIT to databases, VMs, GKE, and AI coding agents. Instrument the audit trail in BigQuery. Run quarterly reviews.
The result is a GCP environment where standing privilege is the exception: time-bound, audited, and structurally unable to persist beyond the task that requires it.
Related Resources
- What is Just-In-Time (JIT) Access?
- How to Implement JIT Access in AWS, Azure & GCP
- The End of Permanent Access: Next-Generation JIT for Granular Database Security
- Understanding Privileged Access Management
- What is Zero Trust Security?
- How to Audit IAM Permissions Across Multi-Cloud Environments
- Mastering Third-Party Access with a Just-in-Time Approach