Danh sách bài viết

Bài 118: Secrets Management — Vault, Secrets Manager, K8s Secret

Redis deployment yêu cầu ít nhất ba loại secret: AUTH password, TLS private key/cert, ACL user credentials. Câu hỏi thực tế không phải "có cần secret không" mà là "lưu ở đâu, ai được đọc, khi nào rotate, nếu bị leak thì phát hiện ra sao". Bài này đi qua các storage level từ tệ tới tốt, cách dùng HashiCorp Vault (static + dynamic secrets, AppRole), AWS Secrets Manager, GCP Secret Manager, K8s Secret với etcd encryption, Sealed Secrets, External Secrets Operator, CSI Secrets Store Driver, quy trình dual-password rotation không downtime, phát hiện secret leakage, và tập hợp anti-patterns hay gặp trong production.

01/06/2026
20 phút đọc
0 lượt xem
1

Mục Tiêu Bài Học

  • Hiểu tại sao hardcode secret hay commit .env là anti-pattern nghiêm trọng.
  • Nắm được các tầng lưu trữ secret và trade-off của từng tầng.
  • Biết cách dùng HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager cho Redis credentials.
  • Hiểu caveat của K8s Secret (base64, không phải encrypted) và cách fix bằng etcd encryption.
  • Biết cách dùng Sealed Secrets, External Secrets Operator, CSI Secrets Store Driver.
  • Nắm quy trình dual-password rotation không downtime và cách detect secret leakage.
2

Ba Loại Secret Trong Redis Deployment

Một Redis deployment production thường cần quản lý ba nhóm secret:

  1. AUTH password / ACL user credentials: password để client xác thực với Redis. Với Redis 6+ ACL, mỗi user (app service, monitoring agent, admin) có password riêng. Bài 113 đã đề cập ACL; bài này tập trung vào việc lưu trữ và rotate các credential đó.
  2. TLS private key và certificate: private key của Redis server (và client nếu dùng mTLS) cần được bảo vệ. Ai có private key có thể impersonate Redis hoặc decrypt traffic đã capture. TLS chi tiết đã ở bài 115; ở đây ta xem cert như một loại secret cần lifecycle management.
  3. Replication và Cluster credentials: masterauth cho replica-to-master auth, cluster bus auth nếu bật. Đây thường bị bỏ sót khi chỉ bảo vệ client connection.

Ba câu hỏi cần trả lời cho mỗi loại secret:

  • Where to store? — Nơi lưu trữ đảm bảo encryption at rest và access control.
  • How to access? — App lấy secret như thế nào: env var, mounted file, API call.
  • How to rotate? — Khi nào rotate, quy trình ra sao, có gây downtime không.

Anti-pattern phổ biến nhất: plaintext trong source code hoặc git

# KHÔNG làm thế này — password hardcode trong code
import redis
r = redis.Redis(host="redis.example.com", password="SuperSecret123")
# KHÔNG commit file này vào git
# .env
REDIS_PASSWORD=SuperSecret123
REDIS_TLS_KEY_PATH=/certs/server.key

Khi password đã commit vào git, git history vẫn giữ nó dù bạn xoá file sau đó. Rotation không đủ nếu git history đã bị access — cần scan history và revoke ngay lập tức.

3

Ba Nguyên Tắc Nền

Bất kể dùng tool nào, ba nguyên tắc sau phải được đảm bảo:

  1. Never store in code or git. Source code và git repository nên được xem là public theo mặc định — cả contractor, third-party CI runner, và người dùng fork đều có thể đọc. Secret không bao giờ xuất hiện trong code, config file tracked bởi git, hay CI logs.
  2. Encrypt at rest. Secret phải được mã hoá khi lưu trữ, không chỉ khi truyền. Điều này áp dụng cho: database lưu secret (Vault backend, AWS KMS), etcd trong K8s (cần bật encryption provider), file trên disk.
  3. Access controlled and audited. Mỗi service, user, hay job chỉ được đọc đúng secret mà nó cần (least privilege). Mọi lần đọc secret phải để lại audit trail: ai đọc gì, lúc nào. Nếu có unauthorized access, audit log là bằng chứng để triage.
4

Storage Levels — Từ Tệ Tới Tốt

Level Ví dụ Mức độ an toàn Vấn đề
Hardcode source password="abc" trong code WORST Ai đọc code thấy secret. Git history không xoá được.
Plain env var export REDIS_PASSWORD=abc Weak ps aux hay /proc/[pid]/environ leak secret. Subprocesses inherit env.
Encrypted config file Ansible Vault, SOPS OK Encryption key cần quản lý riêng. Rotation thủ công. File trên disk.
Dedicated secrets manager Vault, AWS SM, GCP SM BEST Phức tạp hơn nhưng có audit log, rotation tự động, access control chi tiết.

