GCP Logging & Detection Hardening

Overview

This page covers Google Cloud Platform logging and detection across the surfaces that decide whether an attacker who lands in the environment can move undetected. Scope is the commercial GCP regions; GCP Sovereign Cloud (formerly Assured Workloads and the Google Cloud Air-Gapped offering) inherits the same controls but exposes a different service-availability matrix for Security Command Center tiers, Cloud Logging buckets, and BigQuery sink destinations — re-verify the regional and tenancy table before applying any of the IaC below to a sovereign or air-gapped deployment. CIS sub-IDs and NIST / ISO mappings throughout this page reference the CIS Google Cloud Platform Foundation Benchmark v4.0.0 — May 2025 release (accessed 2026-05) unless explicitly annotated as a post-v4.0.0 feature or a best-practice recommendation that the current benchmark has not yet codified. The crosswalk page at compliance frameworks describes how the seven pinned framework columns relate to each other.

The GCP detective stack is the product of three layered services that each answer a different question. Cloud Logging (the current brand for the service formerly known under the deprecated legacy logging-suite name retired by Google in 2020) answers what happened on this resource — a project-scoped log router that ingests Cloud Audit Logs, application logs, and platform logs and routes them through Log Sinks to Cloud Storage, BigQuery, Pub/Sub, or another Cloud Logging bucket. Cloud Monitoring (under the same brand-consolidation that retired the prior monitoring-suite name) answers did the metric we care about just cross a threshold — the metric-and-alert layer that closes the loop from log to page; log-based metrics let any log filter become a numeric series an alert policy can fire on. Security Command Center answers what is broken across our entire estate and which findings are actively being exploited — the GCP CSPM/CWPP control plane that surfaces Security Health Analytics misconfiguration findings, Event/Container/Virtual Machine Threat Detection runtime alerts, and a regulatory-compliance dashboard. These three together provide the full record; cross-link the cross-cutting principles at General Logging — log integrity (immutable audit), centralization, retention, SIEM & detection engineering, and alerting.

Order matters. Controls 01–02 build the audit log of last resort: one org-aggregated sink with include_children = true so every project, folder, and future-project log lands in a security-team-owned destination (Cloud Storage bucket with retention lock, BigQuery dataset for SQL forensics, and a Cloud Logging bucket for retention) and one Cloud Audit Logs Data Access toggle so the ADMIN_READ, DATA_READ, and DATA_WRITE streams that are not on by default actually capture the read-of-secret, scan-of-bucket, and reconnaissance traffic an investigator needs after compromise. Controls 03–05 are the three surfaces of Security Command Center Premium — explicitly authored as three distinct controls because three distinct review rituals attach to them and conflating them under one umbrella loses pedagogical and operational value (mirrors the Microsoft Defender for Cloud three-surface treatment on the Azure logging page). Control 06 covers VPC Flow Logs for the network-layer record Cloud Audit Logs does not produce. Control 07 puts Cloud Monitoring alert policies on the canonical "things-went-wrong" signals via log-based metrics so detection actually pages a human. Control 08 ships the dedicated BigQuery audit-log sink as the forensic SQL store — paired cross-domain with the IR forensic workflow on gcp/ir.html.

Anti-conflation #1 — Security Command Center is THREE separate controls. SCC is the GCP CSPM/CWPP control plane, and the same product surface drives three distinct review workflows. (1) gcp-log-03 — Regulatory Compliance dashboard. The Compliance pane in SCC maps Security Health Analytics findings to CIS GCP v4.0.0 detector IDs and similar regulatory frameworks (PCI DSS, HIPAA, ISO 27001). Important caveat cited here in prose and again on the control body itself: Google's built-in SCC detector certification still covers CIS GCP v2.0.0 as of writing; v4.0.0 closure either via the official Google InSpec profile inspec-gcp-cis-benchmark tagged v4.0.0 release or via third-party CSPM tooling. (2) gcp-log-04 — Threat Detection umbrella. Event Threat Detection (auth anomalies, service-account key abuse, IAM anomaly findings), Container Threat Detection (runtime detections on GKE workloads — added binaries, reverse shells, malicious scripts), Virtual Machine Threat Detection (cryptomining detection on Compute Engine via guest-memory scanning), and Anomaly Detection (legacy heuristic detector). SCC Enterprise tier called out as the multi-cloud CNAPP upgrade with Mandiant threat intel plus case management — Enterprise is referenced in prose but the corpus baseline is SCC Premium per the ROADMAP success criterion that pins "Security Command Center Premium finding types". (3) gcp-log-05 — Security Health Analytics finding lifecycle. SHA findings get a monthly cadence review by the security team plus per-event automation: SCC notification → Pub/Sub topic → Cloud Function on HIGH and CRITICAL findings, with auto-remediation playbooks for the canonical "publicly exposed bucket / disabled audit log / overly-permissive role" patterns.

Anti-conflation #2 — Cloud Audit Logs has four streams with different cost and default-on properties. Admin Activity logs are on by default, are free, and cannot be disabled; they capture any API call that modifies configuration or metadata. System Event logs are also on by default, free, and not disable-able; they capture Google-initiated actions (live migration, automatic patch). Policy Denied logs are on by default with no opt-in needed; they capture IAM and VPC-SC denials. Data Access logs — split into ADMIN_READ (metadata reads), DATA_READ (data-plane reads), and DATA_WRITE (data-plane writes) — are opt-in and chargeable, off by default, and are the single largest source of forensic coverage an investigator gets or does not get after compromise. gcp-log-02 enforces enablement on regulated services with explicit awareness of cost. PITFALL: enabling Data Access on allServices with no exemption list can produce orders-of-magnitude log volume on busy services like Cloud Storage and Cloud SQL — plan the cost envelope before flipping the org-level audit config.

Anti-conflation #3 — Aggregated Sinks at org scope vs project-scoped sinks. A project-scoped sink (the default created by gcloud logging sinks create without --organization or --folder) captures only the project's own logs and does not see logs from new projects later created in the organization. The Aggregated Sink at organization scope with include_children = true (the canonical pattern in gcp-log-01) sees every project, folder, and future-project log in the organization. Aggregated sinks are not "advanced" — they are the only correct pattern at organization scope; project-scoped sinks are the wrong default for the security audit trail.

gcp-log-01-aggregated-sink ! CRITICAL DETECTIVE

Author exactly one organization-scope Aggregated Sink with include_children = true that routes all projects', folders', and future-projects' logs into a security-team-owned destination set: (a) a Cloud Storage bucket in a dedicated security project with retention_policy { is_locked = true; retention_period_seconds = 63072000 } (two years, Bucket Lock immutable — cannot be reduced even by org admin); (b) a BigQuery dataset for SQL-based analytics and detection engineering; (c) a Cloud Logging bucket in the same security project for retention (Log Analytics queries plus a separate-from-project retention bucket; _Required and _Default log buckets remain at project scope) (Google Cloud — Aggregated Sinks documentation (accessed 2026-05)). Missing include_children = true on the org-level sink, or authoring sinks at project scope instead, is the canonical "we have logging" / "no we don't" misconfiguration; CIS GCP v4.0.0 §2.1, §2.2, and §2.3 codify the requirement. This is the audit log of last resort — without it, incident response is reconstructing intent from secondary signals.

Remediation — gcloud CLI

# gcloud CLI (latest stable)
# Step 1: create the dedicated security project + log destinations.
gcloud projects create sec-logs-prod --organization=ORG_ID
gcloud config set project sec-logs-prod

