IAM debt reaccumulates after an initial cleanup: Editor roles granted temporarily and left in place, new workloads reusing existing service accounts, permissions from ended projects never revoked. Three signals catch permission drift: IAM Recommender for over-provisioning, BigQuery INFORMATION_SCHEMA for unexpected access patterns, and audit logs for IAM changes themselves.
IAM Recommender
GCP’s IAM Recommender analyzes 90 days of actual usage data to identify principals with more permissions than they use:
gcloud recommender recommendations list \ --project=YOUR_PROJECT_ID \ --location=global \ --recommender=google.iam.policy.RecommenderThe recommender might surface findings like:
- A service account with
bigquery.dataEditorhas only performed read operations for 90 days — reduce tobigquery.dataViewer - A user with
bigquery.adminhas never modified BigQuery settings — reduce tobigquery.jobUser - A principal has
roles/editorbut only uses BigQuery-specific permissions — replace with predefined BigQuery roles
Each recommendation includes a confidence score and the specific actions to take. High-confidence recommendations from the recommender are safe to apply without investigation. Medium-confidence ones warrant checking whether the usage pattern is representative — a data engineer who only reads for 90 days might just have been in a reading-heavy phase.
Run this monthly and process the recommendations. The recommender does the analysis; you make the judgment calls.
BigQuery INFORMATION_SCHEMA for Unexpected Access
IAM policies tell you what access principals could use. INFORMATION_SCHEMA.JOBS tells you what they actually do:
SELECT user_email, DATE(creation_time) AS query_date, COUNT(*) AS query_count, SUM(total_bytes_billed) / POW(10, 12) AS tb_billedFROM `region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECTWHERE creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)GROUP BY 1, 2ORDER BY tb_billed DESCReview this weekly or monthly with two questions:
Do unexpected identities appear? If a service account you’ve never seen before starts running queries, something changed — a new deployment, a credential being misused, or an old service account that was supposed to be decommissioned still running. Track down unexpected identities immediately.
Does the access pattern match expectations? A service account that ran 50 queries last quarter and now runs 5,000 warrants investigation. A user who normally runs analyst queries suddenly running large table scans at 3am is worth a conversation.
The cost attribution version of this query adds bytes-processed to the analysis — useful for catching runaway queries that result from broad permissions enabling access to large, unpartitioned tables.
Audit Logs for IAM Changes
BigQuery INFORMATION_SCHEMA covers what happens inside BigQuery. Cloud Audit Logs cover IAM changes across all of GCP. Sink audit logs to BigQuery and query them to track who grants permissions and when:
SELECT timestamp, protoPayload.authenticationInfo.principalEmail AS grantor, protoPayload.serviceData.policyDelta.bindingDeltasFROM `your_audit_dataset.cloudaudit_googleapis_com_activity`WHERE protoPayload.methodName = 'SetIamPolicy' AND timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)ORDER BY timestamp DESCThis surfaces every IAM policy change in the past 30 days. What you’re looking for:
- Editor or Owner roles being granted (should almost never happen)
- IAM changes happening outside normal business hours (could indicate compromise)
- Changes made by unexpected principals (who has permission to modify IAM?)
- Bursts of changes (bulk grants sometimes indicate automation gone wrong)
To set up the log sink:
gcloud logging sinks create iam-audit-sink \ bigquery.googleapis.com/projects/YOUR_PROJECT_ID/datasets/audit_logs \ --log-filter='protoPayload.methodName="SetIamPolicy"' \ --project=YOUR_PROJECT_IDThe sink captures new IAM changes going forward. It doesn’t backfill historical events, so set it up before you need it.
A Quarterly Review Process
Monitoring signals are most useful as inputs to a regular review cadence rather than ad-hoc investigation. A quarterly IAM review prevents drift from compounding:
Month 1: Run the full audit queries — Editor/Owner roles, service accounts with keys, shared accounts. Process findings from last quarter’s recommender output.
Month 2: Review INFORMATION_SCHEMA job patterns. Identify service accounts with unusual access patterns. Check that all active service accounts follow the naming convention (which tells you at a glance if something unauthorized was created).
Month 3: Review audit log IAM changes. Verify that IAM changes are happening through the expected mechanisms (Terraform PRs, approved access requests) rather than ad-hoc through the console or CLI.
The first review after a period of neglect is the most time-consuming. Subsequent quarterly reviews are shorter because they prevent accumulation rather than clear backlogs.
The Cost Connection
Over-permissioned IAM isn’t just a security problem. When analysts have access to tables they don’t need, they accidentally run expensive queries against them. When service accounts have Editor roles instead of scoped dataset access, they can read and scan any table in the project.
The INFORMATION_SCHEMA cost queries and the IAM INFORMATION_SCHEMA queries pull from the same data. Teams that monitor both together find that IAM cleanup often produces 20-30% reductions in BigQuery query costs — not because they optimized any queries, but because they removed the access that enabled the expensive ones.