Quản lý Secret và khóa mã hóa an toàn với HashiCorp Vault

Trong các hệ thống phân tán hiện đại chạy trên Kubernetes v1.32+ và đa đám mây (Multi-Cloud) năm 2026, sự gia tăng bùng nổ của hàng trăm Microservices, đường ống CI/CD và các tác nhân tự động hóa đã tạo ra cơn ác mộng mang tên Phân mảnh Thông tin Nhạy cảm (Secret Sprawl). Khi mật khẩu cơ sở dữ liệu PostgreSQL/MySQL, khóa ký số JWT (Ed25519), chứng chỉ mTLS và khóa API thanh toán bị lưu trữ tĩnh bên trong các tệp .env, biến môi trường CI/CD hoặc đối tượng Secret mặc định của Kubernetes (vốn chỉ được mã hóa định dạng Base64 chứ không hề được mã hóa mật mã nếu chưa cấu hình KMS tại etcd), chỉ cần một lập trình viên vô tình đẩy nhầm tệp cấu hình lên Git hoặc một Pod bị khai thác lỗ hổng đọc tệp cục bộ là toàn bộ hạ tầng dữ liệu sẽ bị xâm phạm.

Thiết lập hệ thống quản lý Secret và khóa mã hóa với HashiCorp Vault (v1.18+ hoặc nhánh mã nguồn mở tương thích Linux Foundation OpenBao v2.1+) giúp doanh nghiệp chấm dứt hoàn toàn kỷ nguyên của mật khẩu tĩnh dài hạn. Thay vì phân phát một mật khẩu dùng chung tồn tại hàng năm trời, Vault chuyển dịch sang mô hình Thông tin xác thực động theo yêu cầu (Dynamic Just-In-Time Secrets) tự hủy khi hết hạn Lease và cung cấp Dịch vụ Mã hóa Tập trung (Encryption-as-a-Service - Transit Engine) mà không bao giờ để lộ khóa mật mã nguyên bản ra bộ nhớ ứng dụng. Bài viết chuyên sâu này phân tích chi tiết kiến trúc Rào cản Mật mã (Cryptographic Barrier), triển khai cụm High Availability với Integrated Raft Storage + Cloud KMS Auto-Unseal, cấp phát tài khoản động cho PostgreSQL 17+ / MySQL 8.4 LTS, mã hóa phong bì (Envelope Encryption AES-256-GCM96) và đồng bộ tự động vào Kubernetes qua Vault Secrets Operator (VSO) / External Secrets Operator (ESO).


Mục Lục Nội Dung

1. Thách thức "Secret Sprawl" trong Hệ thống Phân tán và Kiến trúc Bảo mật Cốt lõi của HashiCorp Vault

1.1. Rủi ro từ biến môi trường .env tĩnh, Git Commit Leak và Kubernetes Base64 Secret không mã hóa

