ADR-0003. Mã hóa AES-256-GCM cho credential nhà cung cấp khi lưu trữ
| Trường | Giá trị |
|---|---|
| Trạng thái | Accepted |
| Ngày | 2026-02-12 |
| Người quyết định | payment-team, security |
| Thay thế | — |
Bối cảnh
- Credential nhà cung cấp thanh toán (VNPAY appId/secrets, token theo merchant) có độ nhạy cảm cao — rò rỉ cho phép thanh toán trái phép.
- Lưu trữ chúng dạng plaintext trong bảng
Configurationlà không chấp nhận được. - Chúng tôi cần cân bằng giữa bảo mật và tính đơn giản vận hành (chúng tôi chưa ở quy mô yêu cầu HSM/KMS).
Quyết định
Tất cả credential thanh toán được mã hóa khi lưu trữ bằng AES-256-GCM qua CryptoUtility (từ @nx/core). Khóa mã hóa được dẫn xuất từ APP_ENV_APPLICATION_SECRET.
Chi tiết:
- Cột
Configuration.credentialđược ẩn khỏi các thao tác đọc CRUD chuẩn (drizzle hidden field). PaymentConfigurationService.getPaymentCredentialtruy cập cột trực tiếp qua drizzle connector và giải mã inline.- Cùng secret mã hóa các config payment không phải credential trong
tValue(ví dụmasterMerchantCode,appIdcủa VNPAY). - Tất cả pod (API + WORKER) PHẢI chia sẻ
APP_ENV_APPLICATION_SECRET.
Hệ quả
| Ưu điểm | Nhược điểm |
|---|---|
| Chỉ có dump database không làm rò rỉ credential | Mô hình một secret — rò rỉ secret = compromise toàn bộ |
| Mã hóa xác thực (chế độ GCM) ngăn tampering | Xoay vòng secret yêu cầu mã hóa lại mọi row credential (thủ công) |
| Giải mã in-process — sub-millisecond | Không có cô lập KMS theo từng merchant |
| Primitive tiêu chuẩn, đã được audit kỹ | App secret phải lưu trong K8s secret với RBAC nghiêm ngặt |
Quy trình xoay vòng
- Sinh secret mới.
- Đọc tất cả row
Configurationcó credential, giải mã bằng secret cũ. - Mã hóa lại bằng secret mới.
- Roll deployment với env mới.
Việc xoay vòng PHẢI được điều phối — không có trạng thái half-rolled nơi các pod có secret khác nhau.
Phương án thay thế đã cân nhắc
| Lựa chọn | Ưu điểm | Nhược điểm | Lý do từ chối |
|---|---|---|---|
| HashiCorp Vault / AWS KMS | Khóa per-credential, xoay vòng tích hợp sẵn | Vendor lock-in, chi phí ops, độ trễ | Quá sớm cho quy mô của chúng tôi |
| Bất đối xứng (RSA per merchant) | Cô lập theo merchant | Phức tạp; bùng nổ khóa | Không cần thiết với threat model hiện tại |
| Plaintext + DB ACL | Đơn giản | Không chấp nhận được — DB ops có thể đọc credential | Không |
Tham chiếu
core/src/utilities/crypto.utility.ts(CryptoUtility)services/payment-configuration.service.ts:43-176(vị trí giải mã)core/src/models/schemas/public/configuration/schema.ts(cộtcredentialhidden)