# Step 2: create the Cloud Storage destination bucket with retention lock.
gcloud storage buckets create gs://org-audit-logs-sec-prod \
  --location=EU \
  --uniform-bucket-level-access \
  --public-access-prevention

# Lock retention at 2y (cannot be reduced once locked).
gcloud storage buckets update gs://org-audit-logs-sec-prod \
  --retention-period=63072000s

gcloud storage buckets lock-retention-policy gs://org-audit-logs-sec-prod

# Step 3: create the BigQuery dataset for SQL forensics.
bq --location=EU mk --dataset \
  --default_table_expiration=63072000 \
  sec-logs-prod:org_audit

# Step 4: create the org-scope Aggregated Sink with include_children.
gcloud logging sinks create org-audit-sink-storage \
  storage.googleapis.com/org-audit-logs-sec-prod \
  --organization=ORG_ID \
  --include-children \
  --description="Org-aggregated audit-log sink to immutable Cloud Storage"

gcloud logging sinks create org-audit-sink-bq \
  bigquery.googleapis.com/projects/sec-logs-prod/datasets/org_audit \
  --organization=ORG_ID \
  --include-children \
  --use-partitioned-tables

# Step 5: grant the sinks' writer identities permission on their destinations.
WRITER=$(gcloud logging sinks describe org-audit-sink-storage \
  --organization=ORG_ID --format='value(writerIdentity)')

gcloud storage buckets add-iam-policy-binding gs://org-audit-logs-sec-prod \
  --member="$WRITER" \
  --role=roles/storage.objectCreator

Remediation — Terraform

# Terraform Google provider ~> 5.0
# Source: Google Cloud docs (accessed 2026-05)
resource "google_storage_bucket" "org_audit_logs" {
  project                     = var.sec_project_id
  name                        = "org-audit-logs-sec-prod"
  location                    = "EU"
  uniform_bucket_level_access = true
  public_access_prevention    = "enforced"

  retention_policy {
    is_locked        = true
    retention_period = 63072000 # 2 years
  }

  versioning { enabled = true }
}

resource "google_bigquery_dataset" "org_audit" {
  project       = var.sec_project_id
  dataset_id    = "org_audit"
  location      = "EU"
  default_table_expiration_ms = 63072000000

  default_encryption_configuration {
    kms_key_name = var.log_bucket_kms_key
  }
}

resource "google_logging_organization_sink" "org_audit_storage" {
  name             = "org-audit-sink-storage"
  org_id           = var.org_id
  destination      = "storage.googleapis.com/${google_storage_bucket.org_audit_logs.name}"
  include_children = true
  filter           = ""
}

resource "google_logging_organization_sink" "org_audit_bq" {
  name             = "org-audit-sink-bq"
  org_id           = var.org_id
  destination      = "bigquery.googleapis.com/projects/${var.sec_project_id}/datasets/${google_bigquery_dataset.org_audit.dataset_id}"
  include_children = true
  filter           = ""

  bigquery_options {
    use_partitioned_tables = true
  }
}

resource "google_storage_bucket_iam_member" "sink_writer_storage" {
  bucket = google_storage_bucket.org_audit_logs.name
  role   = "roles/storage.objectCreator"
  member = google_logging_organization_sink.org_audit_storage.writer_identity
}

resource "google_bigquery_dataset_iam_member" "sink_writer_bq" {
  project    = var.sec_project_id
  dataset_id = google_bigquery_dataset.org_audit.dataset_id
  role       = "roles/bigquery.dataEditor"
  member     = google_logging_organization_sink.org_audit_bq.writer_identity
}

Remediation — Config Connector

apiVersion: logging.cnrm.cloud.google.com/v1beta1
kind: LoggingLogSink
metadata:
  name: org-aggregated-sink
  namespace: config-control
spec:
  resourceRef:
    apiVersion: resourcemanager.cnrm.cloud.google.com/v1beta1
    kind: Organization
    external: "organizations/ORG_ID"
  destination:
    bigQueryDatasetRef:
      external: "projects/LOG_PROJECT/datasets/security_audit"
  filter: 'logName:"cloudaudit.googleapis.com"'
  includeChildren: true

Remediation — Pulumi (TypeScript)

import * as gcp from "@pulumi/gcp";

// Org-level aggregated log sink to BigQuery for org-wide audit visibility.
const orgSink = new gcp.logging.OrganizationSink("org-aggregated-sink", {
    name: "org-aggregated-sink",
    orgId: orgId,
    destination: pulumi.interpolate`bigquery.googleapis.com/projects/${logProject}/datasets/security_audit`,
    filter: 'logName:"cloudaudit.googleapis.com"',
    includeChildren: true,
});

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/a2.1; 2.2; 2.3n/a AU-2; AU-3; AU-6; AU-9A.8.15; A.5.28CLD.12.4.5

Log signals

  • Cloud Audit Logs on logging.googleapis.com for ConfigServiceV2.UpdateSink or DeleteSink where the sink scope is the organisation aggregated audit sink (filter pattern includes logName=~"organizations/.*/logs/cloudaudit.googleapis.com.*").
  • Sink-IAM mutations that remove the roles/storage.objectCreator or roles/bigquery.dataEditor binding on the sink's writer-identity — silently breaks ingestion without altering sink config.
  • Aggregated-sink destination resource deletes (Cloud Storage bucket, BigQuery dataset, Pub/Sub topic) — log ingestion fails fast on the next batch flush.

Query

logName=~"organizations/.*/logs/cloudaudit.googleapis.com%2Factivity"
          AND protoPayload.serviceName="logging.googleapis.com"
          AND (protoPayload.methodName="google.logging.v2.ConfigServiceV2.UpdateSink"
               OR protoPayload.methodName="google.logging.v2.ConfigServiceV2.DeleteSink")
          AND resource.type="logging_sink"

Schedule this Cloud Logging query as a saved view at organisation scope; subscribe a Cloud Functions handler via Pub/Sub log-router topic so sink-config mutations notify the on-call channel within seconds, independent of the very pipeline being mutated.

Alert threshold

  • Page immediately on any update or delete of the aggregated organisation audit sink; the sink is a meta-control and its mutation chain almost always precedes a forensic-blackout incident.
  • Page on any IAM mutation against the sink's writer identity that removes the destination-write role.

Initial response

  1. Restore the sink from Cloud Asset Inventory history via gcloud logging sinks update --organization=ORG_ID with the captured filter and destination; re-bind the writer-identity role on the destination resource.
  2. Replay the gap window from the sink-side replica: BigQuery dataset has per-row ingestion-timestamp; Cloud Storage bucket has per-object commit-time — reconstruct the gap window and document a forensic blackout if either record set is incomplete.
  3. Add a Cloud Asset Inventory feed on logging.googleapis.com/Sink so future sink mutations also fire an out-of-band Pub/Sub event, breaking the pipeline-mutates-its-own-detector reflexive failure mode.

References

Equivalent on: AWS · Azure · OCI

gcp-log-02-data-access-logs ! HIGH DETECTIVE

