Back to blog
Cloud SecurityGCPIdentity & AccessMonitoring & LoggingOperations & Compliance

IAM Audit Logging Not Configured on GCP: Why It Matters and How to Fix It

Learn why missing GCP IAM audit logging blinds your breach investigations, and how to enable Data Access logs across all services with gcloud and Terraform.

TL;DR

Without IAM audit logging enabled, GCP does not record who accessed or changed your permissions and resources, leaving you blind during a breach investigation. Enable Data Access audit logs in the IAM policy so admin and data activity is captured across all services.

Audit logs are the difference between knowing exactly what an attacker did inside your project and guessing. On Google Cloud, IAM audit logging controls which categories of activity get written to Cloud Logging. By default, Admin Activity logs are always on, but Data Access logs (the ones that record reads and writes against your resources) are off for almost everything. This check flags projects where audit logging is not configured to cover all services, which means you are missing the most valuable forensic trail you have.


What this check detects

The iam_auditlogging check inspects your project, folder, or organization IAM policy for an auditConfigs block. Specifically, it looks for a configuration that enables audit logging for allServices with the relevant log types turned on.

GCP defines three audit log types:

  • ADMIN_READ — operations that read metadata or configuration (for example, listing IAM policies).
  • DATA_READ — operations that read user-provided data (for example, reading an object from Cloud Storage).
  • DATA_WRITE — operations that write user-provided data.

Note: Admin Activity audit logs (writes to configuration and metadata) are always enabled and cannot be turned off. They do not count against your logging quota. What this check is really about is the Data Access logs, which are disabled by default and must be explicitly switched on.

When no auditConfigs exist, or they only cover a single service instead of allServices, the check fails. A passing configuration looks like this in the project IAM policy:

{
  "auditConfigs": [
    {
      "service": "allServices",
      "auditLogConfigs": [
        { "logType": "ADMIN_READ" },
        { "logType": "DATA_READ" },
        { "logType": "DATA_WRITE" }
      ]
    }
  ]
}

Why it matters

Imagine a service account key leaks through a misconfigured repository. The attacker uses it to read every object in a Cloud Storage bucket holding customer records, then exfiltrates them. With Data Access logging off, none of those read operations were recorded. When your security team investigates, they can see that the service account existed and had Storage permissions, but they cannot prove what was actually accessed. That gap turns a contained incident into a worst-case disclosure obligation, because under most breach notification rules you have to assume everything was taken if you cannot show otherwise.

Audit logs matter across several concrete scenarios:

  • Breach scope determination. DATA_READ logs tell you precisely which resources an attacker touched, so you can scope notifications narrowly instead of assuming the worst.
  • Insider threat detection. An employee quietly pulling data from BigQuery looks identical to normal usage unless you have the logs to spot anomalous read patterns.
  • Privilege escalation tracing. ADMIN_READ and Admin Activity logs together let you reconstruct how a low-privilege identity climbed to project owner.
  • Compliance evidence. Frameworks like PCI DSS, HIPAA, SOC 2, and ISO 27001 require logging of access to sensitive data. Auditors ask for these logs by name.

Warning: Data Access logs can be high volume and they are billed under Cloud Logging ingestion and storage. Turning on DATA_READ for every service in a busy project can generate large amounts of data. Plan log sinks, exclusions, and retention before flipping everything on at the organization level.


How to fix it

The fix is to update the IAM policy with an auditConfigs block covering allServices. You can do this through the console, the gcloud CLI, or infrastructure as code.

Option 1: gcloud CLI

First, export the current IAM policy so you do not overwrite existing bindings:

gcloud projects get-iam-policy PROJECT_ID \
  --format=json > policy.json

Edit policy.json and add (or merge) an auditConfigs array at the top level:

{
  "auditConfigs": [
    {
      "service": "allServices",
      "auditLogConfigs": [
        { "logType": "ADMIN_READ" },
        { "logType": "DATA_READ" },
        { "logType": "DATA_WRITE" }
      ]
    }
  ],
  "bindings": [ ... existing bindings, unchanged ... ],
  "etag": "...keep the existing etag..."
}