Plain env var thường được xem là "đủ tốt" nhưng có hai rủi ro hay bị bỏ qua:

  • Trên Linux, /proc/[pid]/environ chứa toàn bộ environment của process — bất kỳ user nào trên cùng host có thể đọc nếu permissions không đúng.
  • Crash dump, debug log, monitoring agent đôi khi dump toàn bộ env variables.
5

HashiCorp Vault — Kiến Trúc & Cách Hoạt Động

HashiCorp Vault (open source, MPL-2.0) là secrets engine dùng phổ biến nhất trong self-hosted và hybrid cloud. Vault có hai loại secret:

  • Static secrets (KV secrets engine): lưu key-value, app fetch về và dùng. Giống password manager nhưng có API, audit log, policy.
  • Dynamic secrets: Vault generate credential on-demand, tự expire sau TTL. Ví dụ: database secret engine tạo PostgreSQL user tạm thời per-request.

Vault authentication methods (cách app chứng minh identity với Vault trước khi đọc secret):

  • AppRole: phù hợp cho service/CI không chạy trên cloud VM hay K8s.
  • Kubernetes: pod auth bằng ServiceAccount JWT, không cần quản lý credential riêng.
  • AWS IAM: EC2 instance hoặc Lambda auth bằng IAM role.
  • JWT/OIDC: GitHub Actions, GitLab CI dùng OIDC token.

Audit log là built-in — Vault ghi mọi request (read, write, login) vào audit device (file, syslog). Đây là feature quan trọng so với plain env var hay K8s Secret.

Lưu và đọc Redis password với Vault KV

# Bật KV secrets engine (version 2 — có versioning)
vault secrets enable -path=secret kv-v2

# Lưu Redis password
vault kv put secret/redis/prod \
  password="StrongP@ss!2024" \
  acl_app_password="AppP@ss!2024" \
  acl_monitor_password="MonP@ss!2024"

# Đọc toàn bộ secret
vault kv get secret/redis/prod

# Đọc chỉ một field (dùng trong script)
vault kv get -field=password secret/redis/prod

# Đọc version cụ thể (KV v2 giữ history)
vault kv get -version=3 secret/redis/prod

KV v2 giữ lại các phiên bản cũ (mặc định 10 version). Hữu ích khi rotation gặp sự cố — có thể rollback về version trước mà không mất dữ liệu.

6

Vault AppRole Pattern

AppRole là auth method dùng cho service không chạy trên cloud có IAM hay K8s có ServiceAccount. App authenticate bằng hai thứ: role_id (public, định danh role) và secret_id (private, one-time-use hoặc có TTL).

# Bước 1: Admin setup — tạo policy cho phép đọc Redis secret
vault policy write redis-read - <<EOF
path "secret/data/redis/prod" {
  capabilities = ["read"]
}
EOF

# Bước 2: Bật AppRole và tạo role
vault auth enable approle

vault write auth/approle/role/redis-app \
  policies="redis-read" \
  token_ttl=1h \
  token_max_ttl=4h \
  secret_id_ttl=24h \
  secret_id_num_uses=10  # secret_id hết hạn sau 10 lần dùng

# Bước 3: Lấy role_id (public — có thể bake vào image)
ROLE_ID=$(vault read -field=role_id auth/approle/role/redis-app/role-id)

# Bước 4: Lấy secret_id (private — phải bảo vệ, inject lúc runtime)
SECRET_ID=$(vault write -field=secret_id -f auth/approle/role/redis-app/secret-id)

# Bước 5: App login để lấy Vault token
VAULT_TOKEN=$(vault write -field=token auth/approle/login \
  role_id="$ROLE_ID" \
  secret_id="$SECRET_ID")

# Bước 6: Dùng token để đọc secret
REDIS_PASS=$(VAULT_TOKEN=$VAULT_TOKEN vault kv get -field=password secret/redis/prod)

Lưu ý về secret_id: secret_id là credential nhạy cảm nhất trong AppRole. Cần được inject vào app lúc startup (qua init container, CI/CD pipeline) chứ không được bake vào image hay hardcode. Dùng secret_id_num_uses=1 cho one-time use nếu app chỉ cần login một lần lúc khởi động.

Python — đọc Redis password từ Vault lúc startup

import os
import hvac  # pip install hvac
import redis

