Skip to content

Secret Rotation & Các Patterns Truy Cập Secrets trong GKE

Tại sao rotation và access pattern quan trọng

Lưu trữ secret an toàn chỉ giải quyết một nửa bài toán. Nửa còn lại — và thường bị bỏ qua — là: làm thế nào để secret đến được tay application mà không tạo ra attack surface mới, và làm thế nào để rotate khi secret bị lộ hoặc hết hạn mà không gây downtime.

Hầu hết các production incident liên quan đến secrets không xảy ra tại lớp lưu trữ (Secret Manager đủ an toàn) mà xảy ra tại lớp truyền tải: secret được copy vào biến môi trường rồi bị log, secret được mount dưới dạng file nhưng không reload khi rotate, hoặc rotation process cập nhật Secret Manager nhưng không notify application để reload.

Chương này phân tích rotation flow từ góc độ cơ chế, và so sánh các access patterns cho GKE.


Internal model — Secret rotation flow

Rotation là gì (về mặt kỹ thuật)

Secret Manager không tự động thay đổi giá trị của secret. "Rotation" trong Secret Manager chỉ là:

  1. Notification trigger: Tại thời điểm next_rotation_time, Secret Manager gửi một SECRET_ROTATE message đến Pub/Sub topic
  2. Your code xử lý: Subscriber nhận message và thực hiện rotation logic (tạo secret value mới, gọi API của service để cập nhật credential, tạo secret version mới)
  3. Application reload: Application nhận notification (hoặc poll) và load secret version mới

Secret Manager không biết làm thế nào để tạo secret value mới — đây là business logic của bạn. Ví dụ: rotation một database password yêu cầu:

  • Kết nối database và tạo password mới
  • Cập nhật Cloud SQL user với password mới
  • Tạo Secret Manager version mới với password đó
  • Notify application để disconnect và reconnect với credential mới

Cấu trúc rotation schedule

yaml
# Cấu hình khi tạo secret
rotation:
  nextRotationTime: "2026-07-01T00:00:00Z"   # Thời điểm trigger đầu tiên
  rotationPeriod: "2592000s"                   # 30 ngày = 2,592,000 giây

Ràng buộc:

  • rotationPeriod tối thiểu: 3600 giây (1 giờ)
  • nextRotationTime phải trong tương lai ít nhất 5 phút từ thời điểm set
  • Sau mỗi rotation notification, Secret Manager tự động tính nextRotationTime mới = lastRotationTime + rotationPeriod

Pub/Sub notification format

Khi đến next_rotation_time, Secret Manager publish message sau lên Pub/Sub topic:

json
{
  "name": "projects/123/secrets/database-password/versions/5",
  "labels": {
    "environment": "production"
  },
  "rotationSchedule": {
    "nextRotationTime": "2026-07-01T00:00:00Z",
    "rotationPeriod": "2592000s"
  },
  "eventType": "SECRET_ROTATE"
}

Điều quan trọng: Message chứa metadata về secret, không chứa secret value. Subscriber phải tự gọi AccessSecretVersion() nếu cần đọc giá trị.

Setup Pub/Sub cho rotation

bash
# 1. Tạo Pub/Sub topic
gcloud pubsub topics create secret-rotation-topic

# 2. Grant Secret Manager quyền publish
gcloud pubsub topics add-iam-policy-binding secret-rotation-topic \
  --member="serviceAccount:service-PROJECT_NUMBER@gcp-sa-secretmanager.iam.gserviceaccount.com" \
  --role="roles/pubsub.publisher"

# 3. Cấu hình rotation cho secret
gcloud secrets update database-password \
  --next-rotation-time="2026-07-01T00:00:00Z" \
  --rotation-period="2592000s" \
  --topics="projects/my-project/topics/secret-rotation-topic"

Failure mode: Rotation notification bị miss

Nếu subscriber không available khi notification được gửi, Pub/Sub sẽ retry theo backoff policy trong 7 ngày (mặc định). Sau 7 ngày, message bị drop.

Điều này tạo ra một edge case nguy hiểm: nếu rotation service bị down trong đúng thời điểm next_rotation_time, và không ai monitor Pub/Sub dead letter queue, secret có thể không được rotate. Cần set up dead letter topic và alert khi có message trong đó.


Accessing secrets trong GKE — ba approach

Có ba cách phổ biến để application trong GKE truy cập Secret Manager secrets. Mỗi cách có trade-off khác nhau về độ phức tạp, security boundary, và khả năng auto-refresh.

Approach 1: Direct API Call (SDK/library)

Application gọi Secret Manager API trực tiếp trong code:

python
from google.cloud import secretmanager

