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) + } + }) + } +}