Secrets management, audit logging và PII masking

4 câu hỏi~25 phútTừ khóa: Vault, KMS, envelope encryption, rotation, dynamic secrets, gitleaks, audit log, hash chain, WORM, PII masking, tokenization

Bài cuối chương: secrets và audit. Mình phát hiện file application-prod.yml có password DB và API key của payment gateway nằm trong git. Giờ làm sao?

1. Bài toán

application-prod.yml (đã commit 8 tháng trước)
  spring.datasource.password: S3cr3t!
  payment.api-key: sk_live_...

Ai có quyền đọc repo (dev cũ, CI, fork) đều có key production.

2. Vấn đề

3. Giải pháp

Xử lý sự cố ngay: coi secret đã lộ. Rotate trước (đổi password DB, thu hồi API key), rồi mới dọn lịch sử git (git filter-repo), kiểm tra log truy cập xem có bị dùng chưa.

Lâu dài: secret không bao giờ nằm trong code.

Thành phầnVai trò
Secret manager (Vault, AWS Secrets Manager, Azure Key Vault)Lưu secret, phân quyền, audit ai đọc, rotate
KMSGiữ master key, mã hóa key khác (envelope encryption); key không rời KMS
Workload identityApp xác thực với vault bằng danh tính của pod/VM, không cần "secret để lấy secret"
Dynamic secretVault cấp user DB riêng, sống vài giờ, tự thu hồi
Secret scanning (gitleaks, GitHub push protection)Chặn commit chứa key

4. Code từng bước

Bước 1: config chỉ tham chiếu

spring:
  datasource:
    url: jdbc:postgresql://db:5432/shop
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
  config:
    import: optional:vault://        # spring-cloud-starter-vault-config
payment:
  api-key: ${PAYMENT_API_KEY}

Bước 2: chặn ở CI

# .pre-commit / CI step
gitleaks detect --source . --redact --exit-code 1

Bước 3: Code Java, audit log có cấu trúc và mask PII:

public record AuditEvent(
        Instant at, String actorId, String actorType,      // USER / SELLER / ADMIN / SYSTEM
        String action, String resource, String resourceId,  // ORDER_REFUND, order, ord_1
        String outcome, String ip, String traceId,
        Map<String, Object> details, String prevHash, String hash) {}

@Service
public class AuditService {
    private final AuditRepository repo;   // bảng append-only, app chỉ có quyền INSERT

    @Transactional
    public void record(String actor, String action, String resId, Map<String, Object> details) {
        String prev = repo.lastHash();
        Map<String, Object> safe = PiiMasker.mask(details);
        String hash = sha256(prev + actor + action + resId + canonicalJson(safe));
        repo.insert(new AuditEvent(Instant.now(), actor, "ADMIN", action, "order", resId,
                "SUCCESS", RequestContext.ip(), RequestContext.traceId(), safe, prev, hash));
    }
}

final class PiiMasker {
    static String phone(String p) { return p == null ? null : "*****" + p.substring(p.length() - 3); }
    static String email(String e) { int i = e.indexOf('@'); return e.charAt(0) + "***" + e.substring(i); }
}

Bước 4: kiểm tra

git log -p | gitleaks detect --pipe          # còn secret trong lịch sử?
curl -s .../admin/orders/ord_1/refunds -X POST ...   # rồi xem bảng audit có bản ghi, phone đã mask
grep -E "Bearer |sk_live_" app.log                   # phải rỗng

5. Cách hoạt động

Secret:
  Pod (service account) ──auth──> Vault ──> DB creds (TTL 1h)
                                   └── audit: ai đọc gì, lúc nào

Envelope encryption:
  data ──mã hóa bằng DEK──> ciphertext
  DEK  ──mã hóa bằng KEK (trong KMS)──> encrypted DEK (lưu cạnh data)
  Rotate KEK: chỉ mã hóa lại DEK, không mã hóa lại toàn bộ data

Audit hash chain (tamper-evident):
  e1.hash = H(e1)   e2.hash = H(e1.hash + e2)   e3.hash = H(e2.hash + e3)
  Sửa/xóa e2 => e3 không khớp => phát hiện được
  + đẩy sang storage WORM / SIEM ngoài tầm tay admin app

6. So sánh: application log vs audit log

Application logAudit log
Mục đíchDebug, vận hànhTrách nhiệm, điều tra, tuân thủ
Nội dungKỹ thuật, có thể ồnAi, làm gì, với cái gì, khi nào, kết quả, từ đâu
Sửa/xóaRotate, xóa sau vài ngàyAppend-only, giữ theo quy định (1-7 năm)
QuyềnDev đọc đượcHạn chế, chính việc đọc cũng được audit
Mất bản ghiChấp nhận đượcKhông chấp nhận (ghi cùng transaction hoặc outbox)

7. Khi nào nên dùng / không nên dùng

Rotate API key của payment gateway thì lúc đổi, các instance cũ vẫn dùng key cũ, instance mới dùng key mới. Làm sao không bị lỗi giữa chừng?

Đúng, đổi "một phát" sẽ có khoảng lỗi. Dùng hai key song song:

1. Tạo key B ở payment gateway (key A vẫn sống)
2. Ghi B vào Vault
3. App reload B (rolling restart hoặc refresh định kỳ)
4. Theo dõi: còn request nào dùng A?   -> không còn
5. Thu hồi A