def get_redis_client() -> redis.Redis:
    vault_addr = os.environ["VAULT_ADDR"]           # https://vault.example.com
    role_id    = os.environ["VAULT_ROLE_ID"]        # Baked vào config / image
    secret_id  = os.environ["VAULT_SECRET_ID"]      # Injected tại runtime

    # Authenticate với AppRole
    client = hvac.Client(url=vault_addr)
    resp = client.auth.approle.login(
        role_id=role_id,
        secret_id=secret_id,
    )
    client.token = resp["auth"]["client_token"]

    # Đọc Redis credentials
    secret = client.secrets.kv.v2.read_secret_version(
        path="redis/prod",
        mount_point="secret",
    )
    data = secret["data"]["data"]

    return redis.Redis(
        host=os.environ["REDIS_HOST"],
        port=int(os.environ.get("REDIS_PORT", 6379)),
        username="app-user",
        password=data["acl_app_password"],
        ssl=True,
        ssl_ca_certs="/etc/redis-certs/ca.crt",
        ssl_cert_reqs="required",
    )

r = get_redis_client()
7

Vault Dynamic Secrets

Dynamic secrets là trường hợp Vault tạo credential mới mỗi lần app yêu cầu, thay vì trả về credential đã lưu sẵn. Credential đó tự expire sau TTL và Vault revoke nó trên hệ thống đích.

Ví dụ phổ biến nhất là Database Secrets Engine: Vault kết nối đến PostgreSQL và CREATE USER vault_app_abc123 WITH PASSWORD 'x' VALID UNTIL '...' khi app request. Sau TTL, Vault drop user đó.

Với Redis, Vault có Redis Database Secrets Engine (từ Vault 1.6+):

# Bật database secrets engine
vault secrets enable database

# Cấu hình Redis connection
vault write database/config/redis-prod \
  plugin_name=redis-database-plugin \
  host="redis.example.com" \
  port=6379 \
  tls=true \
  ca_cert="$(cat /etc/redis-certs/ca.crt)" \
  username="vault-admin" \
  password="AdminPass" \
  allowed_roles="redis-app-role"

# Tạo role — Vault sẽ tạo ACL user với các quyền này
vault write database/roles/redis-app-role \
  db_name=redis-prod \
  creation_statements='["ACL SETUSER {{username}} on >{{password}} ~cache:* +GET +SET +DEL +EXPIRE"]' \
  default_ttl=1h \
  max_ttl=4h

# App request credential
vault read database/creds/redis-app-role
# Key                Value
# username           v-approle-redis-app-Q3xKs7
# password           A1b2C3d4...
# lease_duration     1h

Trade-off của dynamic secrets:

  • Ưu điểm: không có long-lived credential, mỗi app instance có user riêng, compromise một credential không ảnh hưởng instance khác, revoke tức thì khi cần.
  • Nhược điểm: Redis ACL user list tăng liên tục (cần cleanup expired), app cần logic re-authenticate khi credential hết hạn, phức tạp hơn static secrets.
8

AWS Secrets Manager

AWS Secrets Manager là managed service phù hợp cho stack AWS. Authentication qua IAM role — EC2 instance, ECS task, Lambda đều có IAM role attached và được phép đọc secret mà không cần quản lý credential riêng.

# Tạo secret
aws secretsmanager create-secret \
  --name "redis/prod/credentials" \
  --description "Redis prod AUTH + ACL credentials" \
  --secret-string '{
    "password": "StrongP@ss!2024",
    "acl_app_password": "AppP@ss!2024",
    "acl_monitor_password": "MonP@ss!2024"
  }'

# Đọc secret (trả về JSON string)
aws secretsmanager get-secret-value \
  --secret-id "redis/prod/credentials" \
  --query SecretString \
  --output text

# Đọc chỉ password (kết hợp với jq)
aws secretsmanager get-secret-value \
  --secret-id "redis/prod/credentials" \
  --query SecretString \
  --output text | jq -r '.password'

IAM policy — least privilege

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue",
        "secretsmanager:DescribeSecret"
      ],
      "Resource": "arn:aws:secretsmanager:ap-southeast-1:123456789:secret:redis/prod/*"
    }
  ]
}

Python — boto3

import json, boto3, redis, os

def get_redis_client() -> redis.Redis:
    sm = boto3.client("secretsmanager", region_name="ap-southeast-1")
    resp = sm.get_secret_value(SecretId="redis/prod/credentials")
    creds = json.loads(resp["SecretString"])

    return redis.Redis(
        host=os.environ["REDIS_HOST"],
        port=6379,
        username="app-user",
        password=creds["acl_app_password"],
        ssl=True,
        ssl_ca_certs="/etc/redis-certs/ca.crt",
    )