Enable Cloud Audit Logs Data Access logging — ADMIN_READ, DATA_READ, and DATA_WRITE — at organization scope via an organization-level Audit Config on allServices (with an explicit exemption list for service accounts whose read volume would make the cost envelope untenable) or, where cost forbids the umbrella, on the regulated-data services explicitly: Cloud Storage, BigQuery, Cloud SQL, Secret Manager, Cloud KMS, and the IAM service itself (iam.googleapis.com) — the latter is particularly important because IAMPolicy Data Access logs capture the policy-read calls an attacker uses to enumerate IAM bindings before privilege escalation (Google Cloud — Configure Data Access audit logs (accessed 2026-05)). Data Access logs are off by default and are chargeable — that is precisely the reason they get missed in audit-trail design, and precisely the reason they must be enabled before an incident. Without them, an attacker who has obtained read access to a Cloud Storage bucket of secrets, a BigQuery dataset of customer records, or a Secret Manager version can perform the read with no log entry visible to the investigator after the fact.

Remediation — gcloud CLI

# gcloud CLI (latest stable)
# Step 1: fetch the current organization IAM policy.
gcloud organizations get-iam-policy ORG_ID --format=json > org-policy.json

# Step 2: edit the auditConfigs block to enable the three Data Access streams.
# Append (or merge with) this object inside auditConfigs:
cat >> org-policy.json.fragment <<'JSON'
{
  "auditConfigs": [
    {
      "service": "allServices",
      "auditLogConfigs": [
        { "logType": "ADMIN_READ" },
        { "logType": "DATA_READ",
          "exemptedMembers": [
            "serviceAccount:high-volume-batch@PROJECT.iam.gserviceaccount.com"
          ]
        },
        { "logType": "DATA_WRITE" }
      ]
    },
    {
      "service": "iam.googleapis.com",
      "auditLogConfigs": [
        { "logType": "ADMIN_READ" },
        { "logType": "DATA_READ" },
        { "logType": "DATA_WRITE" }
      ]
    }
  ]
}
JSON

# Step 3: apply the merged policy back.
gcloud organizations set-iam-policy ORG_ID org-policy.json

# Step 4: verify Data Access is now on for the regulated surface.
gcloud organizations get-iam-policy ORG_ID \
  --format='json(auditConfigs)'

Remediation — Terraform

# Terraform Google provider ~> 5.0
# Source: Google Cloud docs (accessed 2026-05)
resource "google_organization_iam_audit_config" "all_services" {
  org_id  = var.org_id
  service = "allServices"

  audit_log_config { log_type = "ADMIN_READ" }

  audit_log_config {
    log_type = "DATA_READ"
    exempted_members = [
      "serviceAccount:high-volume-batch@${var.batch_project_id}.iam.gserviceaccount.com",
    ]
  }

  audit_log_config { log_type = "DATA_WRITE" }
}

