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à:
- Notification trigger: Tại thời điểm
next_rotation_time, Secret Manager gửi mộtSECRET_ROTATEmessage đến Pub/Sub topic - 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)
- 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
# 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âyRàng buộc:
rotationPeriodtối thiểu: 3600 giây (1 giờ)nextRotationTimephả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
nextRotationTimemới =lastRotationTime + rotationPeriod
Pub/Sub notification format
Khi đến next_rotation_time, Secret Manager publish message sau lên Pub/Sub topic:
{
"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
# 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:
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:
# 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: trueCơ chế hoạt động:
- Pod được schedule vào node
- kubelet gọi CSI driver để mount volume
- CSI driver (chạy trên node) gọi Secret Manager API bằng Workload Identity của Pod's ServiceAccount
- Secret values được write vào tmpfs (in-memory filesystem) trên node, gắn vào Pod mount point
- Application đọc file
/var/secrets/db-passwordnhư đọc file bình thường
Auto-rotation support (GKE 1.32.2+):
# 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 120sVớ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:
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: Memorylà bắt buộc — nếu không set, secret có thể được ghi ra disk- Nếu
google/cloud-sdkimage 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:
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: MemoryApproach 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 config | Lưu ở đâu |
|---|---|
| Database password, API key, private key | Secret Manager |
| Non-sensitive config (port, hostname, feature flags) | ConfigMap hoặc env vars |
| TLS certificates (public cert) | ConfigMap |
| TLS private key | Secret Manager hoặc cert-manager |
| Google Cloud credentials | Workload 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:
- Pinned version: Config reference tới version number cụ thể, không phải
latest - 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
- 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)