Auto-rotation: AWS Secrets Manager hỗ trợ rotation tự động qua Lambda function. AWS cung cấp template Lambda cho các dịch vụ phổ biến (RDS, Redshift). Với Redis, cần viết custom Lambda: Lambda gọi redis-cli ACL SETUSER để set password mới, rồi update secret. Audit trail qua AWS CloudTrail — mọi GetSecretValue đều được log.

9

GCP Secret Manager

GCP Secret Manager tương tự AWS Secrets Manager nhưng trên Google Cloud. Authentication qua Workload Identity (GKE) hoặc Service Account.

# Tạo secret từ stdin (tránh secret xuất hiện trong shell history)
echo -n "StrongP@ss!2024" | gcloud secrets create redis-prod-password \
  --replication-policy=automatic \
  --data-file=-

# Thêm version mới (khi rotate)
echo -n "NewP@ss!2025" | gcloud secrets versions add redis-prod-password \
  --data-file=-

# Đọc version latest
gcloud secrets versions access latest --secret=redis-prod-password

# Đọc version cụ thể
gcloud secrets versions access 3 --secret=redis-prod-password

# List versions và trạng thái
gcloud secrets versions list redis-prod-password

GCP Secret Manager có versioning built-in — mỗi lần update tạo version mới, version cũ có thể disable hoặc destroy. Rotation được thực hiện bằng cách add version mới, update app, rồi disable version cũ.

Workload Identity là cách tốt nhất để GKE pod authenticate: pod có K8s ServiceAccount được bind với GCP Service Account qua IAM. Không cần export service account JSON key.

10

K8s Secret — Caveat & Etcd Encryption

K8s Secret là cách đơn giản nhất để inject credential vào Pod, nhưng có caveat quan trọng cần hiểu trước khi dùng.

apiVersion: v1
kind: Secret
metadata:
  name: redis-credentials
  namespace: production
type: Opaque
stringData:
  password: "StrongP@ss!2024"
  acl_app_password: "AppP@ss!2024"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  template:
    spec:
      containers:
        - name: app
          image: myapp:latest
          env:
            - name: REDIS_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: redis-credentials
                  key: acl_app_password
          # Hoặc mount toàn bộ secret thành volume
          volumeMounts:
            - name: redis-creds
              mountPath: /etc/redis-creds
              readOnly: true
      volumes:
        - name: redis-creds
          secret:
            secretName: redis-credentials

Caveat quan trọng: K8s Secret chỉ là base64

# K8s Secret lưu data dưới dạng base64 — KHÔNG phải encrypted
kubectl get secret redis-credentials -o jsonpath='{.data.password}' | base64 -d
# In ra plaintext password ngay lập tức

# Ai có kubectl get secret trong namespace là đọc được toàn bộ

Base64 là encoding, không phải encryption. Bất kỳ ai có quyền kubectl get secret trong namespace, hoặc đọc được etcd backup, đều thấy plaintext.

Bật encryption at rest cho etcd

K8s cho phép configure encryption providers để encrypt Secret objects trước khi ghi vào etcd:

# /etc/kubernetes/enc/encryption-config.yaml (trên control plane)
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:                         # AES-CBC với random key
          keys:
            - name: key1
              secret: <base64-encoded-32-byte-key>
      - identity: {}                    # Fallback: existing unencrypted secrets
# Thêm flag vào kube-apiserver (kubeadm: /etc/kubernetes/manifests/kube-apiserver.yaml)
# --encryption-provider-config=/etc/kubernetes/enc/encryption-config.yaml

# Sau khi bật, encrypt tất cả Secret hiện có
kubectl get secrets --all-namespaces -o json | kubectl replace -f -

# Verify: đọc trực tiếp etcd — value phải có prefix k8s:enc:aescbc:
ETCDCTL_API=3 etcdctl get /registry/secrets/production/redis-credentials \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

Ngoài AES-CBC, K8s 1.29+ hỗ trợ kms provider để dùng external KMS (AWS KMS, GCP KMS, HashiCorp Vault) cho envelope encryption — key encryption key được giữ bên ngoài cluster.

11

Sealed Secrets — Commit-Safe Manifest

Sealed Secrets (Bitnami / VMware Tanzu, MIT license) giải quyết bài toán GitOps: làm sao commit secret manifest vào git mà không leak plaintext.

Cơ chế:

  1. Controller chạy trong cluster, giữ private key.
  2. Developer dùng kubeseal CLI để encrypt Secret bằng public key của controller → tạo SealedSecret resource.
  3. SealedSecret (đã encrypted) được commit vào git — an toàn vì chỉ controller trong cluster mới decrypt được.
  4. Controller watch SealedSecret, decrypt, tạo Secret thực sự trong cluster.
