Tháng 9/2025, RubyGems.org: attacker giành được root password AWS account. Trong 4 phút, họ thay đổi root password, enumerate toàn bộ IAM roles, lấy credential từ EC2 instance. Phát hiện 11 ngày sau — khi một contributor tình cờ check billing thấy spike lạ.
Tại sao 11 ngày? Không có CloudTrail alert cho root activity. Nếu họ có metric filter { $.userIdentity.type = "Root" } → CloudWatch Alarm → SNS notification, họ đã biết trong vài phút.
CloudTrail là “camera an ninh” của AWS — ghi lại mọi API call trong account: ai, làm gì, khi nào, từ IP nào, thành công hay thất bại. Bài này setup CloudTrail đúng cách, dùng nó để điều tra incident, và alert real-time khi có dấu hiệu tấn công.
flowchart LR
API["Every API Call<br/>Console · CLI · SDK · IaC"]
CT["CloudTrail"]
S3["S3 Bucket<br/>gzip JSON logs"]
CW["CloudWatch Logs<br/>real-time stream"]
Lake["CloudTrail Lake<br/>SQL query, 7yr retention"]
Athena["Athena<br/>SQL query on S3"]
Filter["Metric Filters<br/>→ CloudWatch Alarms"]
CT --> S3
CT --> CW
CT --> Lake
S3 --> Athena
CW --> Filter
3 loại event: chọn đúng, trả đúng giá
| Event Type | Ghi những gì | Mặc định? | Giá |
|---|---|---|---|
| Management | Control plane: Create/Delete/Update resource | Có (bản copy đầu miễn phí/account) | Miễn phí (copy đầu) |
| Data | Data plane: GetObject, PutItem, Invoke | Không | $0.10/100K events |
| Insight | ML phát hiện bất thường (API spike, unusual pattern) | Không | $0.35/100K events |
AdvancedEventSelectors để filter chính xác bucket và prefix — không dùng wildcard *.Trail setup production-grade
# 1. S3 bucket cho log (với lifecycle policy)
aws s3api create-bucket --bucket aws-cloudtrail-logs-123456789012 --region ap-southeast-1
# 2. Bucket policy cho CloudTrail
aws s3api put-bucket-policy --bucket aws-cloudtrail-logs-123456789012 --policy '{
"Version": "2012-10-17",
"Statement": [
{"Effect":"Allow","Principal":{"Service":"cloudtrail.amazonaws.com"},"Action":"s3:GetBucketAcl","Resource":"arn:aws:s3:::aws-cloudtrail-logs-123456789012"},
{"Effect":"Allow","Principal":{"Service":"cloudtrail.amazonaws.com"},"Action":"s3:PutObject","Resource":"arn:aws:s3:::aws-cloudtrail-logs-123456789012/AWSLogs/123456789012/*","Condition":{"StringEquals":{"s3:x-amz-acl":"bucket-owner-full-control"}}}
]
}'
# 3. Tạo trail: multi-region + global services + log validation + organization
aws cloudtrail create-trail \
--name org-trail \
--s3-bucket-name aws-cloudtrail-logs-123456789012 \
--is-multi-region-trail \
--include-global-service-events \
--enable-log-file-validation \
--is-organization-trail \
--cloud-watch-logs-log-group-arn arn:aws:logs:ap-southeast-1:123456789012:log-group:cloudtrail:*
aws cloudtrail start-logging --name org-trail
4 flags mandatory:
--is-multi-region-trail: attacker thường nhắm region ít dùng → cần log tất cả region--include-global-service-events: IAM, CloudFront, Route 53, STS--enable-log-file-validation: digest SHA-256 mỗi giờ → phát hiện log bị chỉnh sửa--is-organization-trail: một trail cho toàn organization → member account không thể tắt
Incident investigation với Athena
CREATE EXTERNAL TABLE cloudtrail_logs (
eventVersion STRING,
userIdentity STRUCT<type:STRING,arn:STRING,userName:STRING>,
eventTime STRING, eventName STRING, sourceIPAddress STRING,
userAgent STRING, errorCode STRING, requestParameters STRING
)
PARTITIONED BY (region STRING, year STRING, month STRING, day STRING)
ROW FORMAT SERDE 'com.amazon.emr.hive.serde.CloudTrailSerde'
STORED AS INPUTFORMAT 'com.amazon.emr.cloudtrail.CloudTrailInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://aws-cloudtrail-logs-123456789012/AWSLogs/123456789012/';
-- Ai xóa RDS instance?
SELECT userIdentity.userName, eventTime, sourceIPAddress
FROM cloudtrail_logs WHERE eventName = 'DeleteDBInstance'
AND year='2026' AND month='06' ORDER BY eventTime DESC;
-- API nào bị deny nhiều nhất? (potential attack scan)
SELECT eventName, COUNT(*) AS denied, userIdentity.userName
FROM cloudtrail_logs WHERE errorCode IS NOT NULL
AND year='2026' AND month='06'
GROUP BY eventName, userIdentity.userName ORDER BY denied DESC LIMIT 20;
-- IP lạ gọi API? (ngoài office range)
SELECT sourceIPAddress, userIdentity.userName, COUNT(*) AS calls
FROM cloudtrail_logs WHERE sourceIPAddress NOT LIKE '203.0.113.%'
AND year='2026' AND month='06'
GROUP BY sourceIPAddress, userIdentity.userName ORDER BY calls DESC;
-- Root user activity (phải CỰC HIẾM)
SELECT eventTime, eventName, sourceIPAddress
FROM cloudtrail_logs WHERE userIdentity.type = 'Root'
AND year='2026' ORDER BY eventTime DESC;
Metric filters: real-time security alert
Top 10 metric filter nên có:
| # | Filter Pattern | Ý nghĩa |
|---|---|---|
| 1 | { $.userIdentity.type = "Root" } | Root activity |
| 2 | { $.eventName = "CreateUser" } | IAM user được tạo (backdoor?) |
| 3 | { $.eventName = "AuthorizeSecurityGroupIngress" } | SG rule thay đổi |
| 4 | { $.eventName = "StopLogging" } | CloudTrail bị tắt |
| 5 | { $.eventName = "DeleteTrail" } | Trail bị xóa |
| 6 | { $.eventName = "PutBucketPolicy" } | Bucket policy thay đổi |
| 7 | { $.eventName = "DisableKey" } | KMS key bị disable (ransomware) |
| 8 | { $.eventName = "DeleteBucket" } | S3 bucket bị xóa |
| 9 | { $.eventName = "DetachPolicy" } | Organization policy bị gỡ |
| 10 | { $.errorCode = "AccessDenied" } | Spike deny request |
aws logs put-metric-filter --log-group-name cloudtrail \
--filter-name "RootUserActivity" \
--filter-pattern '{ $.userIdentity.type = "Root" }' \
--metric-transformations metricName=RootUserActivity,metricNamespace=CloudTrailMetrics,metricValue=1
Mình đã từng cấu hình thiếu metric filter Root activity cho một project, may phát hiện kịp trước audit. Từ đó mình không bao giờ bỏ qua bước này.
CloudTrail Lake vs Athena
| Lake | Athena on S3 | |
|---|---|---|
| Setup | Enable → chọn retention | Tạo table + partition |
| SQL | Cơ bản (không JOIN) | Presto SQL đầy đủ |
| Cost | $2.50/GB ingested + $0.005/GB scanned | Chỉ Athena scan (~$5/TB) |
| Partition | Theo ngày (auto) | Tự chọn schema |
| Dùng khi | Setup nhanh, query đơn giản | Query phức tạp, JOIN, cost-sensitive |
CloudTrail là camera an ninh, nhưng camera không có người xem thì cũng vô dụng. Metric filters + alert là người bảo vệ 24/7.
Bài sau: Phần 6: S3 cơ bản — bucket, object, encryption, static hosting