def get_database_password() -> str:
    client = secretmanager.SecretManagerServiceClient()
    response = client.access_secret_version(
        request={
            "name": "projects/my-project/secrets/db-password/versions/latest"
        }
    )
    return response.payload.data.decode("UTF-8")

Ưu điểm:

  • Đơn giản nhất, không cần infrastructure overhead
  • Application kiểm soát hoàn toàn khi và cách refresh secret
  • Có thể implement caching và refresh logic theo yêu cầu

Nhược điểm:

  • Cần Workload Identity được cấu hình đúng (không phải vấn đề lớn trong GKE mới)
  • Secret làm secret material trong memory của application — nếu heap dump hoặc core dump bị lộ, secret bị lộ theo
  • Developer phải chủ động gọi API (không transparent)

Khi nào dùng: Application cần kiểm soát caching, cần đọc nhiều secrets, hoặc cần dynamic refresh logic.


Approach 2: Secret Manager CSI Driver (GKE Add-on)

Secret Manager CSI Driver là add-on GKE (GA từ GKE 1.27+) cho phép mount secrets dưới dạng filesystem volumes trong Pod.

Architecture:

┌─────────────────────────────────────────────┐
│  GKE Node                                    │
│  ┌─────────────────────────────────────────┐│
│  │  secrets-store-gke-csi-driver DaemonSet ││
│  │  (chạy trên mỗi node)                  ││
│  └──────────────────┬──────────────────────┘│
│                     │ mount                   │
│  ┌──────────────────▼──────────────────────┐│
│  │  Pod                                    ││
│  │  /var/secrets/db-password → "my-pass"  ││
│  └─────────────────────────────────────────┘│
└─────────────────────────────────────────────┘
         │ AccessSecretVersion()

   Secret Manager API
   (authenticated via Workload Identity)

Cấu hình:

yaml
# SecretProviderClass — định nghĩa secrets nào cần mount
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: my-app-secrets
spec:
  provider: gke
  parameters:
    secrets: |
      - resourceName: "projects/my-project/secrets/db-password/versions/latest"
        path: "db-password"
      - resourceName: "projects/my-project/secrets/api-key/versions/5"
        path: "api-key"
---
# Pod manifest
apiVersion: v1
kind: Pod
spec:
  serviceAccountName: my-app-sa   # phải có IAM binding với Secret Manager
  volumes:
  - name: secrets-store
    csi:
      driver: secrets-store-gke.csi.k8s.io
      readOnly: true
      volumeAttributes:
        secretProviderClass: my-app-secrets
  containers:
  - name: my-app
    volumeMounts:
    - name: secrets-store
      mountPath: /var/secrets
      readOnly: true

Cơ chế hoạt động:

  1. Pod được schedule vào node
  2. kubelet gọi CSI driver để mount volume
  3. CSI driver (chạy trên node) gọi Secret Manager API bằng Workload Identity của Pod's ServiceAccount
  4. Secret values được write vào tmpfs (in-memory filesystem) trên node, gắn vào Pod mount point
  5. Application đọc file /var/secrets/db-password như đọc file bình thường

Auto-rotation support (GKE 1.32.2+):

bash
# Bật auto-rotation khi enable add-on
gcloud container clusters update my-cluster \
  --enable-secret-manager-rotation \
  --secret-manager-rotation-interval=300  # 5 phút, minimum 120s

Với rotation enabled, CSI driver định kỳ re-fetch secret values từ Secret Manager và update file trên disk. Application phải tự phát hiện file thay đổi và reload (inotify, polling, hoặc SIGHUP signal).

Hạn chế quan trọng:

  • Linux only, không hỗ trợ Windows Server nodes
  • Không support sync sang Kubernetes Secret objects
  • Minimum GKE version: 1.27.14-gke.1042001
  • File mount là read-only tmpfs — không thể write vào

Approach 3: Init Container Pattern

Dùng init container để fetch secrets từ Secret Manager rồi pass cho main container qua shared volume:

yaml
apiVersion: v1
kind: Pod
spec:
  serviceAccountName: my-app-sa
  initContainers:
  - name: secret-fetcher
    image: google/cloud-sdk:slim
    command:
    - /bin/bash
    - -c
    - |
      gcloud secrets versions access latest \
        --secret="database-password" \
        --project="my-project" \
        > /shared/db-password
    volumeMounts:
    - name: shared-secrets
      mountPath: /shared

  containers:
  - name: my-app
    volumeMounts:
    - name: shared-secrets
      mountPath: /secrets
      readOnly: true

  volumes:
  - name: shared-secrets
    emptyDir:
      medium: Memory  # tmpfs, không persist trên disk

Ưu điểm: Đơn giản, không cần CSI driver, hoạt động trên bất kỳ Kubernetes version nào.