# IAMPolicy Data Access is particularly important for reconnaissance detection.
resource "google_organization_iam_audit_config" "iam_service" {
  org_id  = var.org_id
  service = "iam.googleapis.com"

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

resource "google_organization_iam_audit_config" "crm_service" {
  org_id  = var.org_id
  service = "cloudresourcemanager.googleapis.com"

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

Remediation — Infrastructure Manager

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/a2.x (verify)n/a AU-2; AU-12A.8.15CLD.12.4.5

Log signals

  • Cloud Audit Logs on SetIamPolicy against the audit-log config resource where auditConfigs.exemptedMembers is widened or logType=DATA_READ is removed for a sensitive service (e.g. storage.googleapis.com, bigquery.googleapis.com).
  • Project-level audit-log config changes where auditConfigs.service goes from allServices to a narrower list — silent reduction of data-access coverage.
  • Volume-based: rolling 24h data-access log line count drops more than 50% versus the 14-day baseline.

Query

logName=~"projects/.*/logs/cloudaudit.googleapis.com%2Factivity"
          AND protoPayload.methodName="SetIamPolicy"
          AND protoPayload.serviceData.policyDelta.auditConfigDeltas.action="REMOVE"
          AND (protoPayload.serviceData.policyDelta.auditConfigDeltas.logType="DATA_READ"
               OR protoPayload.serviceData.policyDelta.auditConfigDeltas.logType="DATA_WRITE")

This Cloud Logging filter captures the audit-config deltas; pair with a log-based metric on per-service DATA_READ entry rate so coverage erosion shows up as a graph regression in Cloud Monitoring even if the SetIamPolicy event is missed.

Alert threshold

  • Page on any removal of DATA_READ or DATA_WRITE log-type from allServices or from any of the documented sensitive services.
  • Page on any addition to exemptedMembers — exemptions are explicit forensic blind spots and additions need ticket cover.

Initial response

  1. Restore the audit-log config via gcloud projects set-iam-policy with the captured baseline JSON; verify the next data-access call surfaces in the sink.
  2. Audit data-plane access during the coverage gap: pull BigQuery/Cloud Storage object access logs where they remain in the dataset, and flag any access pattern by the principal who issued the SetIamPolicy as candidate-cover-up.
  3. Pin the audit-log config in source control (Terraform google_project_iam_audit_config) and gate edits through a manual approval; document the runbook change in the incident timeline.

References

Equivalent on: AWS · Azure · OCI

gcp-log-03-scc-premium ! HIGH DETECTIVE

Security Command Center Premium — Regulatory Compliance dashboard

Activate Security Command Center Premium at organization scope and enable the Regulatory Compliance dashboard, which maps Security Health Analytics findings to CIS GCP v4.0.0 detector IDs plus PCI DSS, HIPAA, and ISO 27001 control sets (Google Cloud — Security Command Center overview (accessed 2026-05)). Critical caveat to cite explicitly in any compliance attestation that references SCC built-in detector results: as of writing, Google's SCC built-in compliance detector certification still covers CIS GCP v2.0.0; v4.0.0 detector parity is closed externally — either via the official Google InSpec profile inspec-gcp-cis-benchmark tagged v4.0.0 release (GoogleCloudPlatform — inspec-gcp-cis-benchmark v4.0.0 (accessed 2026-05)) or via third-party CSPM tooling. The dashboard remains useful for the v2.0.0 baseline plus the per-finding remediation playbooks, and the dashboard's PCI / HIPAA / ISO mappings are not affected by the CIS-version lag. This control is the compliance-posture surface of SCC; gcp-log-04 is the threat-detection surface and gcp-log-05 is the SHA finding-lifecycle surface — three separate review workflows on the same SCC engine.

Remediation — gcloud CLI

# gcloud CLI (latest stable)
# Step 1: enable SCC Premium services at organization scope.
gcloud scc settings services update \
  --service=SECURITY_HEALTH_ANALYTICS \
  --enablement-state=ENABLED \
  --organization=ORG_ID

# Step 2: enable Web Security Scanner (Premium-only managed module).
gcloud scc settings services update \
  --service=WEB_SECURITY_SCANNER \
  --enablement-state=ENABLED \
  --organization=ORG_ID

# Step 3: confirm SCC tier (Premium) and SHA module activation.
gcloud scc settings describe --organization=ORG_ID

gcloud scc settings services describe SECURITY_HEALTH_ANALYTICS \
  --organization=ORG_ID

# Step 4: pull the v4.0.0 InSpec profile for the CIS gap detector set
# (closes the SCC-v2.0.0-certified-only caveat).
git clone --branch v4.0.0 \
  https://github.com/GoogleCloudPlatform/inspec-gcp-cis-benchmark.git
inspec exec inspec-gcp-cis-benchmark \
  -t gcp:// \
  --input gcp_project_id=PROJECT_ID \
         gcp_organization_id=ORG_ID

Remediation — Terraform

# Terraform Google provider ~> 5.0
# Source: Google Cloud docs (accessed 2026-05)
# Note: SCC organization-tier activation is primarily console / gcloud at
# writing time; Terraform coverage of org-level service-enablement is
# partial — re-verify against the google + google-beta providers at apply.
# Custom-module authoring is supported and shown here.

resource "google_scc_organization_custom_module" "deny_public_bucket" {
  organization = var.org_id
  display_name = "deny-public-storage-bucket"
  enablement_state = "ENABLED"

  custom_config {
    predicate {
      expression = "resource.iamPolicy.bindings.exists(b, b.members.exists(m, m == 'allUsers' || m == 'allAuthenticatedUsers'))"
    }
    resource_selector {
      resource_types = ["storage.googleapis.com/Bucket"]
    }
    severity      = "HIGH"
    description   = "Cloud Storage bucket IAM grants allUsers/allAuthenticatedUsers"
    recommendation = "Remove allUsers/allAuthenticatedUsers bindings; enforce public_access_prevention=enforced"
  }
}

# Pin the Compliance source so dashboard IDs are referenceable from runbooks.
data "google_scc_source" "compliance" {
  organization = var.org_id
  display_name = "Security Health Analytics"
}

Remediation — Infrastructure Manager

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/a(best-practices)n/a CM-8; CM-3A.8.9CLD.12.4.5

Log signals

  • Cloud Audit Logs on securitycenter.googleapis.com for UpdateOrganizationSettings calls where enableAssetDiscovery flips to false or service-tier downgrades from PREMIUM to STANDARD.
  • SCC source mutes via MuteConfig.create with overly broad filter expressions (e.g. category="*").
  • Drop in finding-creation rate against rolling baseline — SCC finding rate is a leading indicator of detector enablement state.

Query

logName=~"organizations/.*/logs/cloudaudit.googleapis.com%2Factivity"
          AND protoPayload.serviceName="securitycenter.googleapis.com"
          AND (protoPayload.methodName=~".*UpdateOrganizationSettings"
               OR protoPayload.methodName=~".*CreateMuteConfig"
               OR protoPayload.methodName=~".*UpdateMuteConfig")

Pin this Cloud Logging filter to a Cloud Monitoring log-based metric grouped by organisation; pair with a saved query on the SCC finding-creation rate (resource.type="organization") so tier downgrades and detector silence show together in one alert pane.

Alert threshold

  • Page on any tier downgrade from Premium/Enterprise; the tier change disables a large fraction of SCC's managed detectors.
  • Page on any new MuteConfig whose filter expression matches more than 10 finding categories, or on rolling 1h finding-rate dropping below 30% of the 30-day baseline.

Initial response

  1. Restore the organisation settings via gcloud scc settings services modules update from the captured baseline; reverse any new MuteConfig and document the gap-window category coverage.
  2. Re-run a one-shot SCC asset discovery to backfill the inventory; treat any finding raised in the catch-up sweep as if it were created during the muted window for triage purposes.
  3. Pin SCC org settings in source control and gate edits through change-management; the SCC config is a meta-detector and warrants the same friction as the sink config.

References

Equivalent on: AWS · Azure · OCI

gcp-log-04-scc-premium ! CRITICAL DETECTIVE

Security Command Center Premium — Threat Detection

Enable the four threat-detection services that ship with Security Command Center Premium at organization scope: Event Threat Detection (authentication anomalies, service-account key abuse, anomalous IAM grants, suspicious cross-project access, brute-force SSH against Compute Engine), Container Threat Detection (runtime detections on GKE workloads — added binaries inside containers, reverse shells, malicious scripts, Linux Bash root histories), Virtual Machine Threat Detection (cryptomining detection on Compute Engine via guest-memory scanning, no agent required), and the legacy Anomaly Detection heuristic engine (Google Cloud — Event Threat Detection overview (accessed 2026-05)). Anti-conflation: this is the threat-detection surface of SCC; the compliance dashboard is gcp-log-03 and the SHA finding lifecycle is gcp-log-05. SCC Enterprise tier is called out as the multi-cloud CNAPP upgrade — it adds Mandiant threat-intelligence feeds, multi-cloud asset inventory (covering AWS / Azure), and a SOAR-style case-management surface — and is the right tier for organisations running a multi-cloud SOC. The corpus baseline is SCC Premium per the ROADMAP success criterion that explicitly pins "Security Command Center Premium finding types"; Enterprise is referenced in prose, not as the primary recommendation. CRITICAL severity because threat detection is the only signal that runtime compromise is currently in progress (versus posture findings, which describe latent risk).

Remediation — gcloud CLI

# gcloud CLI (latest stable)
# Step 1: enable the four threat-detection services at organization scope.
for svc in EVENT_THREAT_DETECTION \
           CONTAINER_THREAT_DETECTION \
           VIRTUAL_MACHINE_THREAT_DETECTION \
           SECURITY_HEALTH_ANALYTICS; do
  gcloud scc settings services update \
    --service="$svc" \
    --enablement-state=ENABLED \
    --organization=ORG_ID
done

# Step 2: confirm activation per-service.
for svc in EVENT_THREAT_DETECTION \
           CONTAINER_THREAT_DETECTION \
           VIRTUAL_MACHINE_THREAT_DETECTION; do
  gcloud scc settings services describe "$svc" \
    --organization=ORG_ID \
    --format='value(serviceEnablementState)'
done

# Step 3: list active threat-detection findings across the org.
gcloud scc findings list ORG_ID \
  --filter='category=~"EXFIL|CRYPTOMINING|MALWARE|SUSPICIOUS|BACKDOOR"
            AND state="ACTIVE"' \
  --format='table(name, category, severity, eventTime)'

Remediation — Terraform

# Terraform Google provider ~> 5.0
# Source: Google Cloud docs (accessed 2026-05)
# Note: org-tier service-enablement for SCC Premium threat-detection services
# is partially supported in google_scc_organization_custom_module form;
# the canonical activation path is gcloud / console. Re-verify support level
# in the google + google-beta providers at apply time. The custom-module
# pattern below shows the supported Terraform-native surface (organisation
# custom detectors that augment the built-in threat-detection set).

resource "google_scc_organization_custom_module" "anomalous_sa_key_create" {
  organization     = var.org_id
  display_name     = "anomalous-sa-key-creation"
  enablement_state = "ENABLED"

  custom_config {
    predicate {
      expression = "resource.type == 'iam.googleapis.com/ServiceAccountKey' && resource.createTime > timestamp(now - duration('1h'))"
    }
    resource_selector {
      resource_types = ["iam.googleapis.com/ServiceAccountKey"]
    }
    severity      = "HIGH"
    description   = "New service account key created — possible credential persistence"
    recommendation = "Audit the creating principal; rotate or delete the key if not expected"
  }
}

# Route threat-detection findings into Pub/Sub for downstream automation
# (the Pub/Sub topic + Cloud Function pair is in gcp-log-05).
resource "google_scc_notification_config" "threat_high_critical" {
  config_id    = "threat-detection-high-critical"
  organization = var.org_id
  description  = "HIGH+CRITICAL threat-detection findings → SOC playbook"
  pubsub_topic = google_pubsub_topic.scc_findings.id

  streaming_config {
    filter = "severity = \"HIGH\" OR severity = \"CRITICAL\""
  }
}

Remediation — Infrastructure Manager

Remediation — Pulumi (TypeScript)

import * as gcp from "@pulumi/gcp";

// SCC organisation custom module — SCC Premium tier required.
const customModule = new gcp.securitycenter.OrganizationCustomModule("scc-custom", {
    organization: orgId,
    displayName: "scc-prod-baseline",
    enablementState: "ENABLED",
    customConfig: {
        predicate: {
            expression: "resource.publicAccessPrevention != 'enforced'",
        },
        recommendation: "Set publicAccessPrevention=enforced on every bucket.",
        description: "Detect storage buckets without PAP enforcement.",
        severity: "HIGH",
        resourceSelector: {
            resourceTypes: ["storage.googleapis.com/Bucket"],
        },
    },
});

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/a(best-practices)n/a SI-4; SI-4(2); SI-4(4)A.8.16CLD.12.4.5

Log signals

  • Cloud Audit Logs on securitycenter.googleapis.com for UpdateSecurityHealthAnalyticsCustomModule or DeleteSecurityHealthAnalyticsCustomModule on custom-detector definitions.
  • Event Threat Detection module enablement flips: UpdateModule where moduleEnablementState transitions from ENABLED to DISABLED on detectors such as iam_anomalous_grant or persistence_iam_anomalous_grant.
  • Container Threat Detection disable on a GKE cluster — pairs with k8s-06 drift and warrants joined alerting.

Query

logName=~"organizations/.*/logs/cloudaudit.googleapis.com%2Factivity"
          AND protoPayload.serviceName="securitycenter.googleapis.com"
          AND (protoPayload.methodName=~".*Module.*"
               OR protoPayload.methodName=~".*EventThreatDetection.*")
          AND (protoPayload.request.module.moduleEnablementState="DISABLED"
               OR protoPayload.methodName=~".*Delete.*")

Run this Cloud Logging filter against the SCC organisation-scope audit feed; the matching documents capture both detector enable-state and detector definition deletes, both of which silently shrink the detection surface.

Alert threshold

  • Page on any detector module disable for Event Threat Detection or Container Threat Detection; per-detector disable should match a documented exception ticket.
  • Page on any delete of a custom Security Health Analytics module — these are bespoke detectors and their removal warrants explicit acknowledgement.

Initial response

  1. Re-enable the affected module via gcloud scc settings services modules update --enablement-state=ENABLED and re-import any deleted custom module from the captured baseline.
  2. Force a re-scan of affected resource scope (organisation or folder) and triage every backfilled finding from the disabled window — they would have been created during the gap and are now visible only after re-enable.
  3. Document the gap-window timeline in the incident record and review the principal who issued the disable for additional SCC-config calls in the surrounding hours.

References

Equivalent on: AWS · Azure · OCI

gcp-log-05-scc-premium ! HIGH DETECTIVE

Security Command Center Premium — Security Health Analytics finding lifecycle

Treat Security Health Analytics (SHA) findings as a managed work queue with a defined cadence and per-event automation: monthly review of all OPEN findings across HIGH and CRITICAL severities at the security-team standup, and a Pub/Sub notification → Cloud Function (Gen 2) playbook chain on every new HIGH or CRITICAL finding so the urgent ones do not wait for the monthly review (Google Cloud — SCC notification configs documentation (accessed 2026-05)). Anti-conflation: this is the lifecycle / recommendation-management surface of SCC; the compliance dashboard is gcp-log-03 and the live threat-detection surface is gcp-log-04. The three controls describe three review rituals on the same SCC engine — monthly posture review (this control), continuous threat triage (gcp-log-04), and compliance reporting (gcp-log-03) — which is why folding them into one umbrella loses the operational signal. The auto-remediation Cloud Function carries playbooks for the canonical SHA categories: PUBLIC_BUCKET_ACL → set bucket public_access_prevention = "enforced"; OPEN_FIREWALL → narrow source ranges; SERVICE_ACCOUNT_KEY_NOT_ROTATED → enqueue rotation reminder; WEAK_SSL_POLICY → swap load-balancer SSL policy. Posture Management within SCC is referenced as the opinionated baseline (re-verify GA versus preview status at writing time).

Remediation — gcloud CLI

# gcloud CLI (latest stable)
# Step 1: create the Pub/Sub topic that receives SCC notifications.
gcloud pubsub topics create scc-findings-high-critical \
  --project=sec-logs-prod

# Step 2: register the SCC notification config (HIGH + CRITICAL severities).
gcloud scc notifications create scc-high-critical \
  --organization=ORG_ID \
  --description="HIGH + CRITICAL SHA findings → automated remediation" \
  --pubsub-topic=projects/sec-logs-prod/topics/scc-findings-high-critical \
  --filter='severity = "HIGH" OR severity = "CRITICAL"'

# Step 3: deploy the remediation Cloud Function (Gen 2).
gcloud functions deploy scc-remediate \
  --gen2 \
  --region=europe-west1 \
  --runtime=python312 \
  --source=./scc-remediate \
  --entry-point=handle_finding \
  --trigger-topic=scc-findings-high-critical \
  --service-account=scc-remediator@sec-logs-prod.iam.gserviceaccount.com \
  --max-instances=10

# Step 4: list current OPEN HIGH+CRITICAL findings (monthly review query).
gcloud scc findings list ORG_ID \
  --filter='state="ACTIVE" AND (severity="HIGH" OR severity="CRITICAL")' \
  --format='table(name.basename(), category, severity, resourceName, eventTime)'

Remediation — Terraform

# Terraform Google provider ~> 5.0
# Source: Google Cloud docs (accessed 2026-05)
resource "google_pubsub_topic" "scc_findings" {
  project = var.sec_project_id
  name    = "scc-findings-high-critical"
}

resource "google_scc_notification_config" "high_critical" {
  config_id    = "scc-high-critical"
  organization = var.org_id
  description  = "HIGH+CRITICAL SHA + threat-detection findings"
  pubsub_topic = google_pubsub_topic.scc_findings.id

  streaming_config {
    filter = "severity = \"HIGH\" OR severity = \"CRITICAL\""
  }
}

resource "google_service_account" "scc_remediator" {
  project      = var.sec_project_id
  account_id   = "scc-remediator"
  display_name = "SCC auto-remediation function identity"
}

resource "google_cloudfunctions2_function" "scc_remediate" {
  project  = var.sec_project_id
  name     = "scc-remediate"
  location = "europe-west1"

  build_config {
    runtime     = "python312"
    entry_point = "handle_finding"
    source {
      storage_source {
        bucket = var.function_source_bucket
        object = "scc-remediate.zip"
      }
    }
  }

  service_config {
    max_instance_count = 10
    available_memory   = "512M"
    service_account_email = google_service_account.scc_remediator.email
    ingress_settings   = "ALLOW_INTERNAL_ONLY"
  }

  event_trigger {
    trigger_region = "europe-west1"
    event_type     = "google.cloud.pubsub.topic.v1.messagePublished"
    pubsub_topic   = google_pubsub_topic.scc_findings.id
    retry_policy   = "RETRY_POLICY_RETRY"
  }
}

Remediation — Infrastructure Manager

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/a(best-practices)n/a CA-7; SI-4A.8.16; A.5.36CLD.12.4.5

Log signals

  • Cloud Audit Logs on securitycenter.googleapis.com for NotificationConfig.create, NotificationConfig.delete, or filter mutation on the Pub/Sub fanout config that ships findings to the SOC.
  • Cloud Pub/Sub admin API events: pubsub.googleapis.com topics.delete or subscriptions.delete on the SCC findings topic / subscription pair.
  • Subscription IAM mutations removing the SCC service identity's roles/pubsub.publisher binding — silently breaks the fanout without deleting the resource.

Query

(logName=~"organizations/.*/logs/cloudaudit.googleapis.com%2Factivity"
           AND protoPayload.serviceName="securitycenter.googleapis.com"
           AND protoPayload.methodName=~".*NotificationConfig.*")
          OR
          (logName=~"projects/.*/logs/cloudaudit.googleapis.com%2Factivity"
           AND protoPayload.serviceName="pubsub.googleapis.com"
           AND protoPayload.methodName=~"google.pubsub.v1.(Publisher.DeleteTopic|Subscriber.DeleteSubscription)"
           AND protoPayload.resourceName=~".*scc-findings.*")

Two-leg Cloud Logging filter joining SCC notification-config mutations with Pub/Sub admin-API deletes; pair with a Pub/Sub publish-rate metric on the findings topic so an absence-of-publish signal fires even when no admin-API mutation is visible.

Alert threshold

  • Page on any NotificationConfig delete or filter-shrink (filter removing a high-severity category).
  • Page when the SCC findings Pub/Sub topic publish-rate drops below 10% of rolling-baseline for 30 minutes.

Initial response

  1. Recreate the NotificationConfig via gcloud scc notifications create from the captured baseline; restore Pub/Sub topic and subscription if deleted, and re-bind IAM for the SCC service identity.
  2. Manually push backlogged findings from SCC into the SOC by exporting via gcloud scc findings list --filter='state="ACTIVE"' and ingesting through the SIEM's batch loader to bridge the gap window.
  3. Add a dead-letter topic with low-latency alerting so future fanout failures surface before the queue silently drains.

References

Equivalent on: AWS · Azure · OCI

gcp-log-06-vpc-flow-logs ! MEDIUM DETECTIVE

Enable VPC Flow Logs on every subnet that hosts workloads, with flow_sampling = 1.0 on security-critical subnets (administrative, regulated-data, internet-facing) and flow_sampling = 0.5 on high-volume application subnets where 100% capture is cost-prohibitive; aggregation interval at INTERVAL_5_SEC for the security-sensitive set and at INTERVAL_30_SEC for the rest; metadata INCLUDE_ALL_METADATA so the flow record carries source-instance, source-pod (where Dataplane V2 applies), and target-service annotations the investigator needs (Google Cloud — VPC Flow Logs documentation (accessed 2026-05)). Flow logs land in Cloud Logging and ride the aggregated sink to BigQuery for SQL-based investigation. Packet Mirroring (configured via google_compute_packet_mirroring) is the IDS-grade companion when full L7 inspection is required — Flow Logs are connection-summary records, not packet captures; Packet Mirroring replicates full packets to a destination ILB for inspection by a network IDS appliance (Suricata, Zeek, or a vendor virtual-appliance). The pair-control on the network page (gcp-net-08 cross-domain) handles egress-IP control; this control handles the per-flow record after the flow has been allowed.

Remediation — gcloud CLI

# gcloud CLI (latest stable)
# Step 1: enable Flow Logs on a security-critical subnet (100% sampling).
gcloud compute networks subnets update snet-app-prod-euw1 \
  --project=svc-app-prod \
  --region=europe-west1 \
  --enable-flow-logs \
  --logging-aggregation-interval=INTERVAL_5_SEC \
  --logging-flow-sampling=1.0 \
  --logging-metadata=INCLUDE_ALL_METADATA

# Step 2: enable Flow Logs on a high-volume application subnet (50% sampling).
gcloud compute networks subnets update snet-app-edge-euw1 \
  --project=svc-app-prod \
  --region=europe-west1 \
  --enable-flow-logs \
  --logging-aggregation-interval=INTERVAL_30_SEC \
  --logging-flow-sampling=0.5 \
  --logging-metadata=INCLUDE_ALL_METADATA

# Step 3: verify Flow Logs config across all subnets in the project.
gcloud compute networks subnets list --project=svc-app-prod \
  --format='table(name, region, logConfig.enable, logConfig.flowSampling)'

Remediation — Terraform

# Terraform Google provider ~> 5.0
# Source: Google Cloud docs (accessed 2026-05)
resource "google_compute_subnetwork" "snet_app_prod_euw1" {
  project       = var.host_project_id
  name          = "snet-app-prod-euw1"
  network       = var.vpc_self_link
  region        = "europe-west1"
  ip_cidr_range = "10.40.0.0/22"

  private_ip_google_access = true

  log_config {
    aggregation_interval = "INTERVAL_5_SEC"
    flow_sampling        = 1.0
    metadata             = "INCLUDE_ALL_METADATA"
  }
}

# Packet Mirroring for IDS-grade inspection on the security-critical subnet.
resource "google_compute_packet_mirroring" "ids_mirror" {
  project = var.host_project_id
  name    = "ids-mirror-app-prod"
  region  = "europe-west1"

  network {
    url = var.vpc_self_link
  }

  collector_ilb {
    url = var.ids_collector_ilb_self_link
  }

  mirrored_resources {
    subnetworks {
      url = google_compute_subnetwork.snet_app_prod_euw1.self_link
    }
  }
}

Remediation — Config Connector

apiVersion: compute.cnrm.cloud.google.com/v1beta1
kind: ComputeSubnetwork
metadata:
  name: prod-subnet-flow-logs
  namespace: config-control
spec:
  region: us-central1
  ipCidrRange: "10.0.0.0/20"
  networkRef:
    name: prod-vpc
  logConfig:
    aggregationInterval: INTERVAL_5_SEC
    flowSampling: 0.5
    metadata: INCLUDE_ALL_METADATA

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/a3.8; 3.9n/a AU-2; AU-12; SC-7A.8.15; A.8.16CLD.12.4.5

Log signals

  • Cloud Audit Logs on compute.googleapis.com for subnetworks.patch where logConfig.enable transitions from true to false.
  • Subnet flow-log aggregationInterval widened beyond 5-second granularity, or flowSampling reduced toward zero — both silently lower forensic resolution.
  • Per-subnet flow-log entry rate dropping below rolling baseline on a subnet whose VM count is unchanged.

Query

logName=~"projects/.*/logs/cloudaudit.googleapis.com%2Factivity"
          AND protoPayload.serviceName="compute.googleapis.com"
          AND protoPayload.methodName="v1.compute.subnetworks.patch"
          AND (protoPayload.request.logConfig.enable=false
               OR protoPayload.request.logConfig.flowSampling<0.5)

Stream this Cloud Logging filter into Cloud Monitoring as a log-based metric grouped by subnet name; pair with a flow-log ingestion-rate query (resource.type="gce_subnetwork" with log_id("compute.googleapis.com/vpc_flows")) so config drift and the resulting observable drop appear together.

Alert threshold

  • Page on any subnet flow-log disable on production VPCs.
  • Page on per-subnet flow-log entry rate dropping below 20% of the 7-day baseline for more than 15 minutes while the subnet's VM count is unchanged.

Initial response

  1. Re-enable flow logging via gcloud compute networks subnets update --enable-flow-logs --logging-flow-sampling=1.0 on the affected subnet; verify the next 5-minute window shows ingestion resumed.
  2. Reconstruct flow data for the gap window from the closest available source: load-balancer logs, firewall-rule logs, or downstream Cloud NAT logs. Document any flow that cannot be reconstructed as a forensic blind spot.
  3. Pin subnet flow-log config in Terraform and gate edits through a manual approval; subnet config edits are infrequent and the rate-limit signal is high-value.

References

Equivalent on: AWS · Azure · OCI

gcp-log-07-log-based-metrics ! HIGH DETECTIVE

Author log-based metrics and Cloud Monitoring alert policies on the canonical security-event log filters from CIS GCP v4.0.0 §2.4–§2.16: IAM policy changes (protoPayload.methodName=~"SetIamPolicy"), log sink modifications, VPC firewall changes, VPC network changes, VPC route changes, Cloud KMS CryptoKey changes / destruction, project ownership changes, Audit Config changes, Cloud SQL instance modifications, Cloud Storage IAM policy changes, custom-role definition changes (Google Cloud — Log-based metrics documentation (accessed 2026-05)). Each metric is paired with a Cloud Monitoring alert policy with a 1-occurrence threshold over a 5-minute window — the value is in fast detection of the configuration change, not in counting how many times it happened. The Terraform pattern uses for_each over a locals map of canonical event filters, mirroring the AWS Phase 6 aws-log-07 CloudWatch-alarms-for_each pattern and the Azure Phase 7 azure-log-07 alert-rule pattern — extensible without per-alarm boilerplate.

Remediation — gcloud CLI

# gcloud CLI (latest stable)
# Step 1: create a log-based metric on IAM policy changes (CIS GCP 2.4).
gcloud logging metrics create iam_policy_changes \
  --project=sec-logs-prod \
  --description='IAM policy changes (CIS GCP 2.4)' \
  --log-filter='protoPayload.methodName="SetIamPolicy"
                OR protoPayload.methodName=~"SetIamPolicy$"'

# Step 2: create a log-based metric on log sink modifications (CIS GCP 2.5).
gcloud logging metrics create logging_sink_changes \
  --project=sec-logs-prod \
  --description='Log sink modifications (CIS GCP 2.5)' \
  --log-filter='protoPayload.methodName=~"logging.sinks.(create|update|delete)$"'

# Step 3: create a log-based metric on VPC firewall changes (CIS GCP 3.7 alert).
gcloud logging metrics create vpc_firewall_changes \
  --project=sec-logs-prod \
  --description='VPC firewall rule changes (CIS GCP 2.7)' \
  --log-filter='resource.type="gce_firewall_rule"
                AND (protoPayload.methodName=~"firewalls.(insert|patch|delete)$")'

# Step 4: bind a Cloud Monitoring alert policy to each metric.
# (Policy authoring shown in the Terraform block below for the canonical set.)

Remediation — Terraform

# Terraform Google provider ~> 5.0
# Source: Google Cloud docs (accessed 2026-05)
locals {
  canonical_events = {
    iam_policy_changes = {
      filter = "protoPayload.methodName=~\"SetIamPolicy$\""
      cis    = "2.4"
    }
    logging_sink_changes = {
      filter = "protoPayload.methodName=~\"logging.sinks.(create|update|delete)$\""
      cis    = "2.5"
    }
    audit_config_changes = {
      filter = "protoPayload.methodName=\"SetIamPolicy\" AND protoPayload.serviceData.policyDelta.auditConfigDeltas:*"
      cis    = "2.6"
    }
    vpc_firewall_changes = {
      filter = "resource.type=\"gce_firewall_rule\" AND protoPayload.methodName=~\"firewalls.(insert|patch|delete)$\""
      cis    = "2.7"
    }
    vpc_network_changes = {
      filter = "resource.type=\"gce_network\" AND protoPayload.methodName=~\"networks.(insert|patch|delete)$\""
      cis    = "2.8"
    }
    vpc_route_changes = {
      filter = "resource.type=\"gce_route\" AND protoPayload.methodName=~\"routes.(insert|delete)$\""
      cis    = "2.9"
    }
    storage_iam_changes = {
      filter = "resource.type=\"gcs_bucket\" AND protoPayload.methodName=\"storage.setIamPermissions\""
      cis    = "2.10"
    }
    sql_instance_changes = {
      filter = "protoPayload.methodName=~\"cloudsql.instances.(create|update|delete)$\""
      cis    = "2.11"
    }
    kms_key_destruction = {
      filter = "resource.type=\"cloudkms_cryptokey\" AND protoPayload.methodName=~\"DestroyCryptoKeyVersion$\""
      cis    = "2.12"
    }
    project_ownership_changes = {
      filter = "(protoPayload.serviceName=\"cloudresourcemanager.googleapis.com\") AND (ProjectOwnership OR projectOwnerInvitee)"
      cis    = "2.13"
    }
    custom_role_changes = {
      filter = "resource.type=\"iam_role\" AND protoPayload.methodName=~\"google.iam.admin.v1.(Create|Update|Delete)Role$\""
      cis    = "2.14"
    }
  }
}

resource "google_logging_metric" "canonical_events" {
  for_each = local.canonical_events
  project  = var.sec_project_id
  name     = each.key
  filter   = each.value.filter

  metric_descriptor {
    metric_kind = "DELTA"
    value_type  = "INT64"
    unit        = "1"
  }

  description = "CIS GCP v4.0.0 §${each.value.cis} canonical event"
}

resource "google_monitoring_alert_policy" "canonical_events" {
  for_each     = local.canonical_events
  project      = var.sec_project_id
  display_name = "alert-${each.key}"
  combiner     = "OR"
  notification_channels = var.notification_channel_ids

  conditions {
    display_name = "occurrence threshold"
    condition_threshold {
      filter          = "metric.type=\"logging.googleapis.com/user/${each.key}\" AND resource.type=\"global\""
      duration        = "0s"
      comparison      = "COMPARISON_GT"
      threshold_value = 0

      aggregations {
        alignment_period   = "300s"
        per_series_aligner = "ALIGN_DELTA"
      }
    }
  }

  alert_strategy {
    auto_close = "1800s"
  }
}

Remediation — Config Connector

apiVersion: logging.cnrm.cloud.google.com/v1beta1
kind: LoggingLogMetric
metadata:
  name: iam-policy-changes
  namespace: config-control
spec:
  projectRef:
    external: "projects/PROJECT_ID"
  filter: 'protoPayload.methodName="SetIamPolicy" AND severity>=NOTICE'
  metricDescriptor:
    metricKind: DELTA
    valueType: INT64

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/a2.4-2.16n/a AU-6; SI-4A.8.16CLD.12.4.5

Log signals

  • Cloud Audit Logs on monitoring.googleapis.com for AlertPolicy.update where enabled transitions to false on alert policies tagged as security-critical.
  • Notification-channel deletes via NotificationChannel.delete on channels referenced by security alert policies — pages stop firing without an obvious alert-policy edit.
  • Log-based metric deletes (logging.googleapis.com LogMetric.delete) on metrics that drive security alert policies.

Query

logName=~"projects/.*/logs/cloudaudit.googleapis.com%2Factivity"
          AND ((protoPayload.serviceName="monitoring.googleapis.com"
                AND protoPayload.methodName=~".*AlertPolicy.*"
                AND protoPayload.request.alertPolicy.enabled=false)
               OR (protoPayload.serviceName="logging.googleapis.com"
                   AND protoPayload.methodName=~".*LogMetric.Delete.*"))

This Cloud Logging filter watches both Cloud Monitoring alert-policy state and Cloud Logging log-metric lifecycle; tag alert policies and log metrics with a security: critical userLabel so the filter can narrow to high-value detectors.

Alert threshold

  • Page on any alert-policy disable or notification-channel delete tagged security: critical.
  • Page on any log-metric delete that backs a security alert policy; replace with absence-of-signal alerting on the alert-policy itself once the metric is gone.

Initial response

  1. Restore the policy / metric / channel via gcloud alpha monitoring policies update or gcloud logging metrics create from the captured baseline JSON; verify the next synthetic event fires the page.
  2. Backfill missed alerts: query Cloud Logging directly for the metric's filter over the gap window and ingest matching events into the SIEM with the original severity label.
  3. Pin alert policies in Terraform and require the security: critical label to be present before any disable is accepted; reject console edits via a Cloud Functions admission webhook on the audit-log topic.

References

Equivalent on: AWS · Azure · OCI

gcp-log-08-bigquery-audit-sink ! MEDIUM RESPONSIVE

Author a dedicated BigQuery-destination Aggregated Sink that ships every audit-log entry from every project in the organization to a partitioned BigQuery dataset in a security-team-owned project, with two-year partition expiration, CMEK encryption on the dataset, and a saved SQL forensic-query library checked into version control (Google Cloud — Configure BigQuery log sinks (accessed 2026-05)). RESPONSIVE typing because the operational value is the during-incident and post-incident SQL forensic workflow — partitioned, queryable, and joinable against asset-inventory and CMDB tables — not the steady-state detection signal (which is owned by gcp-log-07) or the canonical retention store (which is owned by gcp-log-01). Mirrors the AWS pattern aws-log-08-cloudtrail-lake (CloudTrail Lake SQL forensic store) and the Azure pattern azure-log-08-sentinel-data-lake (Microsoft Sentinel data-lake / Log Analytics dedicated cluster). Pairs cross-domain with the IR forensic SQL workflow on gcp-ir-04.

Remediation — gcloud CLI

# gcloud CLI (latest stable)
# Step 1: create the dedicated forensic BigQuery dataset with CMEK.
bq --location=EU mk --dataset \
  --default_table_expiration=63072000 \
  --default_kms_key=projects/sec-logs-prod/locations/europe/keyRings/log-kr/cryptoKeys/log-cmek \
  sec-logs-prod:audit_forensic

# Step 2: author the org-scope Aggregated Sink to BigQuery with partitioned tables.
gcloud logging sinks create org-audit-sink-bq-forensic \
  bigquery.googleapis.com/projects/sec-logs-prod/datasets/audit_forensic \
  --organization=ORG_ID \
  --include-children \
  --use-partitioned-tables \
  --description="Forensic SQL store: org-aggregated audit logs, partitioned by day, 2y expiration"

# Step 3: grant the sink writer identity bigquery.dataEditor on the dataset.
WRITER=$(gcloud logging sinks describe org-audit-sink-bq-forensic \
  --organization=ORG_ID --format='value(writerIdentity)')

bq add-iam-policy-binding \
  --member="$WRITER" \
  --role=roles/bigquery.dataEditor \
  sec-logs-prod:audit_forensic

# Step 4: example forensic query — what did SA X do in the last 24h?
bq query --use_legacy_sql=false <<'SQL'
SELECT timestamp,
       protopayload_auditlog.methodName    AS method,
       protopayload_auditlog.resourceName   AS resource,
       protopayload_auditlog.authenticationInfo.principalEmail AS who
FROM `sec-logs-prod.audit_forensic.cloudaudit_googleapis_com_activity_*`
WHERE _TABLE_SUFFIX BETWEEN
        FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY))
        AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
  AND protopayload_auditlog.authenticationInfo.principalEmail
      = 'sa-suspect@svc-app-prod.iam.gserviceaccount.com'