# Cài kubeseal CLI
brew install kubeseal  # macOS
# hoặc download từ https://github.com/bitnami-labs/sealed-secrets/releases

# Tạo Secret thường (không apply vào cluster)
kubectl create secret generic redis-credentials \
  --from-literal=password="StrongP@ss!2024" \
  --dry-run=client -o yaml > redis-secret.yaml

# Encrypt thành SealedSecret
kubeseal --format=yaml \
  --controller-namespace=kube-system \
  --controller-name=sealed-secrets-controller \
  < redis-secret.yaml > redis-sealed-secret.yaml

# File này an toàn để commit git
cat redis-sealed-secret.yaml
# apiVersion: bitnami.com/v1alpha1
# kind: SealedSecret
# spec:
#   encryptedData:
#     password: AgBy3...  (ciphertext — chỉ controller decrypt được)

# Apply vào cluster (hoặc qua GitOps pipeline)
kubectl apply -f redis-sealed-secret.yaml

Scope: mặc định SealedSecret được binding với namespace + Secret name. Nếu ai copy SealedSecret manifest sang cluster khác, họ không decrypt được (private key khác). Có thể cấu hình --scope=cluster-wide nếu cần dùng chung.

12

External Secrets Operator

External Secrets Operator (ESO, Apache-2.0) là K8s operator tự động sync secret từ external secrets manager (Vault, AWS SM, GCP SM, Azure Key Vault...) vào K8s Secret. Đây là pattern phổ biến nhất trong production K8s kết hợp với cloud secrets manager.

# SecretStore — định nghĩa nguồn secret
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-backend
  namespace: production
spec:
  provider:
    vault:
      server: "https://vault.example.com"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "redis-app"
---
# ExternalSecret — khai báo mapping: key trong Vault → key trong K8s Secret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: redis-credentials
  namespace: production
spec:
  refreshInterval: 1h            # Sync lại mỗi 1h — nhận rotation tự động
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: redis-credentials      # Tên K8s Secret được tạo
    creationPolicy: Owner
  data:
    - secretKey: password
      remoteRef:
        key: redis/prod
        property: password
    - secretKey: acl_app_password
      remoteRef:
        key: redis/prod
        property: acl_app_password

Khi Vault có secret mới (sau rotation), ESO detect sự thay đổi trong interval tiếp theo và update K8s Secret. Pod cần restart hoặc dùng secret mount dưới dạng volume (files được update tự động) để pickup password mới.

ESO hỗ trợ ClusterSecretStore để dùng chung cho nhiều namespace — phù hợp khi có nhiều team chia sẻ cùng Vault backend.

13

CSI Secrets Store Driver

Secrets Store CSI Driver (CNCF) là cách mount secret từ external provider (Vault, AWS Secrets Manager, Azure Key Vault, GCP Secret Manager) trực tiếp vào Pod dưới dạng volume — secret không đi qua K8s Secret (trừ khi bật sync).

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: redis-vault-provider
  namespace: production
spec:
  provider: vault
  parameters:
    vaultAddress: "https://vault.example.com"
    roleName: "redis-app"
    objects: |
      - objectName: "redis-password"
        secretPath: "secret/data/redis/prod"
        secretKey: "password"
---
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
        - name: app
          volumeMounts:
            - name: redis-secrets
              mountPath: "/mnt/redis-secrets"
              readOnly: true
          # App đọc secret từ file: /mnt/redis-secrets/redis-password
      volumes:
        - name: redis-secrets
          csi:
            driver: secrets-store.csi.k8s.io
            readOnly: true
            volumeAttributes:
              secretProviderClass: "redis-vault-provider"

Ưu điểm của CSI driver so với External Secrets Operator:

  • Secret được mount dưới dạng file trong memory (tmpfs) — không persist trên disk.
  • Secret không đi qua etcd (trừ khi bật syncSecret để tạo K8s Secret đồng thời).
  • Auto-rotation: khi secret thay đổi trong provider, CSI driver update file trong volume đang mount mà không cần Pod restart (tính năng này cần bật rotation polling).
14

TLS Cert Management

