Home / Security

Fix Over-Privileged IAM with Policy Analyzer and IAM Recommender | 2026

Fix Over-Privileged IAM with Policy Analyzer and IAM Recommender | 2026

GCP IAM Recommender flags unused permissions and over-granted roles, while Policy Analyzer lets you query who can access what across your organization. Together they replace manual IAM spreadsheet audits with data-driven least-privilege remediation.

By Mateusz Chmielewski · Jul 31, 2026 · 15 min read

What Are IAM Policy Analyzer and IAM Recommender?

IAM Policy Analyzer is a Cloud Asset Inventory capability that evaluates IAM policies to show which principals have access to which resources, and under what conditions. IAM Recommender is a separate Recommender API service that continuously inspects IAM policy usage and suggests removing unused roles or replacing basic roles with least-privilege alternatives.

Think of Policy Analyzer as a building's master key-log: it tells you every door every key can open right now. IAM Recommender is the security guard who watches which doors are never used and recommends revoking those keys.

ConceptExplanationWhen to use
Policy AnalyzerQuery engine that evaluates effective IAM access across orgs, folders, and projects.Forensics, access certification, proving least privilege, pre-change impact analysis
IAM RecommenderMachine-learning service that surfaces unused role grants and role substitutions.Continuous IAM hygiene, basic-role replacement, unused permission cleanup
InsightA finding that explains why a recommendation was generated, including observed permission usage.Validating whether a recommendation is safe before applying it
Recommendation StateLifecycle status: ACTIVE, CLAIMED, SUCCEEDED, FAILED, or DISMISSED.Tracking remediation progress in dashboards or ticketing systems
Basic RolesLegacy Owner, Editor, and Viewer roles with thousands of permissions.Avoid in production; replace with predefined or custom roles
Custom RoleProject- or organization-level role containing only the permissions you define.When no predefined role matches the exact least-privilege surface

Why Use Policy Analyzer and IAM Recommender in Production?

Manual IAM audits rely on outdated spreadsheets and spot-checks. Basic roles accumulate over time, service accounts keep permissions they no longer need, and ex-employees or contractors retain access because no one removed their bindings. Each extra permission expands blast radius.

Policy Analyzer gives you a queryable, auditable view of effective access. IAM Recommender continuously inspects Cloud Logging usage data and proposes safe removals. Combined, they turn IAM cleanup from a quarterly panic into a repeatable, evidence-based workflow. For perimeter defense beyond IAM, combine this with [VPC Service Controls](/tutorial/terraform-vpc-service-controls-custom-module-guide), [Organization Policy Defaults](/tutorial/gcp-organization-policies-defaults-terraform-module), [Secret Manager Automatic Rotation](/tutorial/gcp-secret-manager-terraform-module-automatic-rotation), and secure cluster compute with [GKE Spot VMs](/tutorial/spot-vms-preemptible-gke-cost-optimization).

FeaturethisServicealtAaltB
Discovery MethodAutomated policy evaluation + usage signalsManual CSV export / spreadsheetThird-party CSPM tool
CostFreeEngineer time only$0.10-$2.00 / resource / month
Remediation Formatgcloud + Terraform HCLManual console clicksVendor-specific API / agent
Near Real-TimeYes (Policy Analyzer); Daily (Recommender)NoVaries by scan frequency
Native Audit TrailCloud Logging + Cloud Asset InventorySpreadsheet historyTool-dependent

Prerequisites

  • GCP Organization with active billing
  • gcloud CLI v450.0.0+ authenticated with `gcloud auth login`
  • Terraform CLI v1.5.0+ — `terraform --version`
  • IAM role `roles/cloudasset.viewer` for Policy Analyzer
  • IAM role `roles/recommender.iamViewer` or `roles/recommender.iamAdmin` for IAM Recommender
  • Organization-level permissions to view or modify IAM policies (at least for target projects)

Step-by-Step Guide

Step 1: Enable Required APIs

Activating Cloud Asset Inventory, IAM, Recommender, and Resource Manager APIs so Policy Analyzer and IAM Recommender can evaluate policies and return findings. Policy Analyzer lives behind the Cloud Asset Inventory API. Without it, `gcloud asset analyze-iam-policy` returns API_NOT_ENABLED.