ORDER BY timestamp DESC
LIMIT 500
SQL

Remediation — Terraform

# Terraform Google provider ~> 5.0
# Source: Google Cloud docs (accessed 2026-05)
resource "google_bigquery_dataset" "audit_forensic" {
  project    = var.sec_project_id
  dataset_id = "audit_forensic"
  location   = "EU"
  default_partition_expiration_ms = 63072000000 # 2y
  default_table_expiration_ms     = 63072000000

  default_encryption_configuration {
    kms_key_name = var.log_bucket_kms_key
  }
}

resource "google_logging_organization_sink" "audit_forensic" {
  name             = "org-audit-sink-bq-forensic"
  org_id           = var.org_id
  destination      = "bigquery.googleapis.com/projects/${var.sec_project_id}/datasets/${google_bigquery_dataset.audit_forensic.dataset_id}"
  include_children = true
  filter           = ""

  bigquery_options {
    use_partitioned_tables = true
  }
}

resource "google_bigquery_dataset_iam_member" "sink_writer" {
  project    = var.sec_project_id
  dataset_id = google_bigquery_dataset.audit_forensic.dataset_id
  role       = "roles/bigquery.dataEditor"
  member     = google_logging_organization_sink.audit_forensic.writer_identity
}

Remediation — Config Connector

