mirror of
https://github.com/BagelHole/DevOps-Security-Agent-Skills.git
synced 2026-08-22 12:49:53 +02:00
475 lines
16 KiB
Markdown
475 lines
16 KiB
Markdown
---
|
|
name: gcp-audit-logs
|
|
description: Configure GCP Cloud Audit Logs for compliance. Set up log routing and BigQuery analysis. Use when auditing GCP activity.
|
|
license: MIT
|
|
metadata:
|
|
author: devops-skills
|
|
version: "1.0"
|
|
---
|
|
|
|
# GCP Audit Logs
|
|
|
|
Audit GCP activity with Cloud Audit Logs for compliance, security investigation, and operational monitoring.
|
|
|
|
## When to Use
|
|
|
|
- Enabling organization-wide audit logging across GCP projects
|
|
- Meeting compliance requirements for SOC 2, HIPAA, PCI DSS, or FedRAMP
|
|
- Investigating unauthorized access or suspicious API activity
|
|
- Setting up alerting on administrative and data access events
|
|
- Exporting logs to BigQuery for long-term analysis and reporting
|
|
|
|
## Audit Log Types
|
|
|
|
```yaml
|
|
log_types:
|
|
admin_activity:
|
|
description: API calls that modify resource configuration or metadata
|
|
enabled: Always (cannot be disabled)
|
|
retention: 400 days (default)
|
|
cost: No charge
|
|
examples:
|
|
- Creating or deleting VM instances
|
|
- Changing IAM policies
|
|
- Modifying firewall rules
|
|
|
|
data_access:
|
|
description: API calls that read resource configuration, metadata, or user data
|
|
enabled: Must be explicitly enabled (except BigQuery)
|
|
retention: 30 days (default)
|
|
cost: Can be significant at high volume
|
|
subtypes:
|
|
ADMIN_READ: Read resource configuration/metadata
|
|
DATA_READ: Read user-provided data
|
|
DATA_WRITE: Write user-provided data
|
|
|
|
system_event:
|
|
description: Actions performed by GCP systems on behalf of resources
|
|
enabled: Always (cannot be disabled)
|
|
retention: 400 days (default)
|
|
cost: No charge
|
|
examples:
|
|
- Live migration of VM instances
|
|
- Automatic scaling events
|
|
|
|
policy_denied:
|
|
description: Actions denied by VPC Service Controls or organization policies
|
|
enabled: Always (cannot be disabled)
|
|
retention: 400 days (default)
|
|
cost: No charge
|
|
```
|
|
|
|
## Enable Data Access Logs for an Organization
|
|
|
|
```bash
|
|
# Get current org IAM policy
|
|
gcloud organizations get-iam-policy ORG_ID --format=json > org-policy.json
|
|
|
|
# Add audit config to org-policy.json:
|
|
# {
|
|
# "auditConfigs": [
|
|
# {
|
|
# "service": "allServices",
|
|
# "auditLogConfigs": [
|
|
# {"logType": "ADMIN_READ"},
|
|
# {"logType": "DATA_READ"},
|
|
# {"logType": "DATA_WRITE"}
|
|
# ]
|
|
# }
|
|
# ],
|
|
# ...existing bindings...
|
|
# }
|
|
|
|
# Apply the updated policy
|
|
gcloud organizations set-iam-policy ORG_ID org-policy.json
|
|
|
|
# Enable data access logs for specific services at project level
|
|
gcloud projects get-iam-policy PROJECT_ID --format=json > project-policy.json
|
|
|
|
# Example: enable only for Cloud Storage and BigQuery
|
|
# {
|
|
# "auditConfigs": [
|
|
# {
|
|
# "service": "storage.googleapis.com",
|
|
# "auditLogConfigs": [
|
|
# {"logType": "DATA_READ"},
|
|
# {"logType": "DATA_WRITE"}
|
|
# ]
|
|
# },
|
|
# {
|
|
# "service": "bigquery.googleapis.com",
|
|
# "auditLogConfigs": [
|
|
# {"logType": "DATA_READ"},
|
|
# {"logType": "DATA_WRITE"}
|
|
# ]
|
|
# }
|
|
# ]
|
|
# }
|
|
|
|
gcloud projects set-iam-policy PROJECT_ID project-policy.json
|
|
```
|
|
|
|
## Configure Log Sinks for Export
|
|
|
|
```bash
|
|
# Create BigQuery dataset for audit log export
|
|
bq mk --dataset \
|
|
--description "Audit log export" \
|
|
--default_table_expiration 0 \
|
|
--location US \
|
|
PROJECT_ID:audit_logs
|
|
|
|
# Create organization-level log sink to BigQuery
|
|
gcloud logging sinks create org-audit-bigquery \
|
|
bigquery.googleapis.com/projects/PROJECT_ID/datasets/audit_logs \
|
|
--organization=ORG_ID \
|
|
--include-children \
|
|
--log-filter='logName:"cloudaudit.googleapis.com"'
|
|
|
|
# Get the sink writer identity and grant BigQuery access
|
|
SINK_SA=$(gcloud logging sinks describe org-audit-bigquery \
|
|
--organization=ORG_ID --format='value(writerIdentity)')
|
|
|
|
bq add-iam-policy-binding \
|
|
--member="$SINK_SA" \
|
|
--role="roles/bigquery.dataEditor" \
|
|
PROJECT_ID:audit_logs
|
|
|
|
# Create Cloud Storage sink for long-term archive
|
|
gsutil mb -l US -b on gs://org-audit-logs-archive
|
|
gsutil retention set 7y gs://org-audit-logs-archive
|
|
|
|
gcloud logging sinks create org-audit-storage \
|
|
storage.googleapis.com/org-audit-logs-archive \
|
|
--organization=ORG_ID \
|
|
--include-children \
|
|
--log-filter='logName:"cloudaudit.googleapis.com"'
|
|
|
|
STORAGE_SA=$(gcloud logging sinks describe org-audit-storage \
|
|
--organization=ORG_ID --format='value(writerIdentity)')
|
|
|
|
gsutil iam ch "$STORAGE_SA:objectCreator" gs://org-audit-logs-archive
|
|
|
|
# Create Pub/Sub sink for real-time streaming to SIEM
|
|
gcloud pubsub topics create audit-log-stream
|
|
|
|
gcloud logging sinks create org-audit-pubsub \
|
|
pubsub.googleapis.com/projects/PROJECT_ID/topics/audit-log-stream \
|
|
--organization=ORG_ID \
|
|
--include-children \
|
|
--log-filter='logName:"cloudaudit.googleapis.com" AND (protoPayload.methodName:"delete" OR protoPayload.methodName:"setIamPolicy" OR severity>=WARNING)'
|
|
|
|
PUBSUB_SA=$(gcloud logging sinks describe org-audit-pubsub \
|
|
--organization=ORG_ID --format='value(writerIdentity)')
|
|
|
|
gcloud pubsub topics add-iam-policy-binding audit-log-stream \
|
|
--member="$PUBSUB_SA" \
|
|
--role="roles/pubsub.publisher"
|
|
```
|
|
|
|
## Logging Queries (Cloud Logging Explorer)
|
|
|
|
```bash
|
|
# View admin activity logs for the last 24 hours
|
|
gcloud logging read 'logName:"cloudaudit.googleapis.com/activity"
|
|
AND timestamp>="2024-01-01T00:00:00Z"' \
|
|
--project=PROJECT_ID \
|
|
--format=json \
|
|
--limit=100
|
|
|
|
# Find IAM policy changes
|
|
gcloud logging read 'logName:"cloudaudit.googleapis.com/activity"
|
|
AND protoPayload.methodName="SetIamPolicy"' \
|
|
--project=PROJECT_ID \
|
|
--freshness=7d
|
|
|
|
# Find resource deletions
|
|
gcloud logging read 'logName:"cloudaudit.googleapis.com/activity"
|
|
AND protoPayload.methodName=~"delete"
|
|
AND severity>=NOTICE' \
|
|
--project=PROJECT_ID \
|
|
--freshness=7d
|
|
|
|
# Data access audit log entries
|
|
gcloud logging read 'logName:"cloudaudit.googleapis.com/data_access"
|
|
AND protoPayload.serviceName="storage.googleapis.com"
|
|
AND protoPayload.methodName="storage.objects.get"' \
|
|
--project=PROJECT_ID \
|
|
--freshness=24h
|
|
|
|
# Failed authorization attempts
|
|
gcloud logging read 'logName:"cloudaudit.googleapis.com/policy"' \
|
|
--project=PROJECT_ID \
|
|
--freshness=7d
|
|
```
|
|
|
|
## BigQuery Analysis Queries
|
|
|
|
```sql
|
|
-- All destructive operations in the last 30 days
|
|
SELECT
|
|
timestamp,
|
|
protopayload_auditlog.authenticationInfo.principalEmail AS principal,
|
|
protopayload_auditlog.methodName AS method,
|
|
protopayload_auditlog.resourceName AS resource,
|
|
resource.labels.project_id AS project,
|
|
protopayload_auditlog.status.code AS status_code,
|
|
protopayload_auditlog.status.message AS status_message
|
|
FROM `project.audit_logs.cloudaudit_googleapis_com_activity_*`
|
|
WHERE _TABLE_SUFFIX >= FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY))
|
|
AND protopayload_auditlog.methodName LIKE '%delete%'
|
|
ORDER BY timestamp DESC
|
|
LIMIT 500;
|
|
|
|
-- IAM policy changes across the organization
|
|
SELECT
|
|
timestamp,
|
|
protopayload_auditlog.authenticationInfo.principalEmail AS changed_by,
|
|
resource.labels.project_id AS project,
|
|
protopayload_auditlog.resourceName AS resource,
|
|
protopayload_auditlog.servicedata_v1_iam.policyDelta.bindingDeltas
|
|
FROM `project.audit_logs.cloudaudit_googleapis_com_activity_*`
|
|
WHERE _TABLE_SUFFIX >= FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY))
|
|
AND protopayload_auditlog.methodName = 'SetIamPolicy'
|
|
ORDER BY timestamp DESC;
|
|
|
|
-- Activity per principal (detect anomalous usage)
|
|
SELECT
|
|
protopayload_auditlog.authenticationInfo.principalEmail AS principal,
|
|
COUNT(*) AS action_count,
|
|
COUNT(DISTINCT protopayload_auditlog.methodName) AS unique_methods,
|
|
COUNT(DISTINCT protopayload_auditlog.requestMetadata.callerIp) AS unique_ips,
|
|
MIN(timestamp) AS first_activity,
|
|
MAX(timestamp) AS last_activity
|
|
FROM `project.audit_logs.cloudaudit_googleapis_com_activity_*`
|
|
WHERE _TABLE_SUFFIX >= FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY))
|
|
GROUP BY principal
|
|
ORDER BY action_count DESC
|
|
LIMIT 50;
|
|
|
|
-- Service account key creation events (security risk indicator)
|
|
SELECT
|
|
timestamp,
|
|
protopayload_auditlog.authenticationInfo.principalEmail AS created_by,
|
|
protopayload_auditlog.resourceName AS service_account,
|
|
protopayload_auditlog.requestMetadata.callerIp AS source_ip
|
|
FROM `project.audit_logs.cloudaudit_googleapis_com_activity_*`
|
|
WHERE _TABLE_SUFFIX >= FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY))
|
|
AND protopayload_auditlog.methodName = 'google.iam.admin.v1.CreateServiceAccountKey'
|
|
ORDER BY timestamp DESC;
|
|
|
|
-- Data access patterns for sensitive Cloud Storage buckets
|
|
SELECT
|
|
timestamp,
|
|
protopayload_auditlog.authenticationInfo.principalEmail AS accessor,
|
|
protopayload_auditlog.resourceName AS object_path,
|
|
protopayload_auditlog.methodName AS access_type,
|
|
protopayload_auditlog.requestMetadata.callerIp AS source_ip
|
|
FROM `project.audit_logs.cloudaudit_googleapis_com_data_access_*`
|
|
WHERE _TABLE_SUFFIX >= FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY))
|
|
AND protopayload_auditlog.resourceName LIKE '%sensitive-bucket%'
|
|
ORDER BY timestamp DESC
|
|
LIMIT 1000;
|
|
|
|
-- Failed operations indicating permission issues
|
|
SELECT
|
|
timestamp,
|
|
protopayload_auditlog.authenticationInfo.principalEmail AS principal,
|
|
protopayload_auditlog.methodName AS method,
|
|
protopayload_auditlog.status.code AS error_code,
|
|
protopayload_auditlog.status.message AS error_message,
|
|
protopayload_auditlog.requestMetadata.callerIp AS source_ip
|
|
FROM `project.audit_logs.cloudaudit_googleapis_com_activity_*`
|
|
WHERE _TABLE_SUFFIX >= FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY))
|
|
AND protopayload_auditlog.status.code != 0
|
|
ORDER BY timestamp DESC
|
|
LIMIT 500;
|
|
```
|
|
|
|
## Alerting Policies
|
|
|
|
```bash
|
|
# Alert on service account key creation
|
|
gcloud alpha monitoring policies create \
|
|
--display-name="SA Key Created" \
|
|
--condition-display-name="Service Account Key Creation" \
|
|
--condition-filter='resource.type="audited_resource" AND protoPayload.methodName="google.iam.admin.v1.CreateServiceAccountKey"' \
|
|
--condition-threshold-value=0 \
|
|
--condition-threshold-comparison=COMPARISON_GT \
|
|
--condition-threshold-duration=0s \
|
|
--notification-channels=projects/PROJECT_ID/notificationChannels/CHANNEL_ID \
|
|
--combiner=OR
|
|
|
|
# Create a log-based metric for IAM changes
|
|
gcloud logging metrics create iam-policy-changes \
|
|
--description="Count of IAM policy changes" \
|
|
--log-filter='logName:"cloudaudit.googleapis.com/activity" AND protoPayload.methodName="SetIamPolicy"'
|
|
|
|
# Create alerting policy using the log-based metric
|
|
gcloud alpha monitoring policies create \
|
|
--display-name="IAM Policy Changes" \
|
|
--condition-display-name="IAM Changes Detected" \
|
|
--condition-filter='metric.type="logging.googleapis.com/user/iam-policy-changes"' \
|
|
--condition-threshold-value=0 \
|
|
--condition-threshold-comparison=COMPARISON_GT \
|
|
--condition-threshold-duration=0s \
|
|
--notification-channels=projects/PROJECT_ID/notificationChannels/CHANNEL_ID
|
|
|
|
# Create log-based metric for firewall changes
|
|
gcloud logging metrics create firewall-rule-changes \
|
|
--description="Count of firewall rule changes" \
|
|
--log-filter='logName:"cloudaudit.googleapis.com/activity"
|
|
AND (protoPayload.methodName="v1.compute.firewalls.insert"
|
|
OR protoPayload.methodName="v1.compute.firewalls.delete"
|
|
OR protoPayload.methodName="v1.compute.firewalls.patch")'
|
|
|
|
# Create log-based metric for VPC network changes
|
|
gcloud logging metrics create vpc-network-changes \
|
|
--description="Count of VPC network changes" \
|
|
--log-filter='logName:"cloudaudit.googleapis.com/activity"
|
|
AND resource.type="gce_network"
|
|
AND (protoPayload.methodName=~"insert$" OR protoPayload.methodName=~"delete$")'
|
|
```
|
|
|
|
## Terraform Configuration
|
|
|
|
```hcl
|
|
# Organization-level audit log sink to BigQuery
|
|
resource "google_logging_organization_sink" "audit_bigquery" {
|
|
name = "org-audit-bigquery"
|
|
org_id = var.org_id
|
|
destination = "bigquery.googleapis.com/projects/${var.project_id}/datasets/${google_bigquery_dataset.audit_logs.dataset_id}"
|
|
filter = "logName:\"cloudaudit.googleapis.com\""
|
|
include_children = true
|
|
|
|
bigquery_options {
|
|
use_partitioned_tables = true
|
|
}
|
|
}
|
|
|
|
resource "google_bigquery_dataset" "audit_logs" {
|
|
dataset_id = "audit_logs"
|
|
project = var.project_id
|
|
location = "US"
|
|
description = "Organization audit log export"
|
|
|
|
default_table_expiration_ms = null # No auto-expiry
|
|
|
|
access {
|
|
role = "WRITER"
|
|
user_by_email = google_logging_organization_sink.audit_bigquery.writer_identity
|
|
}
|
|
|
|
access {
|
|
role = "READER"
|
|
group_by_email = "security-auditors@example.com"
|
|
}
|
|
}
|
|
|
|
# Retention bucket with bucket lock
|
|
resource "google_storage_bucket" "audit_archive" {
|
|
name = "org-audit-logs-archive"
|
|
location = "US"
|
|
force_destroy = false
|
|
project = var.project_id
|
|
|
|
uniform_bucket_level_access = true
|
|
|
|
retention_policy {
|
|
is_locked = true
|
|
retention_period = 220752000 # 7 years in seconds
|
|
}
|
|
|
|
lifecycle_rule {
|
|
condition {
|
|
age = 90
|
|
}
|
|
action {
|
|
type = "SetStorageClass"
|
|
storage_class = "COLDLINE"
|
|
}
|
|
}
|
|
}
|
|
|
|
# Log-based alerting
|
|
resource "google_logging_metric" "iam_changes" {
|
|
name = "iam-policy-changes"
|
|
project = var.project_id
|
|
filter = "logName:\"cloudaudit.googleapis.com/activity\" AND protoPayload.methodName=\"SetIamPolicy\""
|
|
|
|
metric_descriptor {
|
|
metric_kind = "DELTA"
|
|
value_type = "INT64"
|
|
}
|
|
}
|
|
|
|
resource "google_monitoring_alert_policy" "iam_changes" {
|
|
display_name = "IAM Policy Changes Detected"
|
|
project = var.project_id
|
|
combiner = "OR"
|
|
|
|
conditions {
|
|
display_name = "IAM policy change count"
|
|
condition_threshold {
|
|
filter = "metric.type=\"logging.googleapis.com/user/iam-policy-changes\" AND resource.type=\"global\""
|
|
comparison = "COMPARISON_GT"
|
|
threshold_value = 0
|
|
duration = "0s"
|
|
}
|
|
}
|
|
|
|
notification_channels = [var.notification_channel_id]
|
|
}
|
|
```
|
|
|
|
## Setup Checklist
|
|
|
|
```yaml
|
|
gcp_audit_logs_checklist:
|
|
log_enablement:
|
|
- [ ] Admin activity logs verified active (always on)
|
|
- [ ] Data access logs enabled for sensitive services
|
|
- [ ] Data access exemptions configured to exclude high-volume, low-risk operations
|
|
- [ ] System event logs verified active (always on)
|
|
|
|
log_routing:
|
|
- [ ] Organization-level sink to BigQuery for analysis
|
|
- [ ] Organization-level sink to Cloud Storage for long-term archive
|
|
- [ ] Pub/Sub sink for real-time SIEM streaming (high severity events)
|
|
- [ ] Sink writer identities granted appropriate destination permissions
|
|
- [ ] Inclusion filters verified to capture all audit log types
|
|
|
|
storage_and_retention:
|
|
- [ ] BigQuery dataset created with appropriate access controls
|
|
- [ ] Cloud Storage bucket with retention policy and bucket lock
|
|
- [ ] Storage class lifecycle rules configured (Standard to Coldline)
|
|
- [ ] Default log retention in Cloud Logging extended if needed
|
|
|
|
alerting:
|
|
- [ ] Notification channels configured (email, PagerDuty, Slack)
|
|
- [ ] Log-based metric for IAM policy changes
|
|
- [ ] Log-based metric for firewall rule changes
|
|
- [ ] Log-based metric for service account key creation
|
|
- [ ] Alert policy for each critical metric
|
|
- [ ] Alert notification tested end-to-end
|
|
|
|
access_control:
|
|
- [ ] Logging Admin role restricted to security team
|
|
- [ ] BigQuery dataset read access granted to auditors only
|
|
- [ ] Storage bucket access restricted with IAM
|
|
- [ ] Sink configuration changes monitored via admin activity logs
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
- Enable data access logs selectively on sensitive services to control cost and volume
|
|
- Use organization-level sinks with include-children to capture all projects automatically
|
|
- Export to BigQuery with partitioned tables for efficient querying over large time ranges
|
|
- Archive to Cloud Storage with bucket lock and retention policies for immutable long-term storage
|
|
- Create log-based metrics and alerting policies for high-severity events
|
|
- Stream critical audit events via Pub/Sub to SIEM for real-time correlation
|
|
- Apply exemptions to exclude high-volume read-only service accounts from data access logs
|
|
- Restrict access to audit log sinks and destinations with least-privilege IAM bindings
|
|
- Regularly run BigQuery analysis queries to detect anomalous patterns and generate compliance reports
|
|
- Monitor log sink health and delivery latency to ensure continuous audit coverage
|