resource "google_project_service" "required_apis" {
  for_each = toset([
    "cloudasset.googleapis.com",
    "recommender.googleapis.com",
    "iam.googleapis.com",
    "cloudresourcemanager.googleapis.com",
  ])

  project            = var.project_id
  service            = each.key
  disable_on_destroy = false
}

Step 2: Define Terraform Variables and Provider

Declaring typed variables for project, organization, and target identities so the same module can run against dev and prod. Typed variables prevent you from accidentally running IAM cleanup against the wrong organization or project.

variable "project_id" {
  description = "Project to audit and remediate"
  type        = string
}

variable "org_id" {
  description = "Organization ID for Policy Analyzer scope"
  type        = string
}

variable "target_member" {
  description = "Principal to analyze, e.g. serviceAccount:[email protected]"
  type        = string
}

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
  }
  backend "gcs" {
    bucket = "my-org-tfstate"
    prefix = "iam-hygiene/prod"
  }
}

provider "google" {
  project = var.project_id
}

Step 3: Run Policy Analyzer to Map Effective Access

Querying which resources a specific identity can access, or which identities can access a specific resource. This is read-only and safe to run in production. Policy Analyzer resolves group memberships and conditional roles, showing effective access rather than raw policy bindings.

# Find what a service account can access inside a project
gcloud asset analyze-iam-policy \
  --project=${PROJECT_ID} \
  --identity="serviceAccount:legacy-batch@${PROJECT_ID}.iam.gserviceaccount.com" \
  --expand-groups \
  --expand-resources \
  --format=json | jq '.mainAnalysis.results[] | {resource, role}'

# Find who can act as Owner on a project
gcloud asset analyze-iam-policy \
  --organization=${ORG_ID} \
  --full-resource-name="//cloudresourcemanager.googleapis.com/projects/${PROJECT_ID}" \
  --permissions="resourcemanager.projects.getIamPolicy" \
  --format=json | jq '.mainAnalysis.results[] | {member, role}'

Step 4: List IAM Recommender Findings

Retrieving active recommendations for unused role grants and basic-role replacements in the target project or organization. IAM Recommender uses Cloud Logging data to determine whether a role's permissions were actually exercised in the last 90 days.

# Service account IAM policy recommendations (unused grants)
gcloud recommender recommendations list \
  --recommender=google.iam.serviceAccount.iamPolicy.Recommender \
  --project=${PROJECT_ID} \
  --location=global \
  --format=json

# IAM policy recommendations (basic role replacement / unused bindings)
gcloud recommender recommendations list \
  --recommender=google.iam.policy.Recommender \
  --project=${PROJECT_ID} \
  --location=global \
  --format=json

Step 5: Inspect Recommendation Details and Safety

Reading the insight behind a recommendation before removing a binding, to confirm the role was genuinely unused. Not all recommendations are safe to apply blindly. Some roles may be used seasonally or during disaster recovery.

gcloud recommender insights describe \
  INSIGHT_ID \
  --insight-type=google.iam.serviceAccount.iamPolicy.Insight \
  --project=${PROJECT_ID} \
  --location=global \
  --format=json | jq '.content'

Step 6: Remediate Over-Privileged Bindings with Terraform

Removing the unused Owner/Editor binding and replacing it with a custom role that contains only required permissions. Terraform keeps IAM changes in version control, allows peer review, and makes rollbacks trivial compared to console changes.

# Remove the over-granted basic role binding
# (Comment out or remove from your IAM module; never use google_project_iam_binding for authoritative cleanup unless you own every binding)
# resource "google_project_iam_member" "legacy_owner" {
#   project = var.project_id
#   role    = "roles/owner"
#   member  = var.target_member
# }

# Create a least-privilege custom role
resource "google_project_iam_custom_role" "batch_minimal" {
  project     = var.project_id
  role_id     = "batchMinimal"
  title       = "Batch Minimal Role"
  description = "Least-privilege role for legacy batch service account"
  permissions = [
    "storage.objects.get",
    "storage.objects.list",
    "storage.objects.create",
    "pubsub.topics.publish",
  ]
}

# Bind the custom role instead of Owner
resource "google_project_iam_member" "batch_custom_role" {
  project = var.project_id
  role    = google_project_iam_custom_role.batch_minimal.id
  member  = var.target_member
}

Step 7: Mark Recommendation as Succeeded or Dismissed

Updating the IAM Recommender recommendation state after manual remediation so security dashboards reflect reality. Stale ACTIVE recommendations create alert fatigue and make it harder to spot new over-privilege issues.