Nhược điểm nghiêm trọng:

  • Secrets chỉ được fetch một lần khi Pod khởi động — không auto-refresh. Nếu secret rotate, phải restart Pod
  • emptyDir.medium: Memory là bắt buộc — nếu không set, secret có thể được ghi ra disk
  • Nếu google/cloud-sdk image có vulnerability, nó trở thành attack vector trong secret fetching path

Sidecar Pattern (ít phổ biến hơn)

Một sidecar container chạy liên tục, định kỳ fetch secrets và update file trong shared volume:

yaml
containers:
- name: my-app
  volumeMounts:
  - name: secrets-vol
    mountPath: /secrets

- name: secret-refresher
  image: my-custom-refresher
  volumeMounts:
  - name: secrets-vol
    mountPath: /secrets
  env:
  - name: REFRESH_INTERVAL
    value: "300"

volumes:
- name: secrets-vol
  emptyDir:
    medium: Memory

Approach này thường được thay thế bởi CSI driver trong GKE modern vì CSI driver làm đúng việc này ở tầng node (hiệu quả hơn, không cần sidecar per Pod).


Secret Manager vs Environment Variables — trade-offs thực sự

Đây là debate phổ biến nhất khi team bắt đầu dùng Secret Manager. Câu trả lời ngắn: environment variables không phải lựa chọn an toàn cho production secrets, nhưng lý do tại sao thì quan trọng hơn câu trả lời.

Vấn đề với Environment Variables

1. Leakage qua debug endpoints

Rất nhiều framework expose /env hoặc /debug/vars endpoint trong development mode. Nếu endpoint này accidentaly được enable trong production, tất cả env vars — bao gồm secrets — bị lộ.

Spring Boot Actuator, Flask debug mode, Django DEBUG=True, Node.js --inspect đều có thể expose env vars. Đây không phải lỗi hypothetical — đây là vector thực tế của nhiều breach.

2. Inherited bởi child processes

Mọi subprocess spawned bởi application tự động inherit environment variables của parent process. Nếu application spawn shell commands (ví dụ: subprocess.run() trong Python), secrets có thể leak vào subprocess và từ đó có thể được log hoặc expose.

3. Visible trong Kubernetes manifest và audit logs

Kubernetes Secrets được stored trong etcd dưới dạng base64 (không mã hoá theo mặc định nếu không bật etcd encryption). Hơn nữa, manifest YAML trong git repo thường contain references đến env vars, và trong môi trường không có vault, chính env var value xuất hiện trong manifests.

4. Không có versioning hoặc audit trail

Không có cách biết:

  • Ai thay đổi env var
  • Khi nào nó thay đổi
  • Giá trị cũ là gì
  • Ai đã access

5. Rotation yêu cầu restart Pod

Khi cần rotate một secret được inject qua env var, cần restart Pod. Trong production system với nhiều replicas, điều này ảnh hưởng đến availability nếu không được quản lý cẩn thận.

Khi nào env vars ĐƯỢC PHÉP

Không phải tất cả config đều cần Secret Manager. Nguyên tắc phân loại:

Loại configLưu ở đâu
Database password, API key, private keySecret Manager
Non-sensitive config (port, hostname, feature flags)ConfigMap hoặc env vars
TLS certificates (public cert)ConfigMap
TLS private keySecret Manager hoặc cert-manager
Google Cloud credentialsWorkload Identity (không cần store gì)

Secret Manager không giải quyết được gì

Secret Manager giải quyết storage và access control, không giải quyết application-level secret handling. Dù dùng Secret Manager, nếu application đọc secret ra rồi log nó, expose qua debug endpoint, hoặc store trong core dump, secret vẫn bị lộ.

Bảo mật là defense-in-depth: Secret Manager là một lớp, nhưng application code cũng phải treat secrets đúng cách.


Pattern production: Immutable infrastructure + versioned secrets

Trong môi trường GitOps/CD hiện đại, pattern tốt nhất kết hợp:

  1. Pinned version: Config reference tới version number cụ thể, không phải latest
  2. Secret rotation pipeline: CI/CD pipeline tự động:
    • Tạo secret version mới
    • Test secret hoạt động
    • Cập nhật reference trong config (version number mới)
    • Deploy application với config mới
  3. Old version cleanup: Sau khi deployment thành công và verified, disable/destroy old version
[Security team rotate secret]


[Rotation service: tạo secret version mới]


[CI/CD pipeline: detect version mới, update manifest]


[Deploy new Pods với pinned version mới]


[Health check pass → disable old version]

Pattern này cho phép:

  • Zero-downtime rotation (new Pods dùng version mới, old Pods vẫn dùng version cũ cho đến khi gracefully terminated)
  • Rollback dễ dàng (chỉ cần re-enable old version và rollback deployment)
  • Full audit trail (mỗi version là immutable với timestamp)

References