Trong các cuộc điều tra sự cố an ninh mạng doanh nghiệp, nguyên nhân hàng đầu dẫn đến việc kẻ tấn công leo thang đặc quyền từ một dịch vụ ngoại vi vào thẳng cơ sở dữ liệu lõi là do thông tin xác thực mắc phải 3 khiếm khuyết kiến trúc:

  1. Tồn tại vĩnh viễn không có vòng đời thu hồi (Infinite TTL): Một mật khẩu tài khoản app_user trên cơ sở dữ liệu được cấu hình từ lúc khởi tạo dự án và không bao giờ được xoay vòng vì đội ngũ kỹ thuật sợ làm gián đoạn 30 dịch vụ đang cùng đọc biến môi trường đó.
  2. Dùng chung danh tính giữa nhiều Workload (Shared Identity & Non-Repudiation Failure): Khi 20 Pod khác nhau cùng kết nối vào PostgreSQL bằng chung một tài khoản db_prod_rw, hệ thống nhật ký kiểm toán không thể phân biệt truy vấn rò rỉ dữ liệu đến từ dịch vụ nào, và việc khóa tài khoản đó để chặn kẻ tấn công sẽ làm sập toàn bộ 19 dịch vụ hợp lệ còn lại.
  3. Lộ lọt qua bộ nhớ tiến trình và Crash Dump: Khi truyền mật khẩu qua biến môi trường (ENV) của container, mọi tiến trình con được sinh ra hoặc các công cụ thu thập thông tin gỡ lỗi (/proc//environ) đều có thể đọc trực tiếp toàn bộ chuỗi bí mật dưới dạng văn bản thuần.

Do đó, một trụ cột không thể tách rời khi triển khai kiến trúc Zero Trust Security trong doanh nghiệp công nghệ là mọi thông tin xác thực phải gắn liền với danh tính mật mã của từng thực thể, có thời hạn sống ngắn hạn (Short-Lived Lease) và có thể bị thu hồi tức thì.

1.2. Kiến trúc Cryptographic Barrier của Vault: Master Key, Encryption Keyring, Shamir's Secret Sharing và Cloud KMS Auto-Unseal

Điểm làm nên sự khác biệt căn bản của HashiCorp Vault 1.18+ (và OpenBao v2.1+) nằm ở kiến trúc Rào cản Mật mã (Cryptographic Barrier). Vault coi toàn bộ tầng lưu trữ vật lý bên dưới (ổ đĩa NVMe, Raft BoltDB hay mạng nội bộ) là môi trường không đáng tin cậy (Untrusted Storage): không một byte dữ liệu nhạy cảm nào được ghi xuống đĩa dưới dạng văn bản rõ.

Cơ chế bảo vệ khóa của Vault vận hành theo cấu trúc phân cấp 3 tầng:

  • Tầng 1 — Vòng khóa Mã hóa Dữ liệu (Encryption Keyring): Mọi dữ liệu trong Vault đều được mã hóa bằng thuật toán xác thực AES-256-GCM96 sử dụng khóa đang hoạt động trong Keyring. Khi quản trị viên xoay vòng khóa nội bộ (vault operator rotate), một khóa mới được thêm vào vòng khóa để mã hóa dữ liệu mới mà không làm gián đoạn việc giải mã dữ liệu cũ.
  • Tầng 2 — Khóa Chủ (Root Key / Master Key): Toàn bộ Encryption Keyring lại được bao bọc và mã hóa bởi một khóa duy nhất gọi là Root Key.
  • Tầng 3 — Khóa Mở niêm phong (Unseal Key / Recovery Key): Khi tiến trình Vault vừa khởi động, nó ở trạng thái Đóng niêm phong (Sealed) — tức là biết dữ liệu mã hóa nằm ở đâu trên đĩa nhưng không có Root Key trong RAM để giải mã. Để mở niêm phong (Unseal), Vault hỗ trợ hai cơ chế:
    1. Shamir's Secret Sharing (Chia sẻ Bí mật Shamir truyền thống): Thuật toán toán học chia khóa mở niêm phong thành $N$ mảnh rời rạc (ví dụ $N = 5$) phát cho 5 lãnh đạo bảo mật khác nhau; bắt buộc phải tập hợp đủ ngưỡng tối thiểu $K$ mảnh (ví dụ $K = 3$) mới tái tạo được Root Key trong bộ nhớ RAM.
    2. Cloud KMS / HSM Auto-Unseal (Chuẩn Production 2026): Thay vì cần con người nhập tay từng mảnh khóa mỗi khi Pod Vault khởi động lại trên Kubernetes, Root Key được bọc trực tiếp bằng khóa phần cứng trong AWS KMS, GCP Cloud KMS, Azure Key Vault hoặc một cụm Vault Transit HSM độc lập, cho phép nút Vault tự động mở niêm phong an toàn trong vài giây.

Sơ đồ kiến trúc dưới đây mô tả tổng thể cụm HashiCorp Vault 1.18+ HA với Integrated Raft Storage, Cloud KMS Auto-Unseal và các Secrets Engines:

flowchart TB
    subgraph UnsealAndAudit["Tầng Bảo vệ Gốc & Kiểm toán Bất biến"]
        KMS["Cloud KMS / HSM Auto-Unseal (Bảo vệ Root Key bằng Phần cứng FIPS 140-3)"]
        Audit1["Primary Audit Device (File JSON — Băm nhạy cảm bằng HMAC-SHA256)"]
        Audit2["Secondary Audit Device (Syslog/SIEM — Fail-Closed nếu mất cả 2 kênh Audit)"]
    end

    subgraph VaultCluster["Cụm HashiCorp Vault 1.18+ / OpenBao v2.1+ HA (Cryptographic Barrier)"]
        ActiveNode["Vault Active Leader Node (RAM Keyring AES-256-GCM + mlock)"]
        Standby1["Vault Raft Follower Node 1 (TLS 1.3 + Performance Standby)"]
        Standby2["Vault Raft Follower Node 2 (TLS 1.3 + Performance Standby)"]

        subgraph Engines["Các Bộ máy Bảo mật (Secrets & Auth Engines)"]
            AuthK8s["Auth: Kubernetes Projected JWT & OIDC / SPIFFE SVID"]
            DbEngine["Database Secrets Engine (Dynamic PostgreSQL 17 / MySQL 8.4)"]
            TransitEngine["Transit Secrets Engine (EaaS — AES-256-GCM96 & Ed25519 Signing)"]
            KVv2["KV v2 Secrets Engine (Versioning + Check-and-Set)"]
        end

        ActiveNode <-->|"Đồng thuận Raft mTLS"| Standby1
        ActiveNode <-->|"Đồng thuận Raft mTLS"| Standby2
        ActiveNode --> Engines
    end

    KMS -->|"Tự động Giải mã Root Key"| ActiveNode
    ActiveNode --> Audit1
    ActiveNode --> Audit2


2. Triển khai Cụm HashiCorp Vault 1.18+ High Availability với Integrated Raft Storage

2.1. Thiết kế topo 3–5 Node Integrated Storage (storage "raft"), TLS 1.3 mTLS nội bộ và mlock chống Swap rò rỉ khóa ra đĩa

Trong các phiên bản hiện đại (Vault 1.18+ và OpenBao v2.1+), kiến trúc Integrated Storage (storage "raft") đã thay thế hoàn toàn mô hình phụ thuộc vào cụm Consul bên ngoài. Bằng cách tích hợp trực tiếp thuật toán đồng thuận Raft dựa trên công cụ lưu trữ khóa-giá trị BoltDB (vault.db), mỗi cụm gồm 3 hoặc 5 nút (số lẻ để đảm bảo Quorum $\lfloor N/2 \rfloor + 1$) vừa đóng vai trò xử lý yêu cầu mật mã, vừa tự sao chép dữ liệu đã mã hóa qua kênh mTLS nội bộ trên cổng 8201.

Khi thiết lập cấu hình máy chủ Vault trên Linux 6.12 LTS, có hai nguyên tắc bảo mật hệ điều hành bắt buộc phải tuân thủ:

  1. Khóa bộ nhớ RAM bằng lời gọi hệ thống mlock() (disable_mlock = false): Khi Vault giải mã Root Key và Encryption Keyring, các khóa này nằm trong bộ nhớ RAM của tiến trình. Nếu máy chủ bị thiếu bộ nhớ và nhân Linux đẩy trang bộ nhớ của Vault xuống phân vùng Swap trên ổ cứng (swap out), khóa mật mã nguyên bản sẽ bị ghi xuống đĩa vật lý! Cấp quyền CAP_IPC_LOCK cho tiến trình Vault và bật mlock ép buộc Kernel ghim chặt toàn bộ bộ nhớ của Vault trong RAM vật lý.
  2. Mã hóa đầu cuối TLS 1.3 tới tận Pod Vault: Không bao giờ chấm dứt TLS (TLS Termination) tại Load Balancer bên ngoài rồi gửi lưu lượng HTTP văn bản thuần vào cổng 8200 của Vault. Kết nối phải được bảo vệ bằng TLS 1.3 xuyên suốt tới tận tiến trình Vault (tls_min_version = "tls13").

Dưới đây là tệp cấu hình /etc/vault.d/vault.hcl chuẩn Production cho nút đầu tiên trong cụm 3 nút Raft HA kết hợp AWS KMS Auto-Unseal và xuất chỉ số giám sát Prometheus:

# File: /etc/vault.d/vault.hcl
# Cấu hình HashiCorp Vault 1.18+ / OpenBao v2.1+ HA với Integrated Raft Storage & KMS Auto-Unseal

cluster_name = "infoteck-prod-vault-raft"
ui           = false
# Bắt buộc giữ disable_mlock = false trên Linux để ngăn Kernel đẩy khóa trong RAM xuống đĩa Swap
disable_mlock = false

# Địa chỉ giao tiếp Client (Cổng 8200) và giao tiếp đồng thuận Raft nội bộ (Cổng 8201)
api_addr     = "https://vault-node-1.infoteck.internal:8200"
cluster_addr = "https://vault-node-1.infoteck.internal:8201"

# 1. CẤU HÌNH LISTENER BẮT BUỘC TLS 1.3
listener "tcp" {
  address                  = "0.0.0.0:8200"
  cluster_address          = "0.0.0.0:8201"
  tls_cert_file            = "/etc/vault.d/tls/vault-server.crt"
  tls_key_file             = "/etc/vault.d/tls/vault-server.key"
  tls_client_ca_file       = "/etc/vault.d/tls/infoteck-root-ca.crt"
  tls_min_version          = "tls13"
  tls_disable_client_certs = false

  telemetry {
    unauthenticated_metrics_access = false
  }
}

# 2. CẤU HÌNH LƯU TRỮ PHÂN TÁN INTEGRATED RAFT (3-Node Quorum)
storage "raft" {
  path    = "/var/lib/vault/raft"
  node_id = "vault-prod-node-1"

  # Tự động kết nối mTLS với các nút thành viên trong cụm Raft
  retry_join {
    leader_api_addr         = "https://vault-node-1.infoteck.internal:8200"
    leader_ca_cert_file     = "/etc/vault.d/tls/infoteck-root-ca.crt"
    leader_client_cert_file = "/etc/vault.d/tls/vault-server.crt"
    leader_client_key_file  = "/etc/vault.d/tls/vault-server.key"
  }
  retry_join {
    leader_api_addr         = "https://vault-node-2.infoteck.internal:8200"
    leader_ca_cert_file     = "/etc/vault.d/tls/infoteck-root-ca.crt"
    leader_client_cert_file = "/etc/vault.d/tls/vault-server.crt"
    leader_client_key_file  = "/etc/vault.d/tls/vault-server.key"
  }
  retry_join {
    leader_api_addr         = "https://vault-node-3.infoteck.internal:8200"
    leader_ca_cert_file     = "/etc/vault.d/tls/infoteck-root-ca.crt"
    leader_client_cert_file = "/etc/vault.d/tls/vault-server.crt"
    leader_client_key_file  = "/etc/vault.d/tls/vault-server.key"
  }

  # Tối ưu hiệu năng ghi BoltDB trên ổ đĩa NVMe tốc độ cao
  performance_multiplier = 1
  max_entry_size         = 1048576
}

# 3. CẤU HÌNH TỰ ĐỘNG MỞ NIÊM PHONG (CLOUD KMS AUTO-UNSEAL)
seal "awskms" {
  region     = "ap-southeast-1"
  kms_key_id = "arn:aws:kms:ap-southeast-1:123456789012:key/mrk-9a8b7c6d5e4f3a2b1c0d"
}

# 4. XUẤT CHỈ SỐ TELEMETRY CHO PROMETHEUS
telemetry {
  prometheus_retention_time = "24h"
  disable_hostname          = true
}

2.2. Khởi tạo cụm, cấu hình Least-Privilege HCL Policies và kích hoạt Dual Audit Devices (file + socket/syslog)

Trong kiến trúc bảo mật của Vault, Nhật ký Kiểm toán (Audit Log) được áp dụng nguyên lý An toàn Đóng (Fail-Closed): một khi bạn đã kích hoạt Audit Device, nếu Vault không thể ghi bản ghi kiểm toán của một yêu cầu vào ít nhất một thiết bị Audit (ví dụ do ổ đĩa đầy), Vault sẽ từ chối thực thi yêu cầu đó ngay lập tức để đảm bảo không một hành vi truy cập Secret nào có thể diễn ra trong bóng tối. Vì vậy, chuẩn Production luôn kích hoạt đồng thời 2 kênh Audit độc lập (file cục bộ và syslog/socket đẩy về SIEM), trong đó mọi giá trị nhạy cảm đều được băm một chiều bằng HMAC-SHA256 để vừa truy vết được chuỗi giá trị vừa không làm lộ mật khẩu trong tệp log.

Mọi quyền truy cập trong Vault đều tuân theo nguyên tắc Mặc định Từ chối Tất cả (Deny by Default) và được định nghĩa qua ngôn ngữ HCL Policy với các quyền hạt mịn: create, read, update, patch, delete, list, và sudo.


3. Cấp phát Thông tin Xác thực Động (Dynamic Secrets) cho PostgreSQL 17+ — Xóa bỏ Mật khẩu Tĩnh

3.1. Cơ chế hoạt động của Database Secrets Engine: Tạo Role SQL tạm thời theo Lease TTL và tự động DROP ROLE khi hết hạn

Tính năng đột phá nhất khi quản lý Secret và khóa mã hóa với HashiCorp Vault là Database Secrets Engine. Thay vì lưu một cặp username/password cố định trong kho lưu trữ để các Pod vào đọc, Vault kết nối trực tiếp vào cụm cơ sở dữ liệu — chẳng hạn như các hệ thống đã được tối ưu hóa truy vấn SQL và thiết kế Indexing hiệu quả trên PostgreSQL hoặc kiến trúc xử lý chịu tải và chống nghẽn cổ chai cho Database quan hệ MySQL — và thực hiện quy trình cấp phát động theo từng phiên:

  1. Khi một Pod của order-service khởi động và yêu cầu quyền truy cập cơ sở dữ liệu tại đường dẫn database/creds/order-service-rw, Vault lập tức sinh ra một tên tài khoản duy nhất (ví dụ: v-k8s-order-rw-a8f92b1c-1762310000) kèm mật khẩu ngẫu nhiên 20 ký tự độ entropy cao.
  2. Vault thực thi câu lệnh CREATE ROLE ... WITH LOGIN PASSWORD '...' VALID UNTIL '...' trực tiếp trên PostgreSQL 17, gán đúng quyền SELECT, INSERT, UPDATE trên schema orders, và trả cặp thông tin đó về cho Pod kèm một mã lease_id có thời hạn sống mặc định default_ttl = 1h (tối đa max_ttl = 24h).
  3. Ngay khi Pod bị xóa hoặc khi lease_id hết hạn mà không được gia hạn, bộ quản lý Lease của Vault tự động chạy câu lệnh thu hồi quyền và DROP ROLE ngay trên PostgreSQL!

Nhờ cơ chế này, mỗi Pod trên Kubernetes sở hữu một tài khoản cơ sở dữ liệu riêng biệt. Nếu phát hiện dấu hiệu bất thường trên một Pod, kỹ sư bảo mật chỉ cần chạy một lệnh vault lease revoke (hoặc thu hồi theo tiền tố vault lease revoke -prefix database/creds/order-service-rw) để cắt đứt quyền truy cập ngay lập tức mà không ảnh hưởng tới các dịch vụ khác.

3.2. Cấu hình thực chiến Dynamic PostgreSQL Credentials, xoay vòng Root Password (rotate-root) và gia hạn Lease tự động

Một chi tiết bảo mật cực kỳ quan trọng sau khi kết nối Vault với PostgreSQL là chạy ngay lệnh vault write -force database/rotate-root/. Lệnh này yêu cầu Vault tự sinh một mật khẩu mới cho chính tài khoản quản trị (vault_db_admin) mà Vault đang dùng để tạo các Role con, cập nhật vào PostgreSQL rồi lưu kín trong Vault. Kể từ giây phút đó, ngay cả quản trị viên đã thiết lập hệ thống cũng không còn biết mật khẩu của tài khoản vault_db_admin!

Dưới đây là kịch bản hoàn chỉnh thiết lập Dual Audit Devices, chính sách HCL Least-Privilege, xác thực Kubernetes và cấp phát Dynamic PostgreSQL 17 Credentials:

#!/usr/bin/env bash
# File: configure-vault-dynamic-postgres-and-k8s-auth.sh
# Thiết lập Audit Log kép, HCL Policy, Kubernetes Auth và Dynamic PostgreSQL 17 Secrets Engine
set -Eeuo pipefail

export VAULT_ADDR="https://vault.infoteck.internal:8200"

# 1. KÍCH HOẠT DUAL AUDIT DEVICES (Đảm bảo Fail-Closed + Băm nhạy cảm bằng HMAC-SHA256)
vault audit enable file file_path=/var/log/vault/vault_audit.json mode=0600
vault audit enable syslog tag="vault-audit" facility="AUTH"

# 2. KHỞI TẠO CHÍNH SÁCH PHÂN QUYỀN TỐI THIỂU (LEAST-PRIVILEGE HCL POLICY) CHO ORDER SERVICE
vault policy write order-service-policy - <<'EOF'
# Cho phép lấy thông tin xác thực động (Dynamic Credentials) cho PostgreSQL 17
path "database/creds/order-service-rw" {
  capabilities = ["read"]
}

# Cho phép sử dụng Transit Engine để mã hóa/giải mã dữ liệu thẻ & PII (Không được đọc khóa gốc)
path "transit/encrypt/orders-pii-key" {
  capabilities = ["update"]
}
path "transit/decrypt/orders-pii-key" {
  capabilities = ["update"]
}
path "transit/datakey/plaintext/orders-pii-key" {
  capabilities = ["update"]
}

# Cho phép đọc cấu hình tĩnh phiên bản hóa từ KV v2
path "secret/data/production/order-service/config" {
  capabilities = ["read"]
}
EOF

# 3. KÍCH HOẠT & CẤU HÌNH DATABASE SECRETS ENGINE CHO POSTGRESQL 17+
vault secrets enable -path=database database

vault write database/config/orders-postgres \
  plugin_name="postgresql-database-plugin" \
  allowed_roles="order-service-rw,order-service-ro" \
  connection_url="postgresql://{{username}}:{{password}}@pg-primary.infoteck.internal:5432/orders_db?sslmode=verify-full" \
  username="vault_db_admin" \
  password="${INITIAL_BOOTSTRAP_DB_PASSWORD}" \
  password_authentication="scram-sha-256"

# Xoay vòng ngay mật khẩu của tài khoản gốc vault_db_admin để không một con người nào biết mật khẩu này
vault write -force database/rotate-root/orders-postgres

# Định nghĩa khuôn mẫu SQL khởi tạo và thu hồi Role động (TTL mặc định 1h, tối đa 24h)
vault write database/roles/order-service-rw \
  db_name="orders-postgres" \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}' INHERIT; GRANT orders_app_readwrite TO \"{{name}}\";" \
  revocation_statements="REASSIGN OWNED BY \"{{name}}\" TO orders_app_readwrite; DROP OWNED BY \"{{name}}\"; DROP ROLE IF EXISTS \"{{name}}\";" \
  default_ttl="1h" \
  max_ttl="24h"