gcloud recommender recommendations mark-succeeded \
  RECOMMENDATION_ID \
  --recommender=google.iam.serviceAccount.iamPolicy.Recommender \
  --project=${PROJECT_ID} \
  --location=global \
  --etag=ETAG_FROM_LIST

# Or dismiss if you decide not to act
gcloud recommender recommendations mark-dismissed \
  RECOMMENDATION_ID \
  --recommender=google.iam.serviceAccount.iamPolicy.Recommender \
  --project=${PROJECT_ID} \
  --location=global \
  --etag=ETAG_FROM_LIST

Verification & Health Check

Best Practices

  • Replace Basic Roles with Predefined or Custom Roles
  • Scope Bindings to the Lowest Resource Level
  • Author Every IAM Change in Terraform
  • Set a Recurring IAM Review Cadence

Common Mistakes

  • {"errorCode":"API_NOT_ENABLED","symptoms":"`gcloud asset analyze-iam-policy` returns `Cloud Asset API has not been used in project before or it is disabled`.","rootCause":"The Cloud Asset Inventory API is not enabled in the project where gcloud is executing.","fixCommand":"gcloud services enable cloudasset.googleapis.com --project=${PROJECT_ID}","code":"resource \"google_project_service\" \"cloudasset\" {\n project = var.project_id\n service = \"cloudasset.googleapis.com\"\n disable_on_destroy = false\n}\n","language":"hcl","filename":"apis.tf","prevention":"Include Cloud Asset, Recommender, IAM, and Resource Manager APIs in your project-bootstrap Terraform module."}
  • {"errorCode":"PERMISSION_DENIED","symptoms":"Policy Analyzer or Recommender commands return HTTP 403 even though you are a project Owner.","rootCause":"These APIs require specific viewer/admin roles at the analyzed scope. Project Owner does not automatically include them.","fixCommand":"gcloud projects add-iam-policy-binding ${PROJECT_ID} \\\n --member=\"user:[email protected]\" \\\n --role=\"roles/cloudasset.viewer\"\ngcloud projects add-iam-policy-binding ${PROJECT_ID} \\\n --member=\"user:[email protected]\" \\\n --role=\"roles/recommender.iamViewer\"\n","code":"resource \"google_project_iam_member\" \"auditor\" {\n project = var.project_id\n role = \"roles/cloudasset.viewer\"\n member = \"group:[email protected]\"\n}\n\nresource \"google_project_iam_member\" \"recommender\" {\n project = var.project_id\n role = \"roles/recommender.iamViewer\"\n member = \"group:[email protected]\"\n}\n","language":"hcl","filename":"iam.tf","prevention":"Grant these roles via group membership and manage them in Terraform so auditors inherit permissions automatically."}
  • {"errorCode":"NO_RECOMMENDATIONS_FOUND","symptoms":"`gcloud recommender recommendations list` returns an empty list immediately after enabling APIs.","rootCause":"IAM Recommender needs enough Cloud Logging activity history (typically several days) before it can identify unused permissions.","fixCommand":"Wait 3-7 days, then re-run the list command.","code":"# No code fix required; verify logging is enabled\ngcloud logging sinks list --project=${PROJECT_ID}\n","language":"bash","filename":"","prevention":"Ensure `_Default` and `_Required` log sinks are active and not disabled by budget constraints."}
  • {"errorCode":"IAM_PROPAGATION_DELAY","symptoms":"After removing `roles/editor`, the service account can still perform editor actions for 30-60 seconds.","rootCause":"IAM policy changes are eventually consistent across GCP services.","fixCommand":"Wait 60 seconds and re-test, or run `gcloud auth application-default print-access-token` to force token refresh.","code":"resource \"time_sleep\" \"iam_propagation\" {\n depends_on = [google_project_iam_member.batch_custom_role]\n create_duration = \"60s\"\n}\n","language":"hcl","filename":"main.tf","prevention":"Add a `time_sleep` resource after critical IAM changes in automation, and design tests to retry with backoff."}

Cost Analysis

Featuremetriccost1kcost10kcost100kcost1m
Policy Analyzer API calls$0.00$0.00$0.00$0.00
IAM Recommender API calls$0.00$0.00$0.00$0.00
Cloud Scheduler (weekly review)$0.00$0.00$0.10$0.10
Cloud Logging storage$0.00$0.50$5.00$50.00

References

Browse all tutorials