This check flags GCP projects where Cloud Audit Logs are not capturing Admin Read, Data Read, and Data Write activity across all services. Without these logs you lose the forensic trail needed to investigate breaches. Fix it by setting an organization or project-level IAM audit config that enables all three log types for allServices.
When something goes wrong in a cloud environment, the first question is always the same: who did what, and when? In Google Cloud, the answer lives in Cloud Audit Logs. The problem is that GCP does not enable every category of audit logging by default. Admin Activity logs are always on, but Data Access logs (which include Admin Read, Data Read, and Data Write) are off for most services unless you explicitly turn them on.
The Lensix check logging_auditnotconfigured looks for exactly this gap. It verifies that your audit logging configuration covers all services with all three Data Access log types enabled. If it does not, you have blind spots in your activity record.
What this check detects
GCP Cloud Audit Logs are split into a few log types, each capturing a different category of activity:
- Admin Activity — administrative writes that modify configuration or metadata. Always enabled and cannot be disabled.
- Data Access — Admin Read — operations that read configuration or metadata (for example, listing buckets or describing an instance).
- Data Access — Data Read — operations that read user-provided data (for example, reading an object from Cloud Storage).
- Data Access — Data Write — operations that write user-provided data.
The check inspects your IAM policy auditConfigs block, either at the project, folder, or organization level, and confirms that allServices has ADMIN_READ, DATA_READ, and DATA_WRITE all enabled. If any of those are missing, or if logging is only configured for a handful of individual services, the check fails.
Note: Admin Activity and System Event logs are always written and free of charge. The three log types this check cares about are the Data Access logs, which are opt-in for almost everything except BigQuery (where Data Access logging is on by default and cannot be turned off).
Why it matters
Audit logs are the foundation of incident response. If an attacker steals a service account key and starts reading data out of your Cloud Storage buckets, Admin Activity logs will not show it. Reading an object is a Data Read operation, and without Data Access logging enabled, that activity leaves no trace.
Consider a realistic scenario. A misconfigured Cloud Function exposes a service account with broad storage permissions. An attacker uses it to exfiltrate customer data over the course of a week. Your security team gets an alert from an external source weeks later. They open Cloud Logging to scope the breach and find nothing, because Data Read logging was never enabled. Now you cannot tell which objects were accessed, how many records were exposed, or whether the access has stopped. That uncertainty turns a contained incident into a worst-case disclosure obligation.
There are also compliance angles. Frameworks like PCI DSS, SOC 2, HIPAA, and ISO 27001 all expect you to log access to sensitive data. The CIS Google Cloud Foundations Benchmark explicitly recommends enabling Data Access audit logs for all services. A failing check here is something auditors will flag.
Warning: Data Access logs can be high volume and they are billable through Cloud Logging ingestion. On a busy project, Data Read logging in particular can generate significant cost. Enable it deliberately, set up log exclusions for noisy low-value entries, and route logs to a cheaper sink like Cloud Storage or BigQuery for long-term retention.
How to fix it
The cleanest fix is to apply an audit config at the organization level so every project inherits it. You can also apply it per project. Either way, the mechanism is the same: you update the IAM policy with an auditConfigs entry for allServices.
Step 1: Export the current policy
Work against the current policy rather than blindly overwriting it, so you do not drop existing IAM bindings.
gcloud projects get-iam-policy PROJECT_ID \
--format=json > policy.json
Step 2: Add the audit config
Edit policy.json and add (or merge into) an auditConfigs array. For full coverage it should look like this:
{
"auditConfigs": [
{
"service": "allServices",
"auditLogConfigs": [
{ "logType": "ADMIN_READ" },
{ "logType": "DATA_READ" },
{ "logType": "DATA_WRITE" }
]
}
],
"bindings": [
...your existing bindings, unchanged...
],
"etag": "...existing etag..."
}
Note: Keep the etag field from the exported policy. It prevents you from overwriting changes that someone else made between your read and your write.
Step 3: Apply the updated policy
Danger: set-iam-policy replaces the entire IAM policy. If your policy.json is missing bindings, you will revoke access for those principals. Always edit the exported policy in place rather than constructing one from scratch, and review the diff before applying.
gcloud projects set-iam-policy PROJECT_ID policy.json
To apply at the organization level instead, swap in the org commands:
gcloud organizations get-iam-policy ORG_ID --format=json > org-policy.json
# edit org-policy.json to add the auditConfigs block
gcloud organizations set-iam-policy ORG_ID org-policy.json
Verify it took effect
gcloud projects get-iam-policy PROJECT_ID \
--format="json(auditConfigs)"
You should see allServices with all three log types listed.
Tip: If you only need to exempt specific noisy principals (for example, a high-traffic service account doing legitimate reads), use the exemptedMembers field inside an auditLogConfigs entry instead of disabling the log type entirely. You keep coverage for everyone else.
Fixing it with infrastructure as code
Doing this once by hand is fine for a single project, but configuration drift will bring the gap back. Codify it.
Terraform
resource "google_project_iam_audit_config" "all_services" {
project = var.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"
}
}
For organization-wide coverage, use google_organization_iam_audit_config with the same three audit_log_config blocks. Applying it at the org level means new projects inherit logging from day one, which is far more reliable than remembering to configure each project.
Warning: google_project_iam_audit_config is authoritative for the services it manages. If you also manage audit configs through another tool or by hand, Terraform will fight with it on every apply. Pick one source of truth.
How to prevent it from happening again
Enabling logging once is not the goal. Keeping it enabled across every project, including the ones created next month, is.
- Set the audit config at the organization level. This is the single most effective control. Child projects and folders inherit it, so a new project cannot be born without Data Access logging.
- Enforce it with an Organization Policy or custom constraint. While there is no built-in boolean constraint for audit logging, you can use a custom org policy or a policy-as-code engine to detect and block non-compliant configs.
- Add a policy-as-code gate in CI. Run a tool against your Terraform plan before it merges so a change that strips out an
audit_log_configblock fails the pipeline.
A simple Open Policy Agent / Conftest rule against a Terraform plan might look like this:
package main
deny[msg] {
resource := input.resource_changes[_]
resource.type == "google_project_iam_audit_config"
resource.change.after.service == "allServices"
required := {"ADMIN_READ", "DATA_READ", "DATA_WRITE"}
configured := {c.log_type | c := resource.change.after.audit_log_config[_]}
missing := required - configured
count(missing) > 0
msg := sprintf("audit config for allServices is missing log types: %v", [missing])
}
Tip: Pair the CI gate with continuous detection in Lensix. Pre-merge policy checks catch what flows through your pipeline, but drift, manual console changes, and resources created outside IaC still need runtime monitoring to catch them.
Best practices
- Enable all three Data Access log types for
allServicesrather than cherry-picking individual services. New services get added to your project over time, and per-service configs leave gaps. - Route audit logs to a dedicated, locked-down sink. Create an aggregated log sink at the org level that exports to a separate logging project or a BigQuery dataset with restricted access. Logs that an attacker can delete are not much use during an investigation.
- Set a retention period that matches your compliance needs. Default Cloud Logging retention for the
_Defaultbucket is 30 days. Many frameworks expect a year or more. Export to Cloud Storage for cheap long-term retention. - Manage cost with exclusions, not by disabling log types. Identify the noisiest, lowest-value Data Read sources and add exclusion filters in your log sink, keeping the underlying logging intact where it matters.
- Alert on changes to the audit config itself. A log-based alert that fires when
SetIamPolicymodifiesauditConfigstells you immediately if someone tries to disable logging.
Audit logging is one of those controls that costs you nothing until the day you need it, at which point it is priceless. Turn it on everywhere, push the configuration up to the org level, and gate it in your pipeline so it stays on. Then you will actually have answers when someone asks who did what, and when.