# 4. CẤU HÌNH XÁC THỰC KUBERNETES WORKLOAD QUA PROJECTED SERVICE ACCOUNT JWT
vault auth enable kubernetes

vault write auth/kubernetes/config \
  kubernetes_host="https://k8s-api.infoteck.internal:6443" \
  kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
  disable_local_ca_jwt="false"

vault write auth/kubernetes/role/order-service-role \
  bound_service_account_names="order-service-sa" \
  bound_service_account_namespaces="checkout" \
  token_policies="order-service-policy" \
  token_ttl="1h" \
  token_max_ttl="4h" \
  audience="https://vault.infoteck.internal"


4. Triển khai Encryption-as-a-Service (EaaS) với Transit Secrets Engine và Envelope Encryption

4.1. Giải phóng Ứng dụng khỏi gánh nặng Quản lý Khóa Mã hóa với transit/encrypt và transit/decrypt (AES-256-GCM96)

Trong các bài toán tuân thủ tiêu chuẩn bảo mật dữ liệu thẻ PCI-DSS v4.0 hoặc bảo vệ dữ liệu định danh cá nhân (CCCD, tài khoản ngân hàng, hồ sơ y tế), yêu cầu bắt buộc là dữ liệu phải được mã hóa ở cấp độ trường (Field-Level Application Encryption) trước khi ghi xuống cơ sở dữ liệu. Tuy nhiên, nếu lập trình viên tự cài đặt thuật toán AES trong mã nguồn và lưu khóa mã hóa vào cấu hình của ứng dụng, thì khi máy chủ ứng dụng bị xâm nhập, kẻ tấn công lấy được cả bản mã trong Database lẫn khóa giải mã trong cấu hình.