TLS cert cần lifecycle management riêng vì có expiry date. Ba công cụ phổ biến:

  • cert-manager (K8s): CNCF project tự động issue và renew TLS cert trong K8s. Hỗ trợ nhiều issuer: Let's Encrypt (ACME), CA tự quản, Vault PKI. Cert được lưu trong K8s Secret và tự được renew trước khi hết hạn.
  • Vault PKI Engine: Vault tự act như CA, issue cert với TTL ngắn (giờ đến ngày). App request cert khi cần, TTL ngắn nên không cần revocation. Phù hợp cho internal service mesh.
  • AWS Certificate Manager (ACM): managed CA cho stack AWS. Auto-renew, tích hợp với ELB/ALB. Phù hợp cho cert trên load balancer trước Redis.
# cert-manager — tự động issue cert cho Redis TLS
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: redis-tls
  namespace: production
spec:
  secretName: redis-tls-secret        # cert/key được lưu vào Secret này
  duration: 2160h                     # 90 ngày
  renewBefore: 360h                   # Renew khi còn 15 ngày
  dnsNames:
    - redis.production.svc.cluster.local
    - redis.example.com
  issuerRef:
    name: internal-ca
    kind: ClusterIssuer

Với cert được mount vào Redis pod, Redis 6+ có thể reload cert không cần restart (CONFIG SET tls-cert-file ...) khi cert-manager renew và update K8s Secret. Có thể dùng sidecar container watch file thay đổi và trigger reload.

15

Dual-Password Rotation Không Downtime

Redis ACL không hỗ trợ "thêm password thứ hai cho cùng user" — mỗi ACL user có một password. Tuy nhiên, vẫn có thể rotation không downtime bằng cách dùng hai ACL user:

Quy trình dual-user rotation

  1. Tạo user mới với password mới, cùng permissions với user cũ:
    redis-cli ACL SETUSER app-user-v2 on >NewStrongP@ss \
      ~cache:* +GET +SET +DEL +EXPIRE +TTL
  2. Update secrets manager với credential mới:
    vault kv patch secret/redis/prod \
      acl_app_username="app-user-v2" \
      acl_app_password="NewStrongP@ss"
  3. Rolling restart app — từng instance pickup credential mới từ secrets manager. Trong thời gian rolling restart, cả hai user đều active nên không có downtime.
  4. Verify — kiểm tra toàn bộ app instances đã dùng user-v2 bằng log hoặc Redis CLIENT LIST.
  5. Disable user cũ:
    redis-cli ACL SETUSER app-user off
  6. Chờ một thời gian (15-30 phút) để chắc chắn không còn connection dùng user cũ, rồi xoá:
    redis-cli ACL DELUSER app-user

Với Redis Enterprise hoặc Redis 7.x ACL với multiple passwords per user (nếu config hỗ trợ), quy trình đơn giản hơn: thêm password mới, rotate app, xoá password cũ.

Lưu ý: requirepass (password cho default user) cũng cần quy trình tương tự khi dùng replica — cần update cả masterauth trên replica đồng bộ với requirepass trên master.

16

Audit Access

