Detection rules › Panther
GCP Log Bucket or Sink Deleted
This rule detects deletions of GCP Log Buckets or Sinks.
Rule body yaml
---
AnalysisType: rule
DedupPeriodMinutes: 60
DisplayName: GCP Log Bucket or Sink Deleted
Enabled: true
Filename: gcp_log_bucket_or_sink_deleted.py
RuleID: "GCP.Log.Bucket.Or.Sink.Deleted"
Severity: Medium
LogTypes:
- GCP.AuditLog
Tags:
- GCP
- Logging
- Bucket
- Sink
- Infrastructure
Description: >
This rule detects deletions of GCP Log Buckets or Sinks.
Runbook: >
Ensure that the bucket or sink deletion was expected. Adversaries may do this to cover their tracks.
Reference: https://cloud.google.com/logging/docs
Tests:
- Name: logging-bucket.deleted-should-alert
LogType: GCP.AuditLog
ExpectedResult: true
Log:
{
"insertid": "xxxxxxxxxx",
"logname": "projects/test-project-123456/logs/cloudaudit.googleapis.com%2Factivity",
"protoPayload":
{
"at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
"authenticationInfo": { "principalEmail": "user@domain.com" },
"authorizationInfo":
[
{
"granted": true,
"permission": "logging.buckets.delete",
"resource": "projects/test-project-123456/locations/global/buckets/testloggingbucket",
"resourceAttributes":
{
"name": "projects/test-project-123456/locations/global/buckets/testloggingbucket",
"service": "logging.googleapis.com",
},
},
],
"methodName": "google.logging.v2.ConfigServiceV2.DeleteBucket",
"request":
{
"@type": "type.googleapis.com/google.logging.v2.DeleteBucketRequest",
"name": "projects/test-project-123456/locations/global/buckets/testloggingbucket",
},
"requestMetadata":
{
"callerIP": "12.12.12.12",
"callerSuppliedUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36,gzip(gfe),gzip(gfe)",
"destinationAttributes": {},
"requestAttributes":
{ "auth": {}, "time": "2023-05-23T19:38:36.846070601Z" },
},
"resourceName": "projects/test-project-123456/locations/global/buckets/testloggingbucket",
"serviceName": "logging.googleapis.com",
"status": {},
},
"receivetimestamp": "2023-05-23 19:38:37.59",
"resource":
{
"labels":
{
"method": "google.logging.v2.ConfigServiceV2.DeleteBucket",
"project_id": "test-project-123456",
"service": "logging.googleapis.com",
},
"type": "audited_resource",
},
"severity": "NOTICE",
"timestamp": "2023-05-23 19:38:36.838",
}
- Name: logging-sink.deleted-should-alert
LogType: GCP.AuditLog
ExpectedResult: true
Log:
{
"insertid": "xxxxxxxxxx",
"logname": "projects/test-project-123456/logs/cloudaudit.googleapis.com%2Factivity",
"protoPayload":
{
"at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
"authenticationInfo": { "principalEmail": "user@domain.com" },
"authorizationInfo":
[
{
"granted": true,
"permission": "logging.sinks.delete",
"resource": "projects/test-project-123456/sinks/test-1",
"resourceAttributes":
{
"name": "projects/test-project-123456/sinks/test-1",
"service": "logging.googleapis.com",
},
},
],
"methodName": "google.logging.v2.ConfigServiceV2.DeleteSink",
"request":
{
"@type": "type.googleapis.com/google.logging.v2.DeleteSinkRequest",
"sinkName": "projects/test-project-123456/sinks/test-1",
},
"requestMetadata":
{
"callerIP": "12.12.12.12",
"callerSuppliedUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36,gzip(gfe),gzip(gfe)",
"destinationAttributes": {},
"requestAttributes":
{ "auth": {}, "time": "2023-05-23T19:39:15.230304077Z" },
},
"resourceName": "projects/test-project-123456/sinks/test-1",
"serviceName": "logging.googleapis.com",
"status": {},
},
"receivetimestamp": "2023-05-23 19:39:15.565",
"resource":
{
"labels":
{
"destination": "",
"name": "test-1",
"project_id": "test-project-123456",
},
"type": "logging_sink",
},
"severity": "NOTICE",
"timestamp": "2023-05-23 19:39:15.213",
}
- Name: logging-bucket.non-deletion-should-not-alert
LogType: GCP.AuditLog
ExpectedResult: false
Log:
{
"insertid": "xxxxxxxxxx",
"logname": "projects/test-project-123456/logs/cloudaudit.googleapis.com%2Factivity",
"protoPayload":
{
"at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
"authenticationInfo": { "principalEmail": "user@domain.com" },
"authorizationInfo":
[
{
"granted": true,
"permission": "logging.buckets.get",
"resource": "projects/test-project-123456/locations/global/buckets/testloggingbucket",
"resourceAttributes":
{
"name": "projects/test-project-123456/locations/global/buckets/testloggingbucket",
"service": "logging.googleapis.com",
},
},
],
"methodName": "google.logging.v2.ConfigServiceV2.GetBucket",
"request":
{
"@type": "type.googleapis.com/google.logging.v2.GetBucketRequest",
"name": "projects/test-project-123456/locations/global/buckets/testloggingbucket",
},
"requestMetadata":
{
"callerIP": "12.12.12.12",
"callerSuppliedUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36,gzip(gfe),gzip(gfe)",
"destinationAttributes": {},
"requestAttributes":
{ "auth": {}, "time": "2023-05-23T19:38:36.846070601Z" },
},
"resourceName": "projects/test-project-123456/locations/global/buckets/testloggingbucket",
"serviceName": "logging.googleapis.com",
"status": {},
},
"receivetimestamp": "2023-05-23 19:38:37.59",
"resource":
{
"labels":
{
"method": "google.logging.v2.ConfigServiceV2.GetBucket",
"project_id": "test-project-123456",
"service": "logging.googleapis.com",
},
"type": "audited_resource",
},
"severity": "NOTICE",
"timestamp": "2023-05-23 19:38:36.838",
}
- Name: logging-sink.non-deletion-should-not-alert
LogType: GCP.AuditLog
ExpectedResult: false
Log:
{
"insertid": "xxxxxxxxxx",
"logname": "projects/test-project-123456/logs/cloudaudit.googleapis.com%2Factivity",
"protoPayload":
{
"at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
"authenticationInfo": { "principalEmail": "user@domain.com" },
"authorizationInfo":
[
{
"granted": true,
"permission": "logging.sinks.get",
"resource": "projects/test-project-123456/sinks/test-1",
"resourceAttributes":
{
"name": "projects/test-project-123456/sinks/test-1",
"service": "logging.googleapis.com",
},
},
],
"methodName": "google.logging.v2.ConfigServiceV2.GetSink",
"request":
{
"@type": "type.googleapis.com/google.logging.v2.GetSinkRequest",
"sinkName": "projects/test-project-123456/sinks/test-1",
},
"requestMetadata":
{
"callerIP": "12.12.12.12",
"callerSuppliedUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36,gzip(gfe),gzip(gfe)",
"destinationAttributes": {},
"requestAttributes":
{ "auth": {}, "time": "2023-05-23T19:39:15.230304077Z" },
},
"resourceName": "projects/test-project-123456/sinks/test-1",
"serviceName": "logging.googleapis.com",
"status": {},
},
"receivetimestamp": "2023-05-23 19:39:15.565",
"resource":
{
"labels":
{
"destination": "",
"name": "test-1",
"project_id": "test-project-123456",
},
"type": "logging_sink",
},
"severity": "NOTICE",
"timestamp": "2023-05-23 19:39:15.213",
}
Detection logic
Rule logic imperative Python
import re
from panther_gcp_helpers import gcp_alert_context
def rule(event):
granted_list = event.deep_walk("protoPayload", "authorizationInfo", "granted", default=[])
authenticated = any(granted_list) if isinstance(granted_list, list) else bool(granted_list)
method_pattern = r"(?:\w+\.)*v\d\.(?:ConfigServiceV\d\.(?:Delete(Bucket|Sink)))"
match = re.search(method_pattern, event.deep_get("protoPayload", "methodName", default=""))
return authenticated and match is not None
def title(event):
actor = event.deep_get(
"protoPayload", "authenticationInfo", "principalEmail", default="<ACTOR_NOT_FOUND>"
)
resource = event.deep_get(
"protoPayload",
"resourceName",
default="<RESOURCE_NOT_FOUND>",
)
return f"[GCP]: [{actor}] deleted logging bucket or sink [{resource}]"
def alert_context(event):
return gcp_alert_context(event)
The parser cannot express this rule's logic as a field filter; the imperative Python above is the detection.
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
project | resource.labels.project_id |
principal | protoPayload.authenticationInfo.principalEmail |
caller_ip | protoPayload.requestMetadata.callerIP |
methodName | protoPayload.methodName |
resourceName | protoPayload.resourceName |
serviceName | protoPayload.serviceName |