Transit Secrets Engine của Vault hoạt động như một dịch vụ Mã hóa dưới dạng Dịch vụ (Encryption-as-a-Service — EaaS):

  • Vault tạo và giữ chặt khóa mật mã (AES-256-GCM96 cho mã hóa đối xứng hoặc Ed25519 / ECDSA P-256 cho chữ ký số) bên trong Rào cản Mật mã của mình; khóa này không bao giờ rời khỏi Vault.
  • Ứng dụng gửi dữ liệu nhạy cảm đã mã hóa Base64 tới API POST /v1/transit/encrypt/orders-pii-key và nhận về bản mã có định dạng chuẩn hóa kèm phiên bản khóa: vault:v1:8SDd3WHDOjf7mq69CyCqYjBXAiQQAVZRkFM13ok481zoCmHnSeDX9vyf7w==.
  • Khi cần hiển thị, ứng dụng gửi chuỗi vault:v1:... tới POST /v1/transit/decrypt/orders-pii-key để lấy lại dữ liệu gốc.

4.2. Xoay vòng Khóa Không Gián đoạn (min_decryption_version, rewrap) và Mã hóa Phong bì (Envelope Encryption datakey) cho Dữ liệu Lớn

Transit Secrets Engine giải quyết triệt để hai bài toán hóc búa nhất của mật mã học ứng dụng theo tiêu chuẩn NIST SP 800-57:

  1. Xoay vòng khóa không gián đoạn và Tái bọc bản mã (Key Rotation & Rewrapping): Khi đến kỳ xoay vòng khóa hàng quý, lệnh vault write -f transit/keys/orders-pii-key/rotate tạo ra phiên bản khóa v2. Mọi bản ghi mới được mã hóa bằng v2 (vault:v2:...), trong khi các bản ghi cũ vault:v1:... vẫn giải mã bình thường. Để nâng cấp các bản mã cũ trong Database lên khóa v2 mà không làm lộ dữ liệu gốc ra ứng dụng, một tiến trình nền chỉ cần gọi POST /v1/transit/rewrap/orders-pii-key: Vault tự giải mã bằng v1, mã hóa lại bằng v2 và trả về vault:v2:... ngay bên trong bộ nhớ của Vault!
  2. Mã hóa Phong bì (Envelope Encryption với transit/datakey/plaintext) cho tệp dung lượng lớn: Bạn không nên gửi một tệp PDF hợp đồng nặng 50 MB qua mạng tới API của Vault để mã hóa. Thay vào đó, ứng dụng gọi POST /v1/transit/datakey/plaintext/orders-pii-key: Vault sinh ra một Khóa Mã hóa Dữ liệu (DEK — Data Encryption Key) 256-bit ngẫu nhiên và trả về cả bản rõ (plaintext DEK) lẫn bản đã được bọc bởi khóa chủ của Vault (ciphertext DEK vault:v1:...). Ứng dụng dùng plaintext DEK để mã hóa tệp 50 MB ngay tại bộ nhớ cục bộ với tốc độ gigabyte/giây, xóa ngay plaintext DEK khỏi RAM, và lưu ciphertext DEK cùng với tệp đã mã hóa lên S3.