Chiều ngược lại cũng vậy: khi mình phát API key cho partner, cho phép mỗi partner có 2 key active, để họ tự rotate. Lưu key partner dạng hash (như password), chỉ hiện một lần lúc tạo, có prefix để nhận dạng (shp_live_...) giúp secret scanning tìm được.

Có 20 service, mỗi dev log một kiểu. Mình không thể review từng dòng log.info. Có cách nào chặn PII lọt vào log một cách hệ thống không?

Có, kết hợp nhiều lớp, đừng trông vào kỷ luật từng người:

  1. Kiểu dữ liệu: bọc PII trong type riêng có toString() đã mask, như record Phone(String value) { public String toString() { return "***" + last3; } }. Log nhầm object vẫn an toàn.
  2. Không log body/header thô: filter log request chỉ in method, path, status, latency, traceId; header Authorization, Cookie bị bỏ.
  3. Masking ở logback: pattern converter / JSON encoder thay regex email, phone, số thẻ trước khi ghi.
  4. Ở pipeline log: Fluent Bit/Vector scrub lần nữa, quét cảnh báo nếu phát hiện.
  5. Tokenization cho dữ liệu cần tra cứu: log customerId thay vì email.

Phỏng vấn hỏi quản lý secret và audit logging thì trả lời sao? Tổng kết luôn chương 11 giúp mình.

Q1. Bạn quản lý secrets thế nào?

🇻🇳 Tiếng Việt:

Secret không nằm trong code, git hay image. App đọc từ secret manager như Vault hoặc Key Vault, xác thực bằng workload identity. Ưu tiên secret ngắn hạn, dynamic credential, có rotation tự động và hai key song song khi đổi. CI có secret scanning. Lộ secret thì rotate ngay trước, rồi mới dọn lịch sử.

🇬🇧 English:

Secrets never live in code, git or images. Applications read them from a secret manager like Vault or Key Vault using workload identity. I prefer short-lived or dynamic credentials with automated rotation and two overlapping keys during a switch. CI runs secret scanning. If a secret leaks, rotate it first, then clean the history.

Q2. Audit log khác application log thế nào, làm sao chống sửa?

🇻🇳 Tiếng Việt:

Audit log ghi ai làm gì, với resource nào, khi nào, kết quả và từ đâu, phục vụ điều tra và tuân thủ. Nó append-only, giữ lâu, quyền đọc hạn chế. Để tamper-evident em dùng hash chain, mỗi bản ghi chứa hash của bản trước, và đẩy sang storage WORM hoặc SIEM ngoài quyền admin ứng dụng.

🇬🇧 English:

An audit log records who did what to which resource, when, with what outcome and from where, for investigations and compliance. It is append-only, retained long-term and access-restricted. To make it tamper-evident I chain hashes so each entry includes the previous entry's hash, and ship it to WORM storage or a SIEM outside the application admins' control.

Q3. Bảo vệ PII trong log thế nào?

🇻🇳 Tiếng Việt:

Không log body và header nhạy cảm, bọc PII trong type có toString đã mask, mask bằng regex ở logger và pipeline, log ID thay cho dữ liệu cá nhân. Tuyệt đối không log password, token, số thẻ.

🇬🇧 English:

Do not log raw bodies or sensitive headers, wrap PII in types whose toString is masked, apply regex masking in the logger and the log pipeline, and log identifiers instead of personal data. Never log passwords, tokens or card numbers.

Cách nhớ ngắn gọn

Tổng kết chương 11

BàiChủ đềÝ chính
11.1Resource và contractURL danh từ, action là sub-resource, cursor pagination, OpenAPI contract-first + lint
11.2Method, PATCH, statusSafe/idempotent, PUT thay hết, Merge Patch, cây quyết định 400/409/412/422
11.3Lỗi và idempotencyRFC 9457 + code ổn định, Idempotency-Key (khác body → 422), retry có jitter
11.4VersioningExpand-contract, URL major version, Deprecation/Sunset, tolerant reader
11.5Browser securitySOP, CORS không phải khiên, CSRF + SameSite, escape trước CSP sau
11.6OWASP API Top 10BOLA/BFLA/BOPLA, giới hạn tài nguyên, SSRF, inventory
11.7Secrets và auditVault/KMS, rotation, audit append-only tamper-evident, PII masking

Tiếp theo: xác thực tập trung với Keycloak.


Tự kiểm tra

Phát hiện API key trong git, bước đầu tiên là gì?

Rotate/thu hồi key. Dọn lịch sử git là bước sau, vì key có thể đã bị copy.

Envelope encryption giúp gì khi rotate key?

Chỉ cần mã hóa lại DEK bằng KEK mới, không phải mã hóa lại toàn bộ dữ liệu.

Hash chain trong audit log phát hiện được gì?

Sửa hoặc xóa một bản ghi làm hash các bản sau không khớp.

Một audit event tối thiểu cần những gì?

Thời điểm, actor, action, resource + ID, kết quả, nguồn (IP), traceId.

API key cấp cho partner nên lưu thế nào?

Lưu hash, chỉ hiện một lần, có prefix nhận dạng, cho phép 2 key active để rotate.