Audit log trả lời câu hỏi: ai đọc secret nào, lúc nào, từ đâu. Cần thiết cho compliance (SOC 2, ISO 27001) và triage khi có incident.

  • HashiCorp Vault: audit log built-in, bật qua vault audit enable file file_path=/var/log/vault/audit.log. Log mọi request: auth, read, write, revoke. Format JSON.
  • AWS Secrets Manager: mọi GetSecretValue, CreateSecret, DeleteSecret đều vào CloudTrail. Filter bằng EventName + ResourceName trong CloudWatch.
  • GCP Secret Manager: Cloud Audit Logs ghi mọi access. Filter bằng resource.type="secretmanager.googleapis.com/Secret" trong Log Explorer.
  • K8s: bật audit policy trong kube-apiserver để log request tới /api/v1/namespaces/*/secrets.
# K8s audit policy — log GET/LIST trên secrets
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
  - level: Metadata              # Log metadata, không log body
    resources:
      - group: ""
        resources: ["secrets"]
    verbs: ["get", "list", "watch"]
  - level: RequestResponse       # Log đầy đủ khi create/update/delete
    resources:
      - group: ""
        resources: ["secrets"]
    verbs: ["create", "update", "patch", "delete"]

Alert nên được set cho: truy cập secret ngoài giờ business, từ IP lạ, bởi principal không expected, hoặc số lượng read đột biến (có thể là credential stuffing hoặc data exfiltration).

17

Local Dev Secrets

Dev local cần Redis credentials để chạy, nhưng không nên dùng prod secrets. Quy tắc:

  • Tách biệt hoàn toàn: dev/staging/prod có credentials riêng. Không bao giờ dùng prod password trên máy local.
  • .env file local: lưu dev credentials trong .env, thêm vào .gitignore. Credentials dev có thể yếu hơn (ví dụ password=devpassword) vì Redis chỉ chạy local.
    # .env (local only, gitignored)
    REDIS_HOST=localhost
    REDIS_PORT=6379
    REDIS_PASSWORD=devpassword
    
    # .gitignore — phải có dòng này
    .env
    *.env
    .env.*
    !.env.example   # .env.example (không có giá trị thật) thì ok commit
  • direnv: tự động load .envrc khi vào thư mục project. Phù hợp khi làm việc với nhiều project có credentials khác nhau.
  • Docker Compose secrets: dùng Docker Compose secrets section để inject credential vào container khi dev local.
# .env.example — template an toàn để commit, không có giá trị thật
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=CHANGE_ME
18

Secret Leakage Detection

Ngay cả với quy trình tốt, secret vẫn có thể leak qua nhiều đường: developer commit nhầm, CI log, paste vào Slack. Cần detection layer:

  • git-secrets (AWS Labs): pre-commit hook scan diff trước khi commit. Chặn commit chứa pattern trông giống secret (password, API key, AWS key...).
    brew install git-secrets
    git secrets --install           # Cài hook vào repo hiện tại
    git secrets --register-aws      # Thêm AWS secret patterns
    # Thêm custom pattern cho Redis password format
    git secrets --add "redis.*password.*=.{8,}"
  • TruffleHog (Trufflesecurity): scan toàn bộ git history để tìm secret đã commit. Phát hiện entropy cao (random string trông giống credential) và match regex patterns.
    trufflehog git file://. --since-commit HEAD~100 --only-verified
  • GitHub Secret Scanning: GitHub tự động scan mọi push lên public repo (và private repo với Advanced Security). Khi phát hiện secret pattern đã biết (AWS key, GCP credential, Vault token...), GitHub alert ngay lập tức và có thể notify provider để revoke.
  • gitleaks: công cụ mã nguồn mở detect secret trong git repos và CI/CD pipelines, hỗ trợ nhiều format hơn git-secrets.

Nếu phát hiện secret đã leak lên public repo: revoke ngay lập tức (đừng chờ "xem có ai đọc không"), rotate credential, scan history để xem còn credential nào khác không.

19

Anti-patterns & Best Practices

Anti-patterns

  • Hardcode password trong source code: bất kỳ ai đọc code hoặc decompile binary đều thấy.
  • Commit .env vào git: git history giữ mãi dù xoá file sau.
  • Pass password qua command-line argument: redis-cli -a password hiển thị trong ps aux và shell history.
  • K8s Secret mà không bật etcd encryption: base64 không phải encryption — ai đọc được etcd đọc được toàn bộ secret.
  • Dùng chung secret giữa dev/staging/prod: nếu dev env bị compromise, attacker có prod credential.
  • Không có rotation policy: credential tồn tại vĩnh viễn; nếu bị leak vào log cũ, attacker vẫn dùng được.
  • Không có audit log: khi có incident, không biết ai đã đọc secret và khi nào.
  • Vault token long-lived hardcode: Vault token là secret; không được lưu trong config hay image.

Best Practices

  • Dùng dedicated secrets manager (Vault hoặc cloud-native) làm single source of truth.
  • Automation rotation khi có thể — không dựa vào lịch rotation thủ công.
  • Per-environment unique secrets — dev, staging, prod có credentials riêng biệt.
  • Audit log đầy đủ và alert cho unauthorized access.
  • Encryption at rest: etcd encryption (K8s), KMS backend (Vault), KMS key (AWS SM).
  • Detection scanning: git-secrets pre-commit và TruffleHog/gitleaks scan định kỳ.
  • Least privilege: mỗi service chỉ được đọc credential của chính nó.
  • Document rotation procedure — ai làm, bằng công cụ gì, bao lâu, rollback như thế nào khi lỗi.
  • Dùng Vault Agent sidecar hoặc CSI driver để inject secret vào pod — app không cần biết về Vault API.
20

Tổng Kết & Quiz

Tổng kết

  • Redis deployment cần quản lý ít nhất ba loại secret: AUTH/ACL credentials, TLS private key/cert, replication auth. Hardcode hoặc commit vào git là anti-pattern nghiêm trọng nhất.
  • Ba nguyên tắc nền: never in code/git, encrypt at rest, access controlled + audited.
  • Storage levels: hardcode (WORST) → plain env var (weak) → encrypted config (OK) → dedicated secrets manager (BEST).
  • HashiCorp Vault hỗ trợ static secrets (KV) và dynamic secrets (generate on-demand). AppRole là auth method phổ biến cho service. Vault K8s auth là cách tốt nhất cho K8s pod.
  • AWS Secrets Manager và GCP Secret Manager là managed alternatives, auth bằng IAM/Service Account — phù hợp khi đã dùng cloud tương ứng.
  • K8s Secret chỉ là base64 — cần bật etcd encryption provider để có encryption at rest thực sự.
  • Sealed Secrets: encrypt manifest để commit git an toàn. External Secrets Operator: sync từ external SM vào K8s Secret. CSI Secrets Store Driver: mount secret trực tiếp từ provider vào pod, không qua etcd.
  • Dual-user rotation (tạo user mới → rolling restart → disable user cũ) đảm bảo rotation không downtime với Redis ACL.
  • Audit log là bắt buộc: Vault audit device, CloudTrail (AWS), Cloud Audit Logs (GCP), K8s audit policy.
  • Detection: git-secrets pre-commit, TruffleHog scan history, GitHub Secret Scanning tự động.

Quiz 5 câu

  1. K8s Secret lưu data dưới dạng base64. Tại sao đây là vấn đề bảo mật và cần làm gì để thực sự encrypt Secret at rest trong K8s?
  2. Giải thích sự khác biệt giữa Vault static secrets (KV engine) và dynamic secrets (Database secrets engine). Khi nào nên dùng dynamic secrets thay vì static?
  3. Bạn cần rotate Redis ACL password mà không gây downtime. Mô tả quy trình dual-user rotation từng bước.
  4. So sánh ba cách inject secret vào K8s Pod: (a) env from K8s Secret, (b) External Secrets Operator, (c) CSI Secrets Store Driver. Mỗi cách có trade-off gì?
  5. Developer vô tình commit file .env chứa Redis password lên GitHub public repo, sau đó xoá file và push lại. Tại sao password vẫn bị leak và cần làm những gì ngay lập tức?

Đáp án gợi ý

  1. Base64 là encoding, không phải encryption — ai có quyền kubectl get secret hoặc đọc etcd backup đều decode được ngay. Để thực sự encrypt: bật --encryption-provider-config trên kube-apiserver với provider aescbc hoặc kms (KMS provider dùng external key management như AWS KMS hoặc Vault). Sau khi bật, cần replace tất cả Secret hiện có để được re-encrypt.
  2. Static secrets: lưu credential đã biết trong Vault KV, app fetch về và dùng cho đến khi rotate. Dynamic secrets: Vault generate credential mới on-demand cho mỗi request, tự expire. Dùng dynamic khi cần per-instance isolation (compromise một app không ảnh hưởng app khác), audit per-instance, và zero long-lived credential. Trade-off: phức tạp hơn, app cần logic re-auth khi credential hết hạn.
  3. (1) Tạo ACL user mới app-user-v2 với password mới và cùng permissions. (2) Update Vault/secrets manager với username và password mới. (3) Rolling restart app — từng instance fetch credential mới từ secrets manager; cả hai user đều active nên không downtime. (4) Verify toàn bộ instance đã dùng user-v2 qua CLIENT LIST hoặc log. (5) Disable user cũ: ACL SETUSER app-user off. (6) Sau 15-30 phút confirm không còn connection dùng user cũ, ACL DELUSER app-user.
  4. (a) Env from K8s Secret: đơn giản nhất, nhưng Secret phải tồn tại trong etcd, cần etcd encryption, app cần restart để nhận giá trị mới. (b) ESO: sync từ external SM (Vault, AWS SM...) vào K8s Secret tự động theo interval, có thể auto-pickup rotation, nhưng vẫn đi qua etcd. (c) CSI driver: mount trực tiếp từ provider, không lưu vào etcd (nếu không bật syncSecret), update file trong volume khi provider thay đổi, nhưng app đọc từ file thay vì env — cần code thay đổi đôi khi.
  5. Git lưu toàn bộ history; dù xoá file và push lại, commit cũ chứa .env vẫn tồn tại trong repo history — bất kỳ ai clone, fork, hoặc đã pull trước đó đều có file đó. Phải làm ngay: (1) Revoke password ngay lập tức — rotate Redis password, không chờ. (2) Kiểm tra audit log xem có access nào dùng password đó chưa. (3) Dùng git filter-repo để rewrite history xoá file (force push, notify collaborators). (4) GitHub Secret Scanning có thể đã alert — acknowledge và verify rotation xong. (5) Review xem có secret nào khác trong cùng file hoặc commit lân cận không.

Bài tiếp theo

Bài 119 tổng hợp Anti-patterns Hardening & Incident — các lỗi hardening phổ biến nhất trong production và cách xử lý incident khi Redis bị tấn công hoặc misconfiguration.

Tham khảo