41 KiB
section, title, status, version, reviewer_notes
| section | title | status | version | reviewer_notes |
|---|---|---|---|---|
| 08 | Thiết kế bảo mật | approved | 2 |
8. Thiết kế bảo mật (Security Design)
Vai trò của mục này: rà soát chéo (cross-cutting review) trên các quyết định đã có ở mục 3 (kiến trúc), 4 (API), 5 (dữ liệu), 6 (luồng xử lý) — không thiết kế lại các mục đó. Mọi thiếu sót phát hiện được liệt kê ở §8.5 và trong
findingscủa structured output để orchestrator cho chạy lại đúng mục.Đầu vào:
00-project-brief.md(profile:scale=large,hasPayment=true,hasPII=true,platforms=["web"], tuân thủ NĐ52/85, NĐ13/2023, PCI-DSS scope giảm, cloud=AWS, ngân sách/timeline chưa xác định),02-phan-tich-yeu-cau.md(NFR-04 Bảo mật, NFR-05 Tuân thủ),03-kien-truc.md(WAF/ALB/API Gateway, cô lập Payment Service, S3 mã hoá KYC, database-per-service),04-api-design.mdv3 (JWT Bearer, quy tắc ownership 4.1.1, mã lỗi403 ERR_FORBIDDEN_OWNERSHIP/409 ERR_ACCOUNT_LINK_REQUIRED, chống replay webhook,Idempotency-Keymở rộng),05-thiet-ke-du-lieu.mdv3 (cột [PII]/[Payment], cột chống brute-forceuser_account.failed_login_count/locked_until/last_failed_login_at, bảngaudit_logtại Audit & Compliance Service — 5.2.11),06-luong-xu-ly.mdv2 (luồng checkout/payment/KYC/payout/dispute/login đã cập nhật pre-signed URL KYC, kênh payout, sự kiện audit, nhánh khoá tài khoản).Right-sizing: vì
hasPayment=truevàhasPII=true, cả 4 mảng bảo mật (xác thực, bảo vệ dữ liệu, OWASP, tuân thủ) đều áp dụng đầy đủ, không có phần "không áp dụng" — riêng phạm vi PCI-DSS được thu hẹp (không lưu số thẻ, giao VNPay/Momo xử lý — xem §8.4). Không đề xuất công nghệ/ngân sách vượt ràng buộc mục 1 (cloud AWS, không SSO doanh nghiệp, ngân sách/timeline chưa xác định) — các đề xuất bên dưới đều dùng dịch vụ AWS chuẩn (KMS, Secrets Manager, WAF, GuardDuty, CloudTrail) hoặc thư viện mã nguồn mở; các hạng mục phát sinh chi phí đáng kể được ghi chú trade-off riêng.(v2 — revision đồng bộ mục 4 v3/5 v3/6 v2, chỉ sửa tối thiểu): (a) §8.1.1 bổ sung chính sách khoá tài khoản (account lockout policy) cụ thể — ngưỡng
failed_login_count, thời lượnglocked_untiltheo vai trò, cách reset — để mục 4 dùng khi bổ sung mã lỗi423 ERR_ACCOUNT_LOCKED; (b) §8.2 bổ sung mục 8.2.5 quyết định về redact PII trongaudit_log.before_json/after_json, kiểm soát truy cập đọcaudit_log, và retention; (c) §8.1.2/8.2/8.3 cập nhật tham chiếu sang mục 4 v3 (quy ước ownership 4.1.1, mã lỗi mới) và mục 5 v3 (cột mới, bảngaudit_log); (d) §8.5 rà soát lại 10 finding v1 — đánh dấu finding đã giải quyết ở mục 4 v3/5 v3/6 v2, giữ lại finding chưa xử lý (F10 — MSK ACL, mục 3) và bổ sung 2-3 finding mới phát sinh từ mục 6 v2 (endpoint xem KYC document, mã lỗi khoá tài khoản — mục 4 đã hết vòng sửa, ghi nhận thủ công).
8.1 Xác thực & phân quyền (Authentication & Authorization)
Cơ chế JWT Bearer/OAuth2/scope theo actor đã được đặc tả ở mục 4.2 — không lặp lại, chỉ dẫn chiếu và bổ sung chi tiết triển khai (mục 4.2 v3 đã ghi rõ "cơ chế MFA chi tiết... chính sách khoá tài khoản thuộc mục 8").
8.1.1 Xác thực (Authentication)
| Hạng mục | Thiết kế | FR/Ghi chú |
|---|---|---|
| Mật khẩu | bcrypt/argon2id (đã có cột password_hash mục 5.2.1), độ dài tối thiểu 10 ký tự, kiểm tra chống mật khẩu rò rỉ (breach list, VD thư viện zxcvbn/HIBP k-anonymity API), không giới hạn ký tự đặc biệt |
FR-01 |
| Chống brute-force (account lockout) | Đã triển khai đủ cột hỗ trợ ở mục 5 v3 (user_account.failed_login_count/locked_until/last_failed_login_at) và luồng ở mục 6.1.6 v2 (tăng đếm khi sai, khoá khi vượt ngưỡng, reset khi đăng nhập thành công) — chính sách cụ thể (ngưỡng/thời lượng theo vai trò) chốt tại §8.1.1a bên dưới, kết hợp với rate-limit theo IP + captcha đã có ở mục 4.2 (defense-in-depth 2 lớp: theo tài khoản + theo IP) |
FR-01, FR-27 |
| JWT | Access token TTL 15-60 phút (đã chốt mục 4.2), refresh token TTL 7-30 ngày với refresh token rotation — mỗi lần refresh phát hành token mới, phát hiện tái sử dụng token cũ (reuse detection) → thu hồi toàn bộ chuỗi token của phiên đó (chống token bị đánh cắp dùng lại) | FR-01 |
| Lưu trữ token phía client | Khuyến nghị: access token giữ trong bộ nhớ (memory) của SPA, refresh token trong cookie HttpOnly; Secure; SameSite=Lax/Strict (không dùng localStorage cho refresh token để giảm rủi ro XSS đánh cắp token dài hạn); nếu dùng cookie cho access token, bắt buộc thêm CSRF token (double-submit cookie) cho mọi request ghi |
FR-01, FR-27 — openQuestion: mục 7 (UI) chưa xác nhận cơ chế lưu token cụ thể, cần đồng bộ khi thiết kế frontend |
| MFA (FR-27) | TOTP (RFC 6238, ưu tiên hơn SMS OTP do rủi ro SIM-swap) bắt buộc cho role=platform_admin, khuyến khích cho seller; cấp 10 mã backup dùng một lần khi enroll; endpoint /v1/auth/mfa/enroll, /v1/auth/mfa/challenge đã có ở mục 4 — bổ sung: giới hạn 5 lần thử OTP sai/challenge token, challenge token TTL ngắn (≤5 phút) |
FR-27, BR-12 |
| OAuth2 Social login (FR-02) | Đã triển khai ở mục 4 v3 (POST /v1/auth/oauth/{provider}/callback): xác thực tham số state (400 ERR_OAUTH_STATE_INVALID nếu thiếu/không khớp), xác minh id_token issuer/audience/expiry phía server trước khi tạo OAuthIdentity, và không tự động liên kết (no auto-merge) khi email trùng tài khoản email/password đã tồn tại — trả 409 ERR_ACCOUNT_LINK_REQUIRED, yêu cầu xác minh sở hữu email trước khi merge tài khoản (chống account takeover) |
FR-02 |
| Session/logout | Refresh token bị thu hồi (đưa vào denylist Redis theo jti tới khi hết TTL) khi logout, đổi mật khẩu, hoặc Admin khoá tài khoản; đăng xuất tất cả thiết bị là hành động tuỳ chọn cho Customer (nice-to-have, không bắt buộc MVP) |
FR-01 |
8.1.1a Chính sách khoá tài khoản (Account Lockout Policy) — v2
Chốt theo yêu cầu người duyệt: quy định ngưỡng số lần đăng nhập sai và thời lượng khoá dựa trên cột
user_account.failed_login_count/locked_until/last_failed_login_at(mục 5.2.1 v3), phục vụ luồng 6.1.6 v2 và để mục 4 bổ sung mã lỗi423 ERR_ACCOUNT_LOCKEDkhi có vòng sửa tiếp theo.
Vai trò (user_account.role) |
Ngưỡng failed_login_count |
Thời lượng khoá (locked_until) |
Lý do khác biệt |
|---|---|---|---|
customer |
5 lần sai liên tiếp | now + 15 phút | Số đông người dùng, ưu tiên trải nghiệp; kết hợp captcha sau 3 lần sai (đã có mục 4.2) giảm rủi ro trước khi chạm ngưỡng khoá |
seller |
5 lần sai liên tiếp | now + 15 phút | Cùng mức Customer; MFA khuyến khích (không bắt buộc) nên lockout theo mật khẩu là lớp phòng thủ chính |
platform_admin |
3 lần sai liên tiếp | now + 30 phút | Quyền hạn cao nhất (scope admin:*) → ngưỡng thấp hơn, thời lượng khoá dài hơn Customer/Seller; rủi ro DoS (kẻ tấn công cố tình khoá tài khoản Admin đã biết email) được giảm thiểu vì Admin Backoffice chỉ truy cập qua VPN/IP allowlist (mục 3.3) — kẻ tấn công ngoài mạng nội bộ không gọi được /v1/auth/login với role Admin để kích hoạt khoá |
ops_staff, csr |
5 lần sai liên tiếp | now + 15 phút | Không có scope admin:* toàn cục; áp dụng như Customer/Seller là đủ, tránh phức tạp hoá chính sách không cần thiết |
Cơ chế cập nhật (áp dụng tại POST /v1/auth/login, khớp sequence 6.1.6 v2):
- Trước khi so khớp mật khẩu: nếu
locked_untilđã được đặt vàlocked_until > now→ từ chối ngay, không so khớp mật khẩu (tránh side-channel timing), trả về mã lỗi tài khoản đang tạm khoá (đề xuất423 ERR_ACCOUNT_LOCKED— xem finding mục 4 ở §8.5) kèm thông tin thời điểm có thể thử lại (retryAfter), không tiết lộ email có tồn tại hay không trong thông báo lỗi. - Nếu
locked_untilđã qua (now ≥locked_until) tại lần thử tiếp theo: coi như tự động mở khoá — resetfailed_login_count = 0trước khi đánh giá mật khẩu của lần thử hiện tại (không cộng dồn từ chuỗi thất bại trước khi khoá), tránh khoá lặp vô hạn nhưng vẫn đánh giá công bằng lần thử mới. - Mật khẩu sai:
failed_login_count += 1,last_failed_login_at = now; nếufailed_login_countvượt ngưỡng theo vai trò ở bảng trên → đặtlocked_until = now + thời lượng tương ứng. - Mật khẩu đúng (dù trước đó có sai một vài lần chưa chạm ngưỡng): reset
failed_login_count = 0,last_failed_login_at = NULL— không giữ lại lịch sử thất bại cũ sau khi xác thực thành công (đã khớp sequence 6.1.6 v2). - Không có endpoint tự mở khoá sớm cho chính người dùng ở MVP (đợi hết
locked_until); trường hợp khẩn cấp (Customer/Seller liên hệ CSKH vì bị khoá do thao tác nhầm) xử lý thủ công qua nghiệp vụ vận hành nội bộ (CSR/Admin sửa trực tiếplocked_until=NULLqua công cụ nội bộ có kiểm soát, không qua API công khai) — không đề xuất thêm endpoint mới ở mục 4 cho luồng này vì tần suất thấp, tránh mở rộng bề mặt tấn công không cần thiết ở MVP. - Khuyến nghị bổ sung (không bắt buộc): khi tài khoản chuyển sang
locked_untillần đầu trong một khoảng thời gian, gửi thông báo email cho chủ tài khoản qua Notification Service (kênh sẵn có, chi phí không đáng kể) để cảnh báo khả năng bị dò mật khẩu — không chặn luồng chính nếu gửi thất bại.
Mã lỗi đề xuất cho mục 4 (chưa có ở mục 4 v3, xem finding §8.5): 423 ERR_ACCOUNT_LOCKED — "Tài khoản tạm khoá do đăng nhập sai nhiều lần", response kèm retryAfterSeconds (tính từ locked_until - now), phân biệt với 401 ERR_AUTH_REQUIRED/ERR_AUTH_INVALID_TOKEN (thiếu/sai token) và với thông báo sai email/mật khẩu thông thường (vẫn trả 401 chung chung không phân biệt "email không tồn tại" hay "sai mật khẩu" để tránh dò email hợp lệ — chỉ riêng lockout mới lộ trạng thái "đã bị khoá", chấp nhận đánh đổi UX vs. ẩn thông tin vì mức độ rủi ro thấp hơn lộ email tồn tại hay không).
8.1.2 Phân quyền (Authorization) — RBAC + kiểm soát ownership (ABAC nhẹ)
- RBAC theo scope: giữ nguyên bảng scope/actor đã chốt ở mục 4.2 (
customer:*,seller:*,admin:*,ops:*,csr:*) — Identity & Access Service là nguồn phát hành duy nhất, API Gateway/BFF enforce tại tầng biên trước khi route vào service nội bộ. - Kiểm soát ownership (resource-level, bắt buộc ở tầng service, không chỉ ở Gateway) — đã chốt thành quy ước chính thức tại mục 4.1.1 v3 ("Quy tắc ownership (chống IDOR)"): mọi endpoint có tham số định danh tài nguyên gắn với một Customer/Seller cụ thể phải đối chiếu
sub/customerId/sellerIdtrong JWT trước khi trả dữ liệu, vi phạm →403 ERR_FORBIDDEN_OWNERSHIP(phân biệt với403 ERR_FORBIDDEN_SCOPEkhi thiếu quyền/scope). Mục 8 xác nhận và bổ sung chi tiết theo từng nhóm actor:- Customer: mọi truy vấn
order,cart,loyalty,wishlist,return-requestsphải so khớpcustomerIdtrong JWTsubclaim vớicustomer_idcủa resource — đã áp dụng đúng tạiGET/POST /v1/orders/{orderId},GET /v1/payments/{paymentId}(mục 4.1.5, 4.1.6 v3). - Seller: so khớp
sellerIdclaim vớiseller_idcủaproduct,order_seller,payout—GET /v1/seller/orders,GET /v1/seller/payouts(mục 4.1.5, 4.1.8 v3) tự lọc theosellerIdtrong JWT, không nhậnsellerIdqua query param. - CSR: chỉ thao tác
disputeđãassigned_csr_id= chính mình hoặc chưa gán (open), không được sửa dispute đã gán cho CSR khác trừ khi Admin escalate — mục 4 hiện thiết kế truy cậpDisputetoàn cục theo scopecsr:disputes:*(không áp dụng ownership vì CSR xử lý tranh chấp toàn sàn theo phân công nội bộ); khuyến nghị bổ sung ràng buộcassigned_csr_idở tầng business logic (không phải lỗi thiết kế API, mà là rule nghiệp vụ nội bộ — không tạo finding mới vì không phải IDOR giữa các Customer/Seller khác nhau). - Ops: giới hạn theo đơn hàng/khu vực được phân công (đã ghi nhận là "chi tiết RBAC ở mục 8" tại mục 4.2) — triển khai qua bảng phân công (assignment) tại Shipping & Fulfillment Service, kiểm tra trước khi cho phép
PATCH /v1/ops/orders/{orderId}/fulfillment. - Shipment tracking (
GET /v1/shipments/{shipmentId}/tracking): đã được vá ở mục 4.1.12 v3 — kiểm tracustomerId/sellerIdliên quan hoặc scopeops:*/admin:*toàn cục, trả403 ERR_FORBIDDEN_OWNERSHIPnếu không khớp (trước đây là Finding F1, nay đã giải quyết — xem §8.5).
- Customer: mọi truy vấn
- Admin/Ops Backoffice: giới hạn mạng qua VPN/IP allowlist (đã quyết định ở mục 3.3) + bắt buộc MFA (role
platform_admin) là 2 lớp phòng thủ độc lập (defense-in-depth); không cấp quyền truy cập DB Production trực tiếp trừ khẩn cấp có phê duyệt (đã ghi ở mục 3.3, giữ nguyên). - Nguyên tắc chung: mọi endpoint ghi dữ liệu (
POST/PUT/PATCH/DELETE) đều phải qua middleware kiểm tra scope và ownership trước khi vào business logic — khuyến nghị triển khai như một lớp policy tập trung (VD OPA/Open Policy Agent hoặc middleware dùng chung trong BFF) để tránh mỗi service tự implement khác nhau và bỏ sót.
8.2 Bảo vệ dữ liệu (Data Protection)
Dựa trực tiếp trên danh sách cột [PII]/[Payment] đã đánh dấu ở mục 5.5 v3 — bảng dưới xác nhận biện pháp cụ thể cho từng nhóm, không lặp lại toàn bộ danh sách cột.
8.2.1 Mã hoá at-rest
| Nhóm dữ liệu | Biện pháp | Ghi chú |
|---|---|---|
| Toàn bộ RDS PostgreSQL (database-per-service) | Mã hoá at-rest bằng AWS KMS (encryption at rest cấp instance/storage), khoá riêng theo service hoặc theo nhóm mức nhạy cảm (Payment/Commission/Seller/Audit & Compliance dùng CMK riêng, tách khỏi Review/Notification) | NFR-04, NFR-05 |
Cột nhạy cảm cao: seller_bank_account.account_number, mfa_device.secret_encrypted, seller.tax_code, seller.business_license_number |
Mã hoá tầng ứng dụng (application-level, AES-256-GCM) bổ sung, khoá quản lý qua KMS envelope encryption — giảm rủi ro nếu bị SQL injection đọc thẳng DB hoặc nhân sự nội bộ (DBA) truy cập trực tiếp không qua ứng dụng | Khớp đề xuất mục 5.5; đây là control bổ sung so với mã hoá at-rest mặc định của RDS |
| S3 (ảnh KYC, ảnh sản phẩm) | SSE-KMS, bucket KYC tách riêng, không public, versioning + cross-region replication (đã chốt mục 5.3.2); truy cập Admin xem tài liệu KYC qua pre-signed URL TTL ≤5 phút — đã triển khai ở mục 6.1.5 v2 (Admin gọi Seller Management Service để sinh viewUrl, không truy cập trực tiếp object storage) |
FR-17 — endpoint cụ thể (GET .../kyc-documents/{documentId}/view-url) chưa có ở mục 4 v3, xem finding §8.5 |
PII còn lại (email, phone, full_name, địa chỉ) |
Mã hoá at-rest theo KMS mặc định của RDS là đủ (không cần application-level do tần suất truy vấn cao, đánh đổi hiệu năng) | Khớp mục 5.5 |
user_account.failed_login_count/locked_until/last_failed_login_at (mục 5.2.1 v3) |
Không phải PII/Payment nhưng là dữ liệu bảo mật nhạy cảm — mã hoá at-rest mặc định của RDS là đủ; kiểm soát ghi chỉ qua luồng xác thực nội bộ (Identity & Access Service), không expose qua bất kỳ API đọc công khai nào (khớp ghi chú mục 5.5 v3) | FR-01, FR-27 |
8.2.2 Mã hoá in-transit & quản lý secret
- TLS 1.2+ bắt buộc cho mọi kết nối: Client ↔ CDN/WAF/ALB, ALB ↔ API Gateway/BFF, BFF ↔ service nội bộ; bật HSTS ở tầng CDN/ALB.
- Secret/key management: AWS Secrets Manager cho DB credentials, API key/secret VNPay/Momo/GHN/GHTK, OAuth client secret, SMTP/SMS provider key — không hard-code trong code/CI/CD; rotation tự động cho DB credentials, rotation thủ công có lịch (khuyến nghị 90 ngày) cho API key bên thứ ba (phụ thuộc khả năng rotate của từng đối tác).
- Payout batch file (chứa
seller_bank_account.account_number,account_holder_name): đã có hướng dẫn kênh truyền ở mục 6.1.4 v2 (SFTP + mã hoá PGP hoặc API HTTPS của ngân hàng đối tác — ngân hàng cụ thể chưa chốt, ghi nhận là giả định/openQuestion tại mục 6, không phải finding bảo mật còn tồn đọng); không qua email trong mọi trường hợp. - Message broker (Kafka/MSK): bật mã hoá in-transit (TLS) + ACL theo topic, đặc biệt các event chứa PII/tài chính (
OrderDelivered,PaymentConfirmed,PayoutScheduled, và các domain event ghiaudit_lognhưKycDocumentVerified/DisputeResolved/PayoutRetried/SellerLockedmục 5.2.11 v3) — chỉ consumer service liên quan được subscribe — vẫn là finding chưa xử lý vì mục 3 (kiến trúc) chưa cập nhật, xem Finding F10 (§8.5, mục 3, low, không đổi so với v1).
8.2.3 Masking & giảm thiểu lộ dữ liệu
- Log/APM/tracing: mọi log ứng dụng (CloudWatch Logs, APM traces) phải qua log-scrubber middleware để masking
email(c***@domain.com),phone(ẩn 4 số giữa),account_number(chỉ hiện 4 số cuối), không logpassword,secret_encrypted,gateway_transaction_refđầy đủ ở mức DEBUG trên môi trường Production. payment.raw_gateway_response(jsonb, mục 5.2.4): cần ràng buộc tại tầng ứng dụng chỉ lưu phần phản hồi phi thẻ (đã ghi chú ở mục 5) — bổ sung: whitelist field được lưu (không lưu nguyên payload thô nếu gateway trả kèm dữ liệu nhạy cảm ngoài dự kiến).- Staging/Dev: không chứa PII/KYC thật (đã chốt mục 3.3) — xác nhận lại quy trình anonymize dữ liệu khi sao chép Production → Staging (hash/mask
email,phone, xoátax_code/account_numberthật, thay bằng dữ liệu giả lập nhất quán để giữ khả năng test).
8.2.4 Quyền của chủ thể dữ liệu (NĐ13/2023)
- Quy trình xoá/ẩn danh (đã có ở mục 5.3.6) cần bổ sung: xác thực danh tính người yêu cầu trước khi xử lý (tránh giả mạo yêu cầu xoá tài khoản người khác), thời hạn phản hồi theo luật định, và log lại yêu cầu (ai yêu cầu, khi nào, xử lý bởi ai) vào
audit_log(nay đã có bảng cụ thể ở mục 5.2.11 v3, xem §8.2.5). - Quyền truy cập/xuất dữ liệu cá nhân ("right to access"): brief/mục 2/5 chưa đề cập endpoint hoặc quy trình cho phép Customer/Seller yêu cầu xuất toàn bộ dữ liệu cá nhân của mình — đây là nghĩa vụ thường đi kèm NĐ13/2023, cần bổ sung (ít nhất là quy trình vận hành thủ công qua CSR ở giai đoạn đầu, không nhất thiết phải tự động hoá ngay).
8.2.5 Nhật ký kiểm toán (audit_log) — chính sách bảo mật (mới — v2)
Trả lời trực tiếp yêu cầu người duyệt (mục 3): quyết định cho bảng
audit_log(mục 5.2.11 v3, Audit & Compliance Service).
(a) Redact/mask trường cực nhạy cảm trong before_json/after_json — QUYẾT ĐỊNH: có, bắt buộc.
- Nguyên tắc: giá trị nhạy cảm cao (
seller_bank_account.account_number,seller.tax_code,seller.business_license_number/số CMND-CCCD trongkyc_document) không bao giờ được ghi ở dạng đầy đủ (raw) vàoaudit_log, kể cả khi đã mã hoá tầng ứng dụng ở nguồn (§8.2.1) — vì mục đích audit chỉ cần biết "đã thay đổi từ giá trị X sang Y", không cần giá trị đầy đủ. - Vị trí thực hiện masking: tại service nguồn phát sự kiện (Seller Management Service khi phát
KycDocumentVerified/SellerLocked, Commission & Payout Service khi phátCommissionRuleUpdated/PayoutRetried), trước khi publish domain event lên Kafka/MSK — không để giá trị raw đi qua message broker dù chỉ tạm thời (khớp lưu ý ACL/mã hoá topic §8.2.2). Audit & Compliance Service chỉ ghi lại snapshot đã được masking từ nguồn, không tự giải mã/hiển thị lại giá trị gốc. - Quy tắc masking cụ thể:
account_number: chỉ giữ 4 ký tự cuối, còn lại thay bằng*(VD**********1234).tax_code,business_license_number, số CMND/CCCD: giữ 3 ký tự đầu và 2 ký tự cuối, phần giữa thay bằng*(VD079*******45).- Các trường KYC dạng file (
file_url_s3): không ghi đường dẫn S3 vàoaudit_log(tránh audit_log trở thành kênh truy cập gián tiếp tới object KYC) — chỉ ghidocument_typevàverified_statusthay đổi. - Snapshot
before_json/after_jsonbổ sung cờ"_redacted": truekhi có trường bị masking, để người đọc audit biết dữ liệu đã qua xử lý, không phải thiếu sót ghi log.
- Đây là quyết định của mục 8 nhưng không yêu cầu sửa lại schema
audit_logở mục 5 (kiểu cộtjsonbđã đủ linh hoạt chứa giá trị đã masking) — chỉ là ràng buộc ở tầng ứng dụng khi ghi dữ liệu, không tạo finding hướng về mục 5.
(b) Kiểm soát truy cập đọc audit_log — QUYẾT ĐỊNH: chỉ scope admin:audit:read (Platform Admin), không cấp cho Ops/CSR.
- Lý do:
audit_logchứa vết hành động nhạy cảm xuyên toàn sàn (duyệt KYC, khoá seller, cấu hình hoa hồng, quyết định dispute, retry payout) — phạm vi đọc rộng hơn phạm vi tác nghiệp thường nhật của Ops/CSR; giới hạn ở Platform Admin giảm bề mặt rủi ro lộ thông tin điều tra nội bộ. - Đề xuất scope mới
admin:audit:read(không dùng chungadmin:*để có thể tách nhỏ quyền sau này nếu marketplace cần vai trò "Security/Compliance Officer" riêng ở giai đoạn sau — hiện chưa có trong danh sách actor mục 1). - Mục 4 chưa có endpoint đọc
audit_log(mục 5.5/5.2.11 v3 đã ghi chú giao choapi-designer, nhưng mục 4 v3 chưa bổ sung) — ghi nhận là finding mới hướng về mục 4 (xem §8.5), không tự thiết kế endpoint ở đây.
(c) Retention — QUYẾT ĐỊNH: giữ nguyên 5 năm cho phần lớn audit_log, khuyến nghị nâng lên 10 năm riêng cho nhóm hành động tài chính.
- Đa số hành động (KYC review, khoá/mở seller) phục vụ mục đích audit an ninh/vận hành — 5 năm (như mục 5.3.6 đã chốt) là hợp lý và nhất quán với thông lệ audit an ninh.
- Riêng các bản ghi
audit_logcóresource_typegắn trực tiếp tới nghiệp vụ tài chính (commission_rulekhi thay đổihold_days/commission_percent,payoutkhi retry,disputekhi quyết định làrefund) nên áp dụng retention 10 năm, khớp với retention củapayment/commission_transaction/payoutở mục 5.3.6 (thông lệ chứng từ kế toán) — vì các bản ghi audit này là bằng chứng bổ trợ cho quyết định tài chính, tách rời hoặc xoá sớm hơn dữ liệu gốc có thể gây thiếu chứng cứ khi kiểm toán/thanh tra thuế. - Đây là khuyến nghị điều chỉnh retention phân nhóm theo
resource_typekhác với retention đơn nhất "5 năm" hiện có ở mục 5.3.6/5.2.11 — ghi nhận thành finding hướng về mục 5 (§8.5, severity medium) vì đòi hỏi điều chỉnh chiến lược partition/archive (partition theo tháng đã có, chỉ cần logic archive job phân biệt theoresource_typekhi tới mốc 5 năm), không tự sửa mục 5 ở đây.
8.3 Phòng chống rủi ro bảo mật (OWASP Top 10 — theo endpoint mục 4 & luồng mục 6)
| OWASP 2021 | Endpoint/luồng cụ thể bị ảnh hưởng | Rủi ro | Biện pháp |
|---|---|---|---|
| A01 – Broken Access Control | GET /v1/shipments/{shipmentId}/tracking (4.1.12 v3) |
IDOR — đã vá ở mục 4 v3: kiểm tra customerId/sellerId liên quan hoặc scope ops:*/admin:* toàn cục, 403 ERR_FORBIDDEN_OWNERSHIP nếu không khớp (trước đây Finding F1, nay giải quyết) |
Xác nhận giữ nguyên thiết kế hiện tại, không cần thay đổi thêm |
| A01 – Broken Access Control | PATCH /v1/admin/disputes/{disputeId} (4.1.5), luồng 6.1.3 |
CSR sửa dispute không do mình phụ trách | Kiểm tra assigned_csr_id = CSR hiện tại hoặc vai trò Admin — đây là rule nghiệp vụ nội bộ, không phải IDOR giữa khách hàng khác nhau, xem §8.1.2 |
| A02 – Cryptographic Failures | POST /v1/sellers/{sellerId}/kyc-documents, seller_bank_account, mfa_device.secret_encrypted |
Lộ dữ liệu tài chính/định danh nếu chỉ dựa mã hoá at-rest mặc định | Mã hoá tầng ứng dụng cho nhóm cột nhạy cảm cao (§8.2.1); áp dụng đồng thời cho snapshot ghi vào audit_log (redact — §8.2.5) |
| A03 – Injection | GET /v1/search/products?q= (4.1.4) |
OpenSearch query injection nếu ghép chuỗi trực tiếp từ q vào Query DSL |
Dùng structured query builder (parameterize), không nối chuỗi thô; sanitize input, giới hạn độ dài q |
| A03 – Injection | Toàn bộ endpoint ghi (checkout, KYC upload, commission rule) | SQL injection qua ORM lỏng lẻo, path traversal khi upload multipart/form-data KYC |
Dùng ORM có parameterized query mặc định (không raw SQL nối chuỗi); validate MIME type/kích thước file KYC, quét virus (VD ClamAV/AWS trước khi lưu S3) |
| A04 – Insecure Design | POST /v1/checkout (4.1.5) |
Request không chứa giá — hệ thống tính giá server-side từ Cart (đã đúng thiết kế), tránh tamper giá phía client |
Xác nhận giữ nguyên nguyên tắc "không tin dữ liệu giá từ client" cho mọi luồng tương lai (VD áp dụng cho apply-coupon, loyalty/redeem) |
| A04 – Insecure Design | PUT /v1/admin/commission-rules/{categoryId} (4.1.8) |
holdDays cho phép Admin override ngoài khoảng 3-7 (chỉ cảnh báo 422, "vẫn cho phép... có xác nhận") |
Bắt buộc log audit riêng (before/after + lý do) cho mọi lần override ngoài khoảng khuyến nghị — đã có qua event CommissionRuleUpdated ghi audit_log (mục 5.2.11/6.1.4 v2) |
| A05 – Security Misconfiguration | API Gateway/BFF, mã lỗi chuẩn hoá (4.1.13) | Rò rỉ stack trace/chi tiết hệ thống qua ERR_INTERNAL |
Response 500 không bao giờ trả chi tiết exception nội bộ ra client, chỉ traceId để tra log nội bộ (đã đúng thiết kế hiện tại, xác nhận giữ nguyên) |
| A05 – Security Misconfiguration | Môi trường Dev/Staging (3.3) | Feature flag "mặc định bật" ở Dev có thể lộ tính năng chưa hoàn thiện nếu môi trường lộ ra ngoài | Xác nhận Dev/Staging không có DNS/IP public không cần thiết, chỉ qua VPN nội bộ |
| A06 – Vulnerable & Outdated Components | Toàn bộ service (container hoá ECS Fargate/EKS) | Dependency có lỗ hổng đã biết | SCA scan (Trivy/Snyk/Dependabot) trong CI/CD — thuộc phạm vi mục 9, dẫn chiếu chéo, không thiết kế lại ở đây |
| A07 – Identification & Authentication Failures | /v1/auth/login, /v1/auth/mfa/challenge (4.1.3) |
Brute-force, credential stuffing | Rate limit (IP, mục 4.2) + account lockout theo vai trò (§8.1.1a, v2) + captcha — đã có đủ cột hỗ trợ ở mục 5 v3, chỉ còn thiếu mã lỗi 423 ERR_ACCOUNT_LOCKED ở mục 4 (finding §8.5) |
| A08 – Software & Data Integrity Failures | /v1/payments/webhooks/{vnpay,momo}, /v1/webhooks/{ghn,ghtk} (4.1.6, 4.1.12 v3) |
Webhook giả mạo/replay nếu chỉ kiểm tra chữ ký mà không kiểm tra thời gian | Đã triển khai ở mục 4 v3: xác thực chữ ký + kiểm tra timestamp (từ chối nếu lệch quá 5 phút) + idempotency theo gatewayTransactionRef (trước đây Finding F3, nay giải quyết) |
| A09 – Security Logging & Monitoring Failures | Toàn hệ thống, đặc biệt hành động Admin (KYC review, dispute resolution, commission override, payout retry, khoá/mở seller) | Thiếu audit trail tập trung để điều tra sự cố/gian lận | Đã triển khai ở mục 5 v3/6 v2: bảng audit_log tại Audit & Compliance Service, ghi qua domain event cho toàn bộ hành động nhạy cảm liệt kê (trước đây Finding F7, nay giải quyết); chính sách redact/access-control/retention chốt tại §8.2.5 (v2); giám sát/alerting realtime thuộc mục 9 (dẫn chiếu chéo) |
| A10 – SSRF | Payment/Shipping Service gọi ra VNPay/Momo/GHN/GHTK (mục 3.4) | Rủi ro thấp vì URL đối tác cấu hình cứng (không nhận URL từ input người dùng); cần xác nhận không có endpoint nào nhận URL callback tuỳ ý từ client | Không phát hiện endpoint SSRF cụ thể trong mục 4/6 hiện tại; khuyến nghị giữ nguyên tắc "không bao giờ gọi ra ngoài theo URL do client cung cấp" khi mở rộng tính năng sau này |
CSRF: vì API dùng JWT Bearer (không session cookie truyền thống) nên rủi ro CSRF thấp với access token lưu trong memory; nếu triển khai theo khuyến nghị §8.1.1 (refresh token trong cookie HttpOnly), bắt buộc bổ sung CSRF token (double-submit) cho các request ghi dùng cookie — cần đồng bộ với thiết kế frontend ở mục 7 (chưa có, xem openQuestions).
Rate limiting bổ sung: POST /v1/customers/me/loyalty/redeem đã yêu cầu Idempotency-Key bắt buộc ở mục 4.1.9 v3 (trước đây Finding F4, nay giải quyết); vẫn khuyến nghị rate limit theo user cho endpoint này và POST /v1/cart/apply-coupon để chống dò mã coupon/lạm dụng đổi điểm hàng loạt bằng script (khuyến nghị bổ sung, không phải lỗi thiết kế đã có).
8.4 Tuân thủ (Compliance)
| Quy định | Trạng thái áp dụng | Ghi chú kỹ thuật |
|---|---|---|
| PCI-DSS | Áp dụng, scope thu hẹp (không lưu số thẻ — đã xác nhận kiến trúc mục 3.1, dữ liệu bảng mục 5.2.4) | Nếu VNPay/Momo tích hợp theo hình thức redirect (không nhúng iframe/form nhập thẻ trên domain của sàn), scope tương ứng SAQ A (đơn giản nhất) — cần xác nhận hình thức tích hợp cụ thể với 2 gateway (openQuestion); dù scope giảm vẫn khuyến nghị: WAF với OWASP Core Rule Set (đã có ở mục 3.2), quét lỗ hổng bên ngoài định kỳ (ASV scan hàng quý) nếu domain thanh toán thuộc phạm vi SAQ yêu cầu, và pentest ứng dụng hàng năm — các hạng mục này có chi phí, cần xác nhận ngân sách (ngân sách/timeline hiện "chưa xác định" theo brief) |
| NĐ13/2023 (Bảo vệ dữ liệu cá nhân) | Áp dụng đầy đủ (hasPII=true) | Đã có: mã hoá, retention (mục 5.3.6), right-to-delete (mục 5.3.6 + bổ sung §8.2.4), audit trail cho yêu cầu xoá (§8.2.5, audit_log). Còn thiếu: DPIA (Data Protection Impact Assessment) chưa thực hiện — khuyến nghị thực hiện trước go-live; cơ chế consent quản lý (marketing email/SMS opt-in/opt-out) — đã có notification-preferences (FR-12) nhưng chưa rõ có tách riêng consent marketing vs giao dịch bắt buộc hay không — openQuestion |
| NĐ52/85 (thông báo website TMĐT marketplace) | Áp dụng — chủ yếu là nghĩa vụ pháp lý/hành chính (đăng ký với Bộ Công Thương), không phải control kỹ thuật của mục 8 | Yêu cầu kỹ thuật liên quan duy nhất: hiển thị thông tin đăng ký/logo xác nhận ở footer — thuộc mục 7 (UI), không lặp lại ở đây |
| Tuân thủ nội bộ khác | Không áp dụng SSO doanh nghiệp/IdP liên kết (đã chốt "không có khách hàng B2B enterprise" ở brief) | Giữ nguyên theo ràng buộc mục 1, không đề xuất bổ sung SAML/OIDC federation ở MVP |
Trade-off/chi phí cần lưu ý (không vượt ràng buộc ngân sách mục 1, chỉ nêu để chủ dự án cân nhắc khi ngân sách được xác định):
- Mã hoá tầng ứng dụng cho cột nhạy cảm cao (§8.2.1) + redact khi ghi
audit_log(§8.2.5) làm tăng độ phức tạp phát triển/vận hành (quản lý key rotation, chi phí CPU giải mã, logic masking tại nhiều service nguồn) — chấp nhận được ở quy mô "large" có PII/Payment, nhưng cần thời gian dev bổ sung so với chỉ dùng mã hoá at-rest mặc định. - ASV scan quý + pentest năm + AWS GuardDuty/Security Hub/Macie (phát hiện PII ngoài ý muốn) là chi phí vận hành liên tục, không bắt buộc về mặt kỹ thuật để hệ thống chạy nhưng khuyến nghị mạnh cho quy mô/loại dữ liệu hiện tại — cần xác nhận ngân sách bảo mật vận hành hàng năm (hiện brief chưa có con số).
- OPA/policy-as-code cho kiểm soát ownership tập trung (§8.1.2) là lựa chọn kiến trúc bổ sung có thể triển khai đơn giản hơn bằng middleware tự viết nếu muốn giảm chi phí học/vận hành thêm một thành phần mới — nêu như một lựa chọn, không bắt buộc.
- Retention 10 năm riêng cho nhóm
audit_logtài chính (§8.2.5c) làm tăng chi phí lưu trữ dài hạn (dù đã partition theo tháng) — chi phí storage lạnh (S3 Glacier archive sau khi hết hạn truy vấn nhanh) là hợp lý, cần chủ dự án xác nhận khi có ngân sách vận hành cụ thể.
8.5 Rủi ro phát hiện & khuyến nghị
(v2) Rà soát lại toàn bộ 10 finding của v1: 9/10 đã được giải quyết ở mục 4 v3 / 5 v3 / 6 v2 (liệt kê tại bảng "Finding đã giải quyết" bên dưới, giữ lại để truy vết lịch sử — không tính vào
findingscủa structured output). 1 finding cũ (F10 — MSK ACL) và 3 finding mới phát sinh từ mục 6 v2 vẫn còn tồn đọng, được liệt kê ở bảng "Finding còn tồn đọng" — đây là các finding trả về trong structured output.
Finding đã giải quyết (lịch sử, không còn hành động cần thiết)
| # | Mục đã sửa | Vấn đề gốc (v1) | Trạng thái v2 |
|---|---|---|---|
| F1 | 04 v3 | GET /v1/shipments/{shipmentId}/tracking thiếu ràng buộc sở hữu → IDOR |
Đã giải quyết — mục 4.1.12 v3 bổ sung kiểm tra ownership, 403 ERR_FORBIDDEN_OWNERSHIP |
| F2 | 04 v3 | X-Guest-Session-Id chưa quy định CSPRNG/cookie flags |
Đã giải quyết — mục 4.1.1 v3: CSPRNG ≥128-bit, cookie HttpOnly/Secure/SameSite=Lax, rate-limit riêng theo IP cho endpoint ghi Cart Guest |
| F3 | 04 v3 | Webhook thiếu chống replay (timestamp/nonce) | Đã giải quyết — mục 4.1.6 v3: kiểm tra timestamp lệch ≤5 phút + idempotency theo gatewayTransactionRef |
| F4 | 04 v3 | loyalty/redeem thiếu Idempotency-Key |
Đã giải quyết — mục 4.1.9 v3 bổ sung Idempotency-Key bắt buộc |
| F5 | 04 v3 | OAuth callback thiếu kiểm tra state/xử lý trùng email |
Đã giải quyết — mục 4.1.3 v3: state bắt buộc (400 ERR_OAUTH_STATE_INVALID), 409 ERR_ACCOUNT_LINK_REQUIRED khi trùng email, không auto-merge |
| F6 | 05 v3 | user_account thiếu cột chống brute-force |
Đã giải quyết — mục 5.2.1 v3 bổ sung failed_login_count/locked_until/last_failed_login_at; chính sách ngưỡng/thời lượng chốt tại §8.1.1a (v2) |
| F7 | 05 v3 | Thiếu bảng audit log tập trung | Đã giải quyết — mục 5.2.11 v3 bổ sung audit_log tại Audit & Compliance Service; chính sách redact/access/retention chốt tại §8.2.5 (v2) |
| F8 | 06 v2 | KYC document chưa có cơ chế xem an toàn (pre-signed URL) | Đã giải quyết — sequence 6.1.5 v2 bổ sung bước sinh pre-signed URL TTL ≤5 phút; lưu ý phụ: endpoint tương ứng chưa có ở mục 4 v3 → xem finding mới #F11 bên dưới |
| F9 | 06 v2 | Payout batch file thiếu kênh truyền/mã hoá cụ thể | Đã giải quyết (ở mức thiết kế) — sequence 6.1.4 v2 nêu kênh SFTP+PGP hoặc API HTTPS ngân hàng đối tác; ngân hàng cụ thể vẫn là giả định/openQuestion tại mục 6 (không phải finding bảo mật còn tồn đọng) |
Finding còn tồn đọng (trả về trong findings của structured output)
| # | Mục cần sửa | Vấn đề | Mức độ | Khuyến nghị |
|---|---|---|---|---|
| F10 | 03 | Sơ đồ kiến trúc (3.2) chưa đề cập ACL/mã hoá theo topic cho Message Broker (Kafka/MSK), trong khi nhiều event mang dữ liệu tài chính/PII gián tiếp (PaymentConfirmed, PayoutScheduled, OrderDelivered, và nay thêm các domain event ghi audit_log) |
Low (không đổi so với v1) | Bổ sung: bật TLS in-transit cho MSK, ACL theo topic giới hạn consumer là service liên quan, không cho mọi service subscribe toàn bộ topic |
| F11 | 04 | Mục 4 chưa có endpoint cho Admin lấy pre-signed URL xem một KYCDocument cụ thể (sequence 6.1.5 v2 đã mô tả cơ chế nhưng thiếu endpoint tương ứng, VD GET /v1/admin/sellers/{sellerId}/kyc-documents/{documentId}/view-url) |
Low — mục 4 đã hết vòng sửa (v3, approved), ghi nhận để xử lý thủ công/vòng sau | Bổ sung endpoint trả { viewUrl, expiresInSeconds<=300 }, không trả file_url_s3 trực tiếp |
| F12 | 04 | Mục 4 chưa có mã lỗi cho trường hợp tài khoản bị khoá tạm do vượt ngưỡng đăng nhập sai (user_account.locked_until, mục 5.2.1 v3) — chính sách ngưỡng/thời lượng đã chốt tại §8.1.1a (v2) |
Low — mục 4 đã hết vòng sửa (v3, approved), ghi nhận để xử lý thủ công/vòng sau | Bổ sung mã lỗi 423 ERR_ACCOUNT_LOCKED kèm retryAfterSeconds, áp dụng tại POST /v1/auth/login |
| F13 | 04 | Mục 4 chưa có endpoint đọc audit_log (mục 5.2.11/5.5 v3 đã ghi chú giao cho api-designer nhưng chưa được bổ sung ở mục 4 v3) |
Low — cùng lý do F11/F12, ghi nhận thủ công/vòng sau | Bổ sung endpoint dạng GET /v1/admin/audit-logs (scope admin:audit:read — xem §8.2.5b), hỗ trợ filter theo resource_type/resource_id/actor_id/khoảng thời gian |
| F14 | 05 | Retention audit_log hiện đồng nhất 5 năm (mục 5.2.11/5.3.6) — khuyến nghị phân nhóm theo resource_type: giữ 5 năm cho hành động vận hành (KYC, khoá seller), nâng lên 10 năm cho hành động gắn trực tiếp tài chính (commission_rule, payout, dispute quyết định refund) để nhất quán với retention payment/payout (mục 5.3.6) |
Medium | Điều chỉnh logic archive/xoá của audit_log theo resource_type thay vì một mốc retention duy nhất; không cần đổi schema (cột jsonb/resource_type đã đủ) |