apiVersion: logging.cnrm.cloud.google.com/v1beta1
kind: LoggingLogSink
metadata:
  name: bigquery-audit-sink
  namespace: config-control
spec:
  projectRef:
    external: "projects/PROJECT_ID"
  destination:
    bigQueryDatasetRef:
      external: "projects/PROJECT_ID/datasets/audit_archive"
  filter: 'logName:"cloudaudit.googleapis.com" AND severity>=NOTICE'

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/a(best-practices)n/a AU-11; IR-4(7)A.8.15; A.5.28CLD.12.4.5

Log signals

  • BigQuery dataset / table mutations against the audit-log sink dataset: bigquery.googleapis.com Dataset.update dropping the roles/bigquery.dataEditor binding for the sink writer identity; Table.delete on partitioned audit-log tables.
  • BigQuery dataset deletion: Dataset.delete on the audit-log sink destination — ingestion fails fast.
  • Per-table partition expiration shortened: Table.patch with timePartitioning.expirationMs reduced below the documented retention horizon.

Query

logName=~"projects/.*/logs/cloudaudit.googleapis.com%2Factivity"
          AND protoPayload.serviceName="bigquery.googleapis.com"
          AND (protoPayload.methodName="google.cloud.bigquery.v2.DatasetService.DeleteDataset"
               OR protoPayload.methodName="google.cloud.bigquery.v2.TableService.DeleteTable"
               OR protoPayload.methodName="google.cloud.bigquery.v2.DatasetService.PatchDataset")
          AND protoPayload.resourceName=~".*audit_logs.*"

Run this Cloud Logging filter at organisation scope and join against the canonical sink-→-destination map from Cloud Asset Inventory; the join key is the dataset resource name. Pair with a row-count query against the audit-log table to surface ingestion-stop independently of the admin-API event.

Alert threshold

  • Page on any dataset / table delete or IAM-binding removal targeting the audit-log sink dataset.
  • Page when the audit-log partition row-count for the current day stops growing for more than 15 minutes during business hours.

Initial response

  1. Restore the dataset / table from BigQuery time-travel (SELECT * FROM ... FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)) if within the 7-day window; otherwise restore from snapshot.
  2. Re-bind the sink writer-identity IAM role and verify the next sink-flush completes; backfill the gap window from the Cloud Storage parallel sink destination if one is configured.
  3. Place a BigQuery DDL deny IAM-condition on the audit-log dataset so future delete attempts surface as denied SetIamPolicy/DeleteDataset events without succeeding.

References

Equivalent on: AWS · Azure · OCI

Sources