Sơ đồ tuần tự dưới đây minh họa luồng cấp phát Dynamic PostgreSQL Credentials kết hợp mã hóa Transit EaaS từ một Pod trên Kubernetes:

sequenceDiagram
    autonumber
    participant K8sPod as Order Service Pod (K8s v1.32+)
    participant VaultCluster as Cụm HashiCorp Vault 1.18+ (Raft Leader)
    participant K8sTokenAPI as Kubernetes TokenReview API
    participant PgPrimary as Cụm PostgreSQL 17 Primary

    K8sPod->>VaultCluster: 1. POST /v1/auth/kubernetes/login (Role + Projected ServiceAccount JWT)
    VaultCluster->>K8sTokenAPI: 2. Xác minh chữ ký JWT, Namespace 'checkout' & ServiceAccount
    K8sTokenAPI-->>VaultCluster: 3. TokenReview Status: Authenticated = true
    VaultCluster-->>K8sPod: 4. Cấp Vault Client Token ngắn hạn (Gắn chính sách order-service-policy)

    K8sPod->>VaultCluster: 5. GET /v1/database/creds/order-service-rw
    VaultCluster->>PgPrimary: 6. CREATE ROLE "v-k8s-order-rw-9f2a..." VALID UNTIL '+1h'
    PgPrimary-->>VaultCluster: 7. Khởi tạo Role SQL thành công
    VaultCluster-->>K8sPod: 8. Trả Username/Password động + lease_id (TTL = 3600s)

    K8sPod->>VaultCluster: 9. POST /v1/transit/encrypt/orders-pii-key (Base64 Card/PII + Context)
    VaultCluster-->>K8sPod: 10. Trả bản mã: "vault:v2:9bK4mP..." (AES-256-GCM96)
    K8sPod->>PgPrimary: 11. INSERT INTO orders (..., encrypted_pii) bằng tài khoản SQL động

