Advanced Cloud
Logging and auditing in the cloud — CloudTrail, Cloud Audit Logs, event alerts
Without proper logging, an attack can go undetected for months. Cloud auditing is not optional — it is the mechanism that separates detection within hours from discovery after the damage is done. CloudTrail on AWS and Cloud Audit Logs on GCP record every control-plane action and are the foundation of any forensic investigation.
AWS CloudTrail — what it records and what it doesn’t
CloudTrail records:
- Control-plane API calls (who created, deleted, or modified resources)
- Console, CLI, SDK, and AWS service actions
- Actions on IAM, S3, EC2, Lambda, RDS, etc.
CloudTrail does NOT record by default:
- Data events (S3 object reads/writes, Lambda invocations)
- These must be explicitly enabled and incur additional cost
# Create a multi-region trail with log file validation
aws cloudtrail create-trail \
--name prod-trail \
--s3-bucket-name audit-logs-prod \
--include-global-service-events \
--is-multi-region-trail \
--enable-log-file-validation
# Enable data events for S3
aws cloudtrail put-event-selectors \
--trail-name prod-trail \
--event-selectors '[{
"ReadWriteType": "All",
"IncludeManagementEvents": true,
"DataResources": [{
"Type": "AWS::S3::Object",
"Values": ["arn:aws:s3:::sensitive-bucket/"]
}]
}]'
Protecting audit logs
CloudTrail logs must be immutable. An attacker who compromises an account will try to delete evidence:
# Log bucket with Object Lock (WORM immutability)
aws s3api put-object-lock-configuration \
--bucket audit-logs-prod \
--object-lock-configuration '{
"ObjectLockEnabled": "Enabled",
"Rule": {
"DefaultRetention": { "Mode": "COMPLIANCE", "Days": 365 }
}
}'
# Log File Validation — detects deleted or tampered files
aws cloudtrail validate-logs \
--trail-arn arn:aws:cloudtrail:us-east-1:123456789:trail/prod-trail \
--start-time 2026-06-01T00:00:00Z
Event alerts with CloudWatch and EventBridge
Detect critical actions in near real time:
# Metric filter — detect root account usage
aws logs put-metric-filter \
--log-group-name CloudTrail/DefaultLogGroup \
--filter-name RootAccountUsage \
--filter-pattern '{ $.userIdentity.type = "Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != "AwsServiceEvent" }' \
--metric-transformations metricName=RootAccountUsage,metricNamespace=CloudTrailMetrics,metricValue=1
# CloudWatch alarm for the metric above
aws cloudwatch put-metric-alarm \
--alarm-name RootAccountUsage \
--metric-name RootAccountUsage \
--namespace CloudTrailMetrics \
--statistic Sum \
--period 300 \
--threshold 1 \
--comparison-operator GreaterThanOrEqualToThreshold \
--evaluation-periods 1 \
--alarm-actions arn:aws:sns:us-east-1:123456789:security-alerts
Critical events that should trigger immediate alerts:
- Root account usage
- CloudTrail disabled or trail modified
- IAM changes (user creation, AdministratorAccess attached)
- Security group with ingress 0.0.0.0/0 created
- S3 bucket with Block Public Access disabled
- Access key created for an IAM user
- Repeated MFA login failures
- High-severity GuardDuty finding
GCP Cloud Audit Logs
On GCP, audit logs have three categories:
Admin Activity → always on, free — resource creation/deletion
Data Access → off by default, paid — data reads
System Events → automatic GCP actions — always on, free
# Enable Data Access logs for Cloud Storage via gcloud
gcloud projects get-iam-policy my-project --format=json > policy.json
# Edit policy.json: add auditLogConfigs for storage.googleapis.com
gcloud projects set-iam-policy my-project policy.json
# Create a log alert in GCP — export to Pub/Sub and process
gcloud logging sinks create audit-alerts \
pubsub.googleapis.com/projects/my-project/topics/cloud-alerts \
--log-filter='protoPayload.methodName="SetIamPolicy"'
Correlation and SIEM
Isolated logs cannot detect complex attacks. Forward them to a SIEM:
AWS → Kinesis Firehose → S3 → Athena or SIEM (Splunk, Elastic)
GCP → Log Sink → Pub/Sub → Dataflow → BigQuery or SIEM
Azure → Diagnostic Settings → Event Hub → SIEM
Useful queries (Athena over CloudTrail):
SELECT userIdentity.arn, eventName, COUNT(*)
FROM cloudtrail_logs
WHERE errorCode IS NOT NULL
GROUP BY 1, 2
ORDER BY 3 DESC
LIMIT 20;