Auditor SOC2 không hỏi “có security không”. Họ hỏi: “Tháng 3, lúc 02:14 UTC, user [email protected] đọc index customer-orders-*, chứng minh đi”. Nếu không trả lời được trong dưới 24h thì control FAIL. Audit log là bằng chứng duy nhất. Phải bật từ trước, không thể recreate.
Đây là bài 15 trong series Kibana từ A đến Z. Sau bài này bạn sẽ làm được:
- Bật ES audit log và Kibana audit log đúng cách
- Chọn event quan trọng, drop event noise
- Ship audit log sang index riêng, giữ 1 năm theo ILM
- Trả lời 6 câu hỏi auditor SOC2 hay hỏi bằng KQL
- Tránh pitfall audit log full disk và mất event do buffer
Phần 1: Hai luồng audit khác nhau
Elastic stack có 2 audit log độc lập:
| Audit | Ghi gì | Output |
|---|---|---|
| Elasticsearch audit | Auth, authorization, security config, index access | File JSON trên node ES |
| Kibana audit | UI action: save dashboard, copy object, login Kibana | File JSON trên Kibana host |
Không bật cả hai = mù một nửa. Auth thất bại ghi ở ES audit. Dashboard bị xoá ghi ở Kibana audit. Cả hai phải tồn tại để có bức tranh đầy đủ.
Yêu cầu license: audit log thuộc nhóm Enterprise feature. Basic license không có. Check GET /_xpack trước khi cài.
Phần 2: Bật Elasticsearch audit
Trong elasticsearch.yml của MỌI node:
xpack.security.audit.enabled: true
xpack.security.audit.logfile.events.include:
- access_denied
- access_granted
- anonymous_access_denied
- authentication_failed
- authentication_success
- connection_denied
- tampered_request
- run_as_denied
- run_as_granted
- security_config_change
xpack.security.audit.logfile.events.exclude:
- access_granted # noise nếu cluster busy
xpack.security.audit.logfile.events.emit_request_body: false
Lưu ý:
access_grantedghi MỌI thao tác đọc được phép. Cluster busy ghi vài triệu event/ngày. Cân nhắc exclude khi traffic lớn, chỉ giữaccess_deniedplus auth event.emit_request_body: trueghi cả body request. Hữu ích để forensic nhưng có thể leak data nhạy cảm. Default tắt là đúng.
Restart node theo rolling. File audit xuất hiện ở logs/<cluster_name>_audit.json (path tuỳ install).
Sample event
{
"type": "audit",
"timestamp": "2026-05-17T02:14:33.512+0000",
"cluster.name": "prod",
"cluster.uuid": "abc-123",
"node.name": "es-node-1",
"event.type": "ip_filter",
"event.action": "access_granted",
"authentication.type": "REALM",
"user.name": "alice",
"user.realm": "azure_ad",
"user.roles": ["tenant_a_reader"],
"origin.type": "rest",
"origin.address": "10.0.1.42:54321",
"request.id": "req-xyz",
"request.method": "POST",
"url.path": "/customer-orders-*/_search",
"url.query": "size=10"
}
Field event.action, user.name, url.path, origin.address là 4 field truy vấn nhiều nhất.
Phần 3: Bật Kibana audit
Trong kibana.yml:
xpack.security.audit.enabled: true
xpack.security.audit.appender:
type: rolling-file
fileName: /var/log/kibana/audit.log
policy:
type: time-interval
interval: 24h
strategy:
type: numeric
pattern: -%i
max: 10
layout:
type: json
Audit log Kibana được giữ 10 file, mỗi file 24h.
Sample event
{
"@timestamp": "2026-05-17T02:14:33.512Z",
"message": "User is changing dashboard",
"event": {
"action": "saved_object_update",
"category": ["database"],
"type": ["change"],
"outcome": "success"
},
"kibana": {
"saved_object": { "type": "dashboard", "id": "abc-123" },
"space_id": "prod"
},
"user": {
"name": "alice",
"roles": ["kibana_admin"]
},
"http": {
"request": { "method": "put" },
"headers": { "x-forwarded-for": "203.0.113.10" }
}
}
Action phổ biến: user_login, user_logout, saved_object_create, saved_object_update, saved_object_delete, space_copy_saved_objects, failure, success.
Phần 4: Ship audit log vào ES (đệ quy nhưng đúng)
Audit log nằm trên filesystem node là khó query. Ship vào index ES audit-* bằng Filebeat hoặc Elastic Agent.
Filebeat config:
filebeat.inputs:
- type: filestream
id: es-audit
paths:
- /var/log/elasticsearch/*_audit.json
parsers:
- ndjson:
target: ""
add_error_key: true
fields:
audit_source: elasticsearch
- type: filestream
id: kibana-audit
paths:
- /var/log/kibana/audit.log*
parsers:
- ndjson:
target: ""
add_error_key: true
fields:
audit_source: kibana
output.elasticsearch:
hosts: ["https://es.example.com:9200"]
index: "audit-%{+yyyy.MM.dd}"
api_key: "${AUDIT_API_KEY}"
setup.template.name: "audit"
setup.template.pattern: "audit-*"
setup.ilm.enabled: false # set ILM riêng cho audit, không dùng filebeat default
API key cho Filebeat audit chỉ cần create_doc trên audit-*. Quyền tối thiểu để tránh Filebeat bị compromise rồi xoá audit.
Chống tampering
Vì audit là pháp y, người được audit không được xoá log của chính họ. Hai biện pháp:
- Audit index lưu cluster khác (forwarder bằng cross-cluster replication hoặc Logstash với output qua HTTPS một chiều).
- Snapshot audit index hàng ngày sang S3 immutable bucket với object lock.
Snapshot S3 immutable là tiêu chuẩn vàng cho SOC2 evidence. Bài 17 sẽ đi sâu.
Phần 5: ILM cho audit log
Audit cần giữ tối thiểu 1 năm theo SOC2 (12 tháng rolling window). Policy mẫu:
PUT _ilm/policy/audit-policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_age": "30d",
"max_primary_shard_size": "30gb"
},
"set_priority": { "priority": 100 }
}
},
"warm": {
"min_age": "30d",
"actions": {
"shrink": { "number_of_shards": 1 },
"forcemerge": { "max_num_segments": 1 },
"set_priority": { "priority": 50 }
}
},
"cold": {
"min_age": "90d",
"actions": {
"freeze": {},
"set_priority": { "priority": 0 }
}
},
"delete": {
"min_age": "395d",
"actions": { "delete": {} }
}
}
}
}
13 tháng để dư margin cho audit window. Index template gắn policy này:
PUT _index_template/audit-template
{
"index_patterns": ["audit-*"],
"data_stream": {},
"template": {
"settings": {
"index.lifecycle.name": "audit-policy",
"index.lifecycle.rollover_alias": "audit"
}
}
}
Bài 16 sẽ đi sâu về ILM (hot/warm/cold/shrink).
Phần 6: Sáu câu hỏi auditor hay hỏi với KQL sẵn
Câu 1: User X đã login bao nhiêu lần tháng trước?
event.action : "user_login" and user.name : "[email protected]"
Bật Discover, time range “Last 30 days”, count records.
Câu 2: Có lần auth failed nào của user X không?
event.action : "authentication_failed" and user.name : "[email protected]"
Câu 3: User X đã đọc index Y khi nào?
event.action : "access_granted"
and user.name : "[email protected]"
and url.path : *customer-orders*
Câu 4: Có ai từng truy cập từ IP ngoài VPN?
event.action : ("access_granted" or "authentication_success")
and not origin.address : "10.0.0.0/8"
and not origin.address : "172.16.0.0/12"
Subnet VPN tuỳ tổ chức. Lưu lại Saved Search này là báu vật.
Câu 5: Ai xoá dashboard Error Overview?
event.action : "saved_object_delete"
and kibana.saved_object.id : "<dashboard-id>"
Câu 6: Có ai từng thay đổi role hoặc API key?
event.action : "security_config_change"
Hoặc cụ thể hơn:
event.action : "security_config_change"
and url.path : (*role* or *api_key*)
Phần 7: Build dashboard “Audit Overview”
Panel đề xuất:
| Panel | Visualization | Filter |
|---|---|---|
| Total auth failures (24h) | Metric | event.action : "authentication_failed" |
| Top users failed login | Pie | event.action : "authentication_failed", slice by user.name |
| Access denied over time | Bar | event.action : "access_denied", bucket @timestamp 1h |
| Sensitive index access | Table | event.action : "access_granted" and url.path : *customer-orders*, columns user.name, origin.address, @timestamp |
| Security config changes | Table | event.action : "security_config_change" |
| Dashboard mutations | Table | event.action : ("saved_object_update" or "saved_object_delete") and kibana.saved_object.type : "dashboard" |
Export NDJSON dashboard, commit vào repo compliance-evidence/. Auditor có URL tĩnh xem được, không cần share account Kibana.
Phần 8: Pitfall hay gặp
Pitfall 1: bật audit rồi quên rotation
Audit log không tự rotate nếu chỉ dùng file appender mặc định. File có thể lên 50GB sau 1 tháng. Disk full plus node crash. Fix: dùng rolling-file appender với policy time-interval plus numeric strategy max: 10, hoặc rely on logrotate host-level.
Pitfall 2: include event quá nhiều
Bật full events.include cho cluster traffic cao = audit log size lớn hơn log ứng dụng. Một team tôi từng thấy ghi 200GB audit/ngày. Fix: exclude access_granted, chỉ giữ access_denied plus auth event. Bù lại bằng cách bật slowlog cho query bất thường.
Pitfall 3: ship audit log dùng API key superuser
Filebeat compromised mà có key superuser thì attacker xoá luôn audit. Key dành cho Filebeat audit chỉ cần create_doc trên audit-*. Không cấp delete, không cấp read.
Pitfall 4: thiếu chứng minh tampering
Audit nằm cùng cluster với data = audit có thể bị admin sửa. SOC2 hỏi “chứng minh audit chưa bị sửa”. Cách trả lời: snapshot S3 object lock hàng ngày. Snapshot SHA-256 plus timestamp là evidence.
Pitfall 5: timezone lệch
ES audit ghi UTC. Kibana hiển thị theo browser timezone. Khi paste evidence cho auditor luôn quy về UTC để khớp với ES log. Ghi rõ “All timestamps in UTC” trong report.
Phần 9: Pattern viết runbook compliance
Tạo file compliance/runbook.md:
# Audit Evidence Runbook
## Câu hỏi 1: User X login khi nào?
Index: audit-*
KQL: event.action : "user_login" and user.name : "<X>"
Saved Search: AuditUserLogin
## Câu hỏi 2: User X truy cập index Y?
KQL: event.action : "access_granted"
and user.name : "<X>"
and url.path : *<Y>*
Saved Search: AuditUserIndexAccess
...
Khi auditor hỏi, on-call mở runbook, copy KQL, đổi parameter, export CSV từ Discover (Share plus CSV reports). Tổng thời gian trả lời under 10 phút.
Cheatsheet
| Việc | Cách |
|---|---|
| Bật ES audit | xpack.security.audit.enabled: true trong elasticsearch.yml |
| Bật Kibana audit | xpack.security.audit.enabled: true trong kibana.yml |
| Format JSON | Kibana audit layout.type: json |
| Ship vào ES | Filebeat filestream, parser ndjson |
| Retention 1 năm | ILM delete.min_age: 395d |
| Chống tampering | Snapshot S3 object lock hàng ngày |
| Audit user login | event.action : "user_login" |
| Audit access denied | event.action : "access_denied" |
| Audit config change | event.action : "security_config_change" |
| Audit object delete | event.action : "saved_object_delete" |
Lời kết
Audit log là tài sản pháp lý, không phải metric. Setup phải làm trước khi auditor đến. Khi rồi mới bật là không kịp (audit log từ tháng trước không recreate được). Tuân thủ 3 nguyên tắc: bật cả hai (ES plus Kibana), ship sang storage độc lập, giữ tối thiểu 13 tháng.
Bài 16 mở Part 5 Production Operations với Index Lifecycle Management (ILM): hot/warm/cold/delete phase, shrink, force-merge và rollover. Đây là cơ chế nền cho mọi index trong cluster, bao gồm cả audit-* mà bài này vừa setup.