Dưới đây là mã nguồn Python 3.13+ chuẩn Production minh họa cả 3 thao tác với Transit Engine: mã hóa/giải mã trường PII, tái bọc khóa (rewrap) và mã hóa phong bì (Envelope Encryption) cho dữ liệu lớn bằng thư viện cryptography:

# File: vault_transit_eaas_client.py
# Yêu cầu: Python 3.13+, httpx >= 0.28.0, cryptography >= 44.0.0
from __future__ import annotations

import base64
import os
from dataclasses import dataclass
import httpx
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

@dataclass(frozen=True, slots=True)
class EnvelopeEncryptedPayload:
    wrapped_dek: str       # Khóa DEK đã được bọc bởi Vault KEK (vault:v2:...)
    nonce_b64: str         # Vector khởi tạo 96-bit (12 bytes) cho AES-GCM
    ciphertext_b64: str    # Dữ liệu lớn đã mã hóa cục bộ kèm Authentication Tag

class VaultTransitClient:
    """Client thực thi Encryption-as-a-Service (EaaS) & Envelope Encryption với HashiCorp Vault."""

    def __init__(self, vault_addr: str, vault_token: str, key_name: str = "orders-pii-key") -> None:
        self.key_name = key_name
        self._client = httpx.Client(
            base_url=vault_addr.rstrip("/"),
            headers={"X-Vault-Token": vault_token, "X-Vault-Request": "true"},
            timeout=httpx.Timeout(5.0, connect=2.0),
            verify="/etc/ssl/certs/infoteck-root-ca.pem",
        )

    def encrypt_field(self, plaintext: str) -> str:
        """Mã hóa trường dữ liệu nhạy cảm (PII/PCI) trực tiếp qua Vault Transit AES-256-GCM96."""
        b64_input = base64.b64encode(plaintext.encode("utf-8")).decode("ascii")
        resp = self._client.post(
            f"/v1/transit/encrypt/{self.key_name}",
            json={"plaintext": b64_input},
        )
        resp.raise_for_status()
        return resp.json()["data"]["ciphertext"]

    def decrypt_field(self, ciphertext: str) -> str:
        """Giải mã chuỗi 'vault:vN:...' về dữ liệu gốc."""
        resp = self._client.post(
            f"/v1/transit/decrypt/{self.key_name}",
            json={"ciphertext": ciphertext},
        )
        resp.raise_for_status()
        b64_plain = resp.json()["data"]["plaintext"]
        return base64.b64decode(b64_plain).decode("utf-8")

    def rewrap_ciphertext(self, legacy_ciphertext: str) -> str:
        """Nâng cấp bản mã cũ (vault:v1:...) lên phiên bản khóa mới nhất mà không lộ Plaintext."""
        resp = self._client.post(
            f"/v1/transit/rewrap/{self.key_name}",
            json={"ciphertext": legacy_ciphertext},
        )
        resp.raise_for_status()
        return resp.json()["data"]["ciphertext"]

    def envelope_encrypt_large_blob(self, raw_payload: bytes, aad: bytes) -> EnvelopeEncryptedPayload:
        """Mã hóa Phong bì (Envelope Encryption): Xin DEK 256-bit từ Vault và mã hóa cục bộ bằng AESGCM."""
        resp = self._client.post(f"/v1/transit/datakey/plaintext/{self.key_name}", json={"bits": 256})
        resp.raise_for_status()
        data = resp.json()["data"]

        # Giải mã khóa DEK tạm thời trong RAM, mã hóa dữ liệu rồi xóa ngay biến khóa
        dek_bytes = base64.b64decode(data["plaintext"])
        try:
            nonce = os.urandom(12)
            aesgcm = AESGCM(dek_bytes)
            encrypted_bytes = aesgcm.encrypt(nonce, raw_payload, aad)
        finally:
            del dek_bytes

        return EnvelopeEncryptedPayload(
            wrapped_dek=data["ciphertext"],
            nonce_b64=base64.b64encode(nonce).decode("ascii"),
            ciphertext_b64=base64.b64encode(encrypted_bytes).decode("ascii"),
        )


