From 9c3c6d8fe2e8ec0d8e6e646ce26b837ecdb87d35 Mon Sep 17 00:00:00 2001 From: Raajhesh Kannaa Chidambaram <495042+raajheshkannaa@users.noreply.github.com> Date: Sun, 17 May 2026 19:02:32 -0400 Subject: [PATCH] fix(plugins/gcpaudit): route gcp.policyDelta by IAM proto, not gcs_bucket allowlist Before, gcp.policyDelta sent every resource type except gcs_bucket through protoPayload.metadata.datasetChange.bindingDeltas. That path only exists for BigQuery dataset.setIamPolicy. Every other GCP IAM SetIamPolicy event emits the deltas under protoPayload.serviceData.policyDelta.bindingDeltas (the generic google.iam.v1.PolicyDelta proto), so project, folder, organization, service_account, and any future IAM-using resource type returned an empty field, breaking rules that match on %gcp.policyDelta. Route bigquery_dataset to the metadata.datasetChange path and default every other resource type to the generic serviceData.policyDelta path. This matches both the GCP audit log schema and the reporter's description ("the else path is really only used by bigquery_dataset type"). Unknown future resource types now resolve correctly without another code change. Extract the routing into a small policyDeltaPath helper so the policy is unit-testable; the gcpaudit package previously had no tests. Closes #1351 Signed-off-by: Raajhesh Kannaa Chidambaram <495042+raajheshkannaa@users.noreply.github.com> --- plugins/gcpaudit/pkg/gcpaudit/extract.go | 21 +++++-- plugins/gcpaudit/pkg/gcpaudit/extract_test.go | 57 +++++++++++++++++++ 2 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 plugins/gcpaudit/pkg/gcpaudit/extract_test.go diff --git a/plugins/gcpaudit/pkg/gcpaudit/extract.go b/plugins/gcpaudit/pkg/gcpaudit/extract.go index f64f102e8..8438c449f 100644 --- a/plugins/gcpaudit/pkg/gcpaudit/extract.go +++ b/plugins/gcpaudit/pkg/gcpaudit/extract.go @@ -92,12 +92,7 @@ func (p *Plugin) Extract(req sdk.ExtractRequest, evt sdk.EventReader) error { case "gcp.policyDelta": resource := string(p.jdata.Get("resource").Get("type").GetStringBytes()) - - if resource == "gcs_bucket" { - fsval = p.jdata.Get("protoPayload", "serviceData", "policyDelta", "bindingDeltas") - } else { - fsval = p.jdata.Get("protoPayload", "metadata", "datasetChange", "bindingDeltas") - } + fsval = p.jdata.Get(policyDeltaPath(resource)...) case "gcp.methodName": fsval = p.jdata.Get("protoPayload", "methodName") @@ -201,3 +196,17 @@ func (p *Plugin) Extract(req sdk.ExtractRequest, evt sdk.EventReader) error { return nil } + +// policyDeltaPath returns the JSON path that contains the bindingDeltas array +// for a SetIamPolicy audit event on the given resource type. The generic IAM +// AuditData proto (google.iam.v1.PolicyDelta) is emitted under +// protoPayload.serviceData.policyDelta for every resource type that uses the +// shared IAM service. BigQuery's dataset.setIamPolicy is the outlier and emits +// its deltas under protoPayload.metadata.datasetChange as part of the +// BigQueryAuditMetadata. See issue #1351. +func policyDeltaPath(resource string) []string { + if resource == "bigquery_dataset" { + return []string{"protoPayload", "metadata", "datasetChange", "bindingDeltas"} + } + return []string{"protoPayload", "serviceData", "policyDelta", "bindingDeltas"} +} diff --git a/plugins/gcpaudit/pkg/gcpaudit/extract_test.go b/plugins/gcpaudit/pkg/gcpaudit/extract_test.go new file mode 100644 index 000000000..bb2f79d83 --- /dev/null +++ b/plugins/gcpaudit/pkg/gcpaudit/extract_test.go @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2026 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gcpaudit + +import ( + "reflect" + "testing" +) + +// TestPolicyDeltaPath guards against #1351: gcp.policyDelta was returning the +// BigQuery datasetChange path for every non-gcs_bucket resource type, leaving +// the field empty for project, folder, organization, service_account, and any +// other GCP resource that uses the generic IAM SetIamPolicy flow. +func TestPolicyDeltaPath(t *testing.T) { + serviceData := []string{"protoPayload", "serviceData", "policyDelta", "bindingDeltas"} + datasetChange := []string{"protoPayload", "metadata", "datasetChange", "bindingDeltas"} + + cases := []struct { + resource string + want []string + }{ + {"gcs_bucket", serviceData}, + {"project", serviceData}, + {"folder", serviceData}, + {"organization", serviceData}, + {"service_account", serviceData}, + // Unknown / future resource types default to the generic IAM path, + // not the BigQuery-specific datasetChange path. + {"unknown_type", serviceData}, + {"", serviceData}, + {"bigquery_dataset", datasetChange}, + } + + for _, tc := range cases { + t.Run(tc.resource, func(t *testing.T) { + got := policyDeltaPath(tc.resource) + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("policyDeltaPath(%q) = %v, want %v", tc.resource, got, tc.want) + } + }) + } +}