Danger: set-iam-policy replaces the entire policy. If you drop existing bindings from the file, you can lock yourself or your team out of the project. Always start from the exported policy, keep the etag value, and review the diff before applying.

Apply the updated policy:

gcloud projects set-iam-policy PROJECT_ID policy.json

Option 2: Cloud Console

  1. Open IAM & Admin, then Audit Logs.
  2. Select the services you want to cover. To cover everything, you will configure the default for all Google Cloud services.
  3. In the info panel, check Admin Read, Data Read, and Data Write.
  4. Click Save.

Option 3: Terraform

For repeatable, reviewable changes, manage audit configs in Terraform. Use google_project_iam_audit_config:

resource "google_project_iam_audit_config" "all_services" {
  project = "PROJECT_ID"
  service = "allServices"

  audit_log_config {
    log_type = "ADMIN_READ"
  }
  audit_log_config {
    log_type = "DATA_READ"
  }
  audit_log_config {
    log_type = "DATA_WRITE"
  }
}

Tip: Set this at the organization level with google_organization_iam_audit_config instead of per project. Audit configs inherit downward, so one organization-wide policy guarantees every existing and future project starts with logging on, and you never have to remember to repeat the fix.

If specific projects need to exempt certain service accounts (for example, a high-traffic internal pipeline that would otherwise flood logs), use exempted_members on the relevant log type rather than disabling logging entirely:

  audit_log_config {
    log_type         = "DATA_READ"
    exempted_members = ["serviceAccount:pipeline@PROJECT_ID.iam.gserviceaccount.com"]
  }

How to prevent it from happening again

Fixing one project is easy. Keeping every project compliant as your org grows is the real work. Bake the control into your delivery pipeline so a misconfigured project never reaches production.

Set it at the organization level once

The single most effective prevention is the organization-level audit config shown above. Because new projects inherit it automatically, you remove the manual step entirely.

Enforce with Organization Policy and CI checks

Add a policy-as-code gate so pull requests that remove or weaken audit configs fail before merge. With Open Policy Agent and Conftest you can scan Terraform plans:

package gcp.audit

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "google_project_iam_audit_config"
  resource.change.after.service != "allServices"
  msg := sprintf("Audit config must target allServices, got %v", [resource.change.after.service])
}

Wire it into your pipeline:

terraform plan -out=plan.tfplan
terraform show -json plan.tfplan > plan.json
conftest test plan.json --policy policy/

Detect drift continuously

Manual changes and emergency fixes will eventually drift from your Terraform state. Continuous scanning catches a project where someone disabled Data Access logs to reduce a logging bill. Lensix runs the iam_auditlogging check on a schedule and alerts you the moment a project falls out of compliance, so you are not relying on the next audit cycle to notice.


Best practices

  • Cover allServices, not individual services. Per-service configs are brittle. A new service goes unlogged until someone remembers to add it. The wildcard scales by default.
  • Route logs to a dedicated, locked-down project. Use an aggregated log sink to a separate project or a write-once bucket so an attacker who compromises one project cannot tamper with the evidence.
  • Set retention deliberately. Match retention to your compliance and incident response needs. Many breaches are discovered months after the fact, so 30 days is rarely enough for sensitive workloads.
  • Use exemptions surgically. When log volume from a noisy service account is genuinely a problem, exempt that single identity rather than turning off a whole log type.
  • Alert on the high-signal events. Build log-based metrics and alerts for IAM policy changes, service account key creation, and reads against your most sensitive buckets and datasets. Collecting logs is only half the job; you also need to act on them.

Note: Audit logs feed downstream security tooling. Cloud SIEM platforms, anomaly detection, and incident response runbooks all depend on this data existing. Enabling it is a prerequisite for nearly every detection capability you might want to build later.

Treat IAM audit logging as a baseline control, not an optional extra. It costs little to enable at the organization level, it is invisible during normal operations, and it becomes the most important data you own the day something goes wrong.