5. Tích hợp Vault với Kubernetes v1.32+: Xác thực Workload Identity và Đồng bộ qua Vault Secrets Operator (VSO) / ESO

5.1. Xác thực Kubernetes Pod qua Projected ServiceAccount JWT (auth/kubernetes) không cần lưu Token tĩnh

Làm thế nào một Pod trên Kubernetes nhận được VAULT_TOKEN đầu tiên để gọi tới Vault nếu chúng ta cấm lưu trữ token tĩnh? Giải pháp chuẩn trên Kubernetes v1.32+ là sử dụng Bound Projected ServiceAccount Tokens: Kubelet tự động gắn một JWT có thời hạn ngắn, có chữ ký số của cụm Kubernetes và ràng buộc chặt với audience: "https://vault.infoteck.internal" vào đường dẫn /var/run/secrets/tokens/vault-jwt của Pod. Pod gửi JWT này tới auth/kubernetes/login để đổi lấy Vault Token mà không cần bất kỳ bí mật khởi tạo nào trong Git.

5.2. So sánh Vault Agent Sidecar Injector, Vault Secrets Operator (VSO) và External Secrets Operator (ESO)

Khi tích hợp thông tin xác thực từ Vault vào các ứng dụng triển khai theo mô hình triển khai GitOps toàn diện với ArgoCD, Helm và Kustomize trên Kubernetes, kiến trúc sư hạ tầng có 3 phương án tiêu chuẩn:

Phương thức Tích hợp K8s Cơ chế Hoạt động Trường hợp Sử dụng Tối ưu
1. Vault Secrets Operator (VSO) Operator chính thức của HashiCorp đồng bộ cả KV v2 lẫn Dynamic Database Secrets (VaultDynamicSecret) kèm cơ chế tự động gia hạn Lease và cuộn lại (rollout restart) Deployment khi xoay vòng. Khuyên dùng hàng đầu cho các ứng dụng cần Dynamic Database Credentials hoặc PKI Certificates trên Kubernetes.
2. External Secrets Operator (ESO) Operator chuẩn CNCF đọc từ nhiều nguồn (Vault, AWS Secrets Manager, GCP) qua CRD ExternalSecret và tạo đối tượng Secret đồng bộ. Tối ưu cho kiến trúc Multi-Cloud GitOps cần một giao diện CRD thống nhất cho mọi nhà cung cấp.
3. Vault Agent Sidecar / CSI Driver Gắn trực tiếp tệp cấu hình chứa Secret vào vùng nhớ RAM (tmpfs /vault/secrets/db.ini) của Pod mà không bao giờ tạo đối tượng Kubernetes Secret trong etcd. Dành cho các workload tài chính/quốc phòng yêu cầu mức cô lập cao nhất, cấm lưu Secret vào etcd.

Dưới đây là cấu hình hoàn chỉnh của Vault Secrets Operator (VSO) sử dụng VaultAuth và VaultDynamicSecret để tự động cấp phát, gia hạn và xoay vòng thông tin xác thực PostgreSQL 17 cho order-service:

# File: k8s-vso-dynamic-postgres-secret.yaml
# Đồng bộ tự động Dynamic PostgreSQL 17 Credentials từ Vault vào K8s và tự động Rollout khi xoay vòng
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
  name: order-service-vault-auth
  namespace: checkout
spec:
  method: kubernetes
  mount: kubernetes
  kubernetes:
    role: order-service-role
    serviceAccount: order-service-sa
    audiences:
      - "https://vault.infoteck.internal"
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
  name: order-postgres-dynamic-creds
  namespace: checkout
spec:
  vaultAuthRef: order-service-vault-auth
  mount: database
  path: creds/order-service-rw
  # Tự động gia hạn Lease khi đã trôi qua 67% thời gian sống (TTL)
  renewalPercent: 67
  revoke: true
  destination:
    create: true
    name: order-db-runtime-secret
  # Tự động kích hoạt Rolling Restart không gián đoạn cho Deployment khi Lease đạt max_ttl (24h)
  rolloutRestartTargets:
    - kind: Deployment
      name: order-service


