Skip to content

ADR-0001. Chứng chỉ license ký Ed25519, xác minh offline qua Redis

TrườngGiá trị
StatusAccepted
Date2026-03-20
Deciderslicensing-team, platform-team
Supersedes

Bối cảnh

  • Mọi request trong mọi service (sale, commerce, inventory, …) cần biết trạng thái license, tier, và feature của một merchant / user.
  • Một lệnh gọi HTTP đồng bộ tới licensing trên mỗi request sẽ ràng mọi service vào tính khả dụng của licensing và thêm latency vào hot path.
  • Trạng thái license thay đổi hiếm (issue / suspend / renew / revoke), nhưng đọc liên tục — hồ sơ read-heavy, write-rare.
  • Dữ liệu phải chống giả mạo (consumer không được giả mạo payload "PERPETUAL/all-features") và bảo mật (config feature không nên đọc được từ một dump Redis rò rỉ).

Quyết định

Chúng ta sẽ để licensing ký một chứng chỉ tự chứa cho mỗi license và cache nó trong Redis tại lic:certs:<entityType>:<entityId>. Consumer xác minh nó offline qua LicenseMiddleware của @nx/core — họ không bao giờ gọi licensing lúc runtime.

Envelope chứng chỉ là aes-256-gcm+ed25519:

  • ILicenseCertificatePayload (status, tier, features, giới hạn activation, expiry) được mã hoá AES-256-GCM với APP_ENV_APPLICATION_SECRET.
  • Ciphertext được ký Ed25519 với APP_ENV_LICENSING_ED25519_PRIVATE_KEY; consumer xác minh với public key đóng gói trong @nx/core.
  • Payload mang certExpiresAt; key Redis cũng nhận EX = APP_ENV_LICENSING_CERT_TTL_SECONDS (mặc định 86400).

Licensing re-publish sau mỗi mutation vòng đời (issue / suspend / reinstate / renew / revoke) và khi lazy expiry. LicenseMiddlewarefail-open: cert thiếu / hết hạn / không xác minh được trả về license context null ("unknown", không phải "unlicensed").

Hệ quả

ƯuNhược
Zero ràng buộc runtime — consumer không gọi licensingCrypto bất đối xứng + AES mỗi lần publish license (rẻ, nhưng có thật)
Đường read là một Redis GET + verify cục bộAPP_ENV_APPLICATION_SECRET dùng chung phải phân phối tới mọi verifier (lan tràn secret đối xứng)
Chống giả mạo (Ed25519) + bảo mật (AES)Xoay keypair yêu cầu roll public-key phối hợp + re-publish
Trạng thái cũ giới hạn bởi TTL ngay cả khi bỏ lỡ publishSửa feature qua CRUD không re-publish — cert có thể trễ so với DB tới khi có hành động vòng đời
Fail-open tránh việc licensing down kéo sập mọi serviceFail-open nghĩa Redis down lặng lẽ làm suy giảm cưỡng chế — cổng phải xử lý null có chủ ý

Phương án đã cân nhắc

Phương ánƯuNhượcVì sao loại
HTTP validate đồng bộ mỗi requestLuôn tươiRàng mọi service vào uptime + latency của licensingChi phí hot-path & bán kính ảnh hưởng không chấp nhận được
JWT (bất đối xứng) thay vì envelope AES+Ed25519Chuẩn, nhiều công cụPayload đọc được (base64) — config feature lộYêu cầu bảo mật
Chứng chỉ chỉ HMAC đối xứngĐơn giản hơn, một secretBất kỳ consumer nào giữ secret đều giả mạo được certCần tách tin cậy issuer/verifier
Broadcast Kafka thay đổi licensePush, event-sourcedThêm phụ thuộc broker cho nhu cầu read-on-demand; consumer phải lưu trạng tháiQuá kỹ cho read-heavy/write-rare; cache Redis là đủ

Tham chiếu

  • licensing/src/services/licensing/licensing-base.service.tspublishCertificate()
  • core/src/services/license/certs/signer.tsLicenseCertSignerHelper.sign
  • core/src/services/license/certs/verifier.tsLicenseCertVerifierHelper.verify
  • core/src/middlewares/license/license.middleware.tsLicenseProvider (fail-open)
  • core/src/services/license/certs/types.tsCertificateDefinitions.FULL_FORM = aes-256-gcm+ed25519

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.