6. Quy trình Sao lưu Raft Snapshot, Khôi phục Thảm họa (DR) & Tài liệu Tham khảo Chuẩn Kỹ thuật

6.1. Tự động hóa vault operator raft snapshot save và Kiểm tra Tính Toàn vẹn

Vì toàn bộ dữ liệu trong Integrated Raft Storage đã được mã hóa bởi Rào cản Mật mã (AES-256-GCM), tệp bản chụp trạng thái Raft Snapshot hoàn toàn an toàn để đẩy lên kho lưu trữ đối tượng S3 ở một vùng địa lý khác phục vụ Khôi phục Thảm họa (Disaster Recovery). Dưới đây là kịch bản sao lưu nguyên tử kèm kiểm tra mã băm SHA-256 và mã hóa phía máy chủ S3 (SSE-KMS):

#!/usr/bin/env bash
# File: vault-raft-snapshot-backup.sh
# Sao lưu nguyên tử trạng thái cụm HashiCorp Vault Integrated Raft và đẩy lên S3 DR Bucket
set -Eeuo pipefail

export VAULT_ADDR="https://vault.infoteck.internal:8200"
TIMESTAMP="$(date -u +%Y%m%dT%H%M%SZ)"
BACKUP_DIR="$(mktemp -d /tmp/vault-snap.XXXXXX)"
SNAPSHOT_FILE="${BACKUP_DIR}/vault-raft-${TIMESTAMP}.snap"
trap 'rm -rf "${BACKUP_DIR}"' EXIT

echo "=== [1/3] Thực thi chụp ảnh trạng thái Raft từ nút Leader ==="
vault operator raft snapshot save "${SNAPSHOT_FILE}"

# Kiểm tra kích thước tệp Snapshot khác 0 và tính mã băm toàn vẹn SHA-256
if [[ ! -s "${SNAPSHOT_FILE}" ]]; then
  echo "ERROR: Tệp Raft Snapshot rỗng!" >&2
  exit 1
fi
sha256sum "${SNAPSHOT_FILE}" > "${SNAPSHOT_FILE}.sha256"

echo "=== [2/3] Đẩy bản sao lưu lên S3 Disaster Recovery Bucket kèm khóa SSE-KMS ==="
aws s3 cp "${SNAPSHOT_FILE}" "s3://infoteck-vault-dr-backups/raft/${TIMESTAMP}/" \
  --sse aws:kms \
  --sse-kms-key-id "alias/infoteck-dr-backup-key"
aws s3 cp "${SNAPSHOT_FILE}.sha256" "s3://infoteck-vault-dr-backups/raft/${TIMESTAMP}/"

echo "=== [3/3] Hoàn tất sao lưu Raft Snapshot an toàn: $(basename "${SNAPSHOT_FILE}") ==="

6.2. Bảng Kiểm toán 6 Nguyên tắc Vàng khi Vận hành Cụm Vault Production

  1. Thu hồi Root Token ngay sau khi khởi tạo (vault token revoke -self): Tài khoản Initial Root Token chỉ được dùng đúng một lần để cấu hình phương thức xác thực quản trị và chính sách ban đầu, sau đó bắt buộc phải bị hủy bỏ. Khi cần thao tác khẩn cấp, tạo lại Root Token tạm thời bằng quy trình vault operator generate-root kết hợp Quorum.
  2. Bật disable_mlock = false và tắt Swap trên các Node chạy Vault: Đảm bảo 100% khóa giải mã trong RAM không bao giờ bị ghi xuống đĩa cứng.
  3. Luôn kích hoạt tối thiểu 2 Audit Devices độc lập: Tránh gián đoạn dịch vụ do cơ chế Fail-Closed khi một kênh ghi nhật ký gặp sự cố.
  4. Áp dụng Time-To-Live (TTL) ngắn cho mọi Token và Dynamic Secret: Thiết lập default_ttl = 1h và max_ttl = 24h cho các tài khoản cơ sở dữ liệu động.
  5. Xoay vòng mật khẩu quản trị gốc (rotate-root) cho mọi kết nối Database: Đảm bảo chỉ riêng Vault biết mật khẩu của tài khoản quản trị tạo Role.
  6. Kiểm thử khôi phục Raft Snapshot (vault operator raft snapshot restore) định kỳ hàng tháng trên môi trường Staging độc lập.

6.3. Tài liệu Tham khảo & Nguồn Chuẩn Kỹ thuật (Primary Technical References)

Toàn bộ kiến trúc bảo mật, cấu hình HCL và mô hình mật mã trong bài viết này được trích dẫn và kiểm chứng từ các nguồn tài liệu kỹ thuật chính thống dưới đây:

  1. HashiCorp Vault Official Documentation — Security Model, Cryptographic Barrier & Shamir's Secret Sharing Architecture
  2. HashiCorp Vault Official Documentation — Integrated Storage (Raft) Backend Configuration & Production Hardening
  3. HashiCorp Vault Official Documentation — Database Secrets Engine (Dynamic PostgreSQL & MySQL Credentials)
  4. HashiCorp Vault Official Documentation — Transit Secrets Engine (Encryption-as-a-Service & Envelope Encryption)
  5. NIST Special Publication 800-57 Part 1 Rev. 5 — Recommendation for Cryptographic Key Management
  6. External Secrets Operator (ESO) Official Documentation — HashiCorp Vault Provider Specification
  7. Linux Foundation OpenBao Official Documentation — Open-Source Identity-Based Secrets & Encryption Management System