← DevOps← DevOps
DevOpsDevOps18 Th7, 2026Jul 18, 202624 phút đọc20 min read

Secret Management (Quản lý Secret)Secret Management

Thuộc knowledge base theo DevOps Roadmap.

Tổng quan

Secret management là kỷ luật lưu trữ, phân phối, xoay vòng (rotate) và audit các thông tin nhạy cảm — API key, mật khẩu database, chứng chỉ TLS, SSH key, encryption key, OAuth client secret và access token. Bất kỳ hệ thống thực tế nào cũng phụ thuộc vào hàng chục secret như vậy, và mỗi secret là một điểm có thể bị khai thác: một AWS key bị lộ có thể bị lợi dụng đào coin gây thiệt hại hàng nghìn đô la chỉ trong vài giờ, còn một mật khẩu database bị lộ có thể phơi bày toàn bộ dữ liệu khách hàng. Secret là “chìa khóa vương quốc”, và khác với một server bị chiếm hay một website bị deface, một credential bị đánh cắp thường cấp quyền truy cập âm thầm, đã được xác thực — trông y hệt traffic hợp lệ.

Vấn đề cốt lõi là secret có xu hướng “lan tỏa” một cách tự nhiên. Developer dán chúng vào file .env, commit lên Git “tạm thời thôi”, nhúng vào Docker image, in ra log CI, hardcode trong Terraform, hoặc gửi qua Slack. Các nghiên cứu quét GitHub public liên tục phát hiện hàng triệu credential còn hiệu lực bị commit nhầm mỗi năm. Một khi secret đã lọt vào Git history, phải coi như nó đã bị lộ — rewrite history không thể “thu hồi” nó khỏi các fork, clone, CI cache và các con bot tự động quét mọi lần push public chỉ trong vài giây.

Với DevOps engineer, secret management nằm ở giao điểm giữa security và automation: pipeline, container và infrastructure-as-code đều cần credential lúc runtime, nhưng không thứ nào trong đó được phép chứa credential. Các hệ thống trưởng thành sẽ tập trung secret vào một kho chuyên dụng (Vault hoặc cloud secret manager), inject lúc runtime với thời hạn ngắn, rotate tự động, cưỡng chế least privilege theo từng workload, và liên tục quét phát hiện rò rỉ. Mô hình tư duy cần khắc cốt ghi tâm: coi mỗi secret là một thứ sống, nguy hiểm, có chủ sở hữu rõ ràng, vòng đời ngắn và audit trail đầy đủ — không phải một chuỗi tĩnh cấu hình một lần rồi quên.

Vì sao chuyện này ngày càng quan trọng hơn trước

Kiến thức nền tảng

Vì sao secret không được nằm trong code hay file env

Mục tiêu: secret phải được tập trung hóa, mã hóa at rest và in transit, kiểm soát truy cập, audit đầy đủ, có vòng đời ngắn và inject lúc runtime — không bao giờ lưu cạnh code sử dụng nó, và không lưu ở bất cứ đâu mà con người đọc được một cách tùy tiện.

Bài toán “secret zero”

Mọi cơ chế phân phối đều đối mặt một câu hỏi “con gà quả trứng”: để fetch được secret, workload phải xác thực trước — nhưng bằng cái gì? Nếu câu trả lời là “một secret khác”, bạn chỉ đang dời vấn đề đi chỗ khác. Đây là bài toán secret zero (bootstrap). Lời giải hiện đại là workload identity: nền tảng (platform) bảo chứng danh tính của workload (Kubernetes service account token, AWS instance role, GCP metadata-server identity, OIDC token từ CI), và secret store tin vào danh tính do platform cấp thay vì một mật khẩu pre-shared. Secret zero trở thành thứ do platform quản lý và rotate hộ bạn, không phải thứ bạn phải lưu.

Vòng đời của secret

  1. Tạo — sinh với entropy đủ mạnh, lý tưởng nhất là do chính secret store sinh ra để plaintext không bao giờ tồn tại trên máy developer.
  2. Lưu trữ — mã hóa at rest trong hệ thống chuyên dụng, không phải Git/wiki/chat/hệ thống ticket.
  3. Phân phối — giao cho workload lúc runtime dựa trên danh tính (identity) đã xác thực, không phải một shared secret khác (bài toán secret zero).
  4. Sử dụng — đọc vào memory ở thời điểm muộn nhất có thể, không ghi plaintext ra disk, không log.
  5. Rotation — thay thế theo lịch hoặc theo yêu cầu, lý tưởng là tự động và không downtime.
  6. Thu hồi — vô hiệu hóa ngay khi bị lộ hoặc khi lease hết hạn.
  7. Audit — ghi log mọi lần đọc/ghi kèm ai/cái gì/khi nào/ở đâu, và alert được khi bất thường.

Static secret vs dynamic secret

Static secretDynamic secret
Vòng đờiSống lâu tới khi rotate thủ côngLease ngắn (vài phút tới vài giờ), tự hết hạn
Cách tạoTạo một lần, nhiều bên dùng chungSinh theo từng request/consumer, theo yêu cầu
Blast radius khi bị đánh cắpToàn phần, cho tới khi ai đó phát hiện và rotateGần bằng không — credential tự chết
Truy nguyên (attribution)Dùng chung, khó biết consumer nào làm lộMỗi consumer một credential riêng
Ví dụAPI key trong vault, KV secretUser Postgres theo phiên, IAM credential tạm thời

Dynamic secret là đòn bẩy lớn nhất trong secret management: một credential database có lease 1 giờ biến một vụ lộ từ thảm họa thành phiền toái nhỏ, và tự động cho bạn khả năng truy nguyên theo từng consumer.

HashiCorp Vault

Vault là secret manager self-hosted / trung lập cloud tiêu chuẩn de facto. Các thành phần kiến trúc chính:

# KV (v2) tĩnh — key-value có version
vault kv put secret/myapp/db password='s3cr3t' username='app'
vault kv get -field=password secret/myapp/db
vault kv get -version=3 secret/myapp/db      # phục hồi một version cũ

# Credential database động — mỗi request một user mới, tự hết hạn
vault read database/creds/readonly-role
# Key                Value
# lease_id           database/creds/readonly-role/abc123
# lease_duration     1h
# username           v-token-readonly-x7Kq...   (vừa được tạo)
# password           A1a-9nB2...                (chết khi lease kết thúc)

# Encryption-as-a-service: app không bao giờ thấy key
vault write transit/encrypt/orders plaintext=$(base64 <<< "4111-1111-1111-1111")

# Xử lý sự cố: revoke mọi thứ mà một role bị compromise từng cấp
vault lease revoke -prefix database/creds/readonly-role

Mô hình tư duy về Vault: Vault là một broker, không phải tủ hồ sơ. Giá trị cao nhất của nó không phải lưu chuỗi tĩnh (cloud manager cũng làm được) mà là đúc ra credential ngắn hạn theo từng consumer và các phép crypto theo yêu cầu, để secret sống lâu ngừng tồn tại trong hệ thống của bạn.

Cloud secret managers

Dịch vụĐiểm mạnhGhi chú / giới hạn
AWS Secrets ManagerRotation tự động qua Lambda, tích hợp RDS/Redshift/DocumentDB, truy cập cross-account qua resource policy, secret dạng JSON~0.40 USD/secret/tháng + 0.05 USD mỗi 10k API call
AWS SSM Parameter StoreMiễn phí (standard param), SecureString qua KMS, path phân cấp, phù hợp cho cả config lẫn secretKhông có rotation tích hợp; giới hạn 4 KB (standard) / 8 KB (advanced); throughput thấp hơn
Azure Key VaultSecret, key và certificate trong một dịch vụ; key software hoặc HSM-backed; RBAC access policy cũ; tier managed HSMTích hợp sâu Entra ID (Azure AD); soft-delete + purge protection
GCP Secret ManagerSecret có version, truy cập theo IAM, hỗ trợ CMEK, chính sách replication tự độngRotation native chỉ là thông báo (Pub/Sub) — bạn tự viết function rotate

Cloud manager thắng về độ đơn giản vận hành (không phải chạy cluster, không nghi thức seal/unseal), tích hợp IAM native (workload identity có sẵn) và trả theo mức dùng. Vault thắng về dynamic secret cho nhiều hệ thống, tính di động multi-cloud (một API bất kể provider) và các engine nâng cao (transit encryption, PKI nội bộ, SSH CA). Nhiều tổ chức chạy cả hai: cloud manager cho secret tĩnh cloud-native, Vault cho credential động và crypto xuyên suốt.

KMS vs secret manager — một nhầm lẫn phổ biến

Một Key Management Service (AWS KMS, Cloud KMS, key trong Azure Key Vault) quản lý encryption key và thực hiện các phép crypto; key material không bao giờ rời KMS. Một secret manager lưu và phân phối các giá trị secret tùy ý (và thường dùng KMS bên dưới để mã hóa at rest). Quy tắc: dùng KMS khi cần mã hóa/giải mã/ký dữ liệu mà không lộ key; dùng secret manager khi workload cần chính giá trị secret (mật khẩu, API token). Engine transit của Vault chính là chức năng kiểu KMS nằm trong Vault.

Khái niệm chính

Kubernetes Secrets và những hạn chế

Object Secret gốc của K8s mặc định chỉ base64-encode, không mã hóa — base64 là một cách encode chứ không phải cipher, base64 -d đảo ngược tức thì. Chúng được lưu trong etcd, dưới dạng plaintext trừ khi bật encryption at rest bằng EncryptionConfiguration (và kể cả khi đó, nên dùng KMS provider thay vì key aescbc tĩnh — vì key tĩnh nằm ngay trên disk của API server). Ngoài vấn đề lưu trữ, các bề mặt lộ thực sự là:

Nên mount secret dưới dạng file (volume tmpfs) thay vì env var, siết chặt RBAC, và dùng một trong các pattern dưới đây để giữ plaintext ngoài Git.

Cách tiếp cậnCơ chếPhù hợp khiCần lưu ý
External Secrets Operator (ESO)Đồng bộ secret từ Vault/AWS/Azure/GCP thành K8s Secret qua CRD ExternalSecretGitOps: manifest chỉ tham chiếu tên, giá trị nằm ở store bên ngoàiGiá trị vẫn nằm trong etcd; vẫn phải bật encryption etcd
Sealed Secrets (Bitnami)kubeseal mã hóa secret bằng public key của controller trong cluster; CR SealedSecret commit lên Git an toànGitOps đơn giản, không cần chạy store bên ngoàiPrivate key của controller cực kỳ quan trọng — phải backup; giải mã gắn với cluster
SOPS (+ age/KMS)Mã hóa giá trị bên trong file YAML/JSON nhưng giữ key đọc được; tích hợp Flux/Argo CDMã hóa nguyên file config trong Git, thân thiện với git-diffPhân phối/rotate key là việc của bạn; ai giữ age/KMS key thì giải mã được
Vault Agent Injector / Vault Secrets OperatorInjector thêm sidecar/init container render secret vào volume dùng chung; VSO đồng bộ qua CRDCluster đã dùng Vault; hỗ trợ dynamic secret và auto-renewSidecar tốn tài nguyên pod; VSO là hướng mới, khai báo (declarative) hơn
Secrets Store CSI DriverMount secret từ store bên ngoài dưới dạng volume; có thể không tạo K8s Secret luônTránh hoàn toàn việc lưu vào etcd; tiêu thụ dạng fileTiêu thụ dạng env var lại cần sync ngược thành K8s Secret (mất ý nghĩa)
# Ví dụ External Secrets Operator
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: myapp-db
spec:
  refreshInterval: 1h                 # re-sync để rotation lan truyền
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: myapp-db                    # tên K8s Secret được tạo ra
    creationPolicy: Owner
  data:
    - secretKey: password             # key trong K8s Secret
      remoteRef:
        key: secret/myapp/db          # path trong Vault
        property: password

Hướng dẫn chọn: Không có store bên ngoài và chỉ muốn secret ra khỏi Git → Sealed Secrets hoặc SOPS. Đã chạy Vault/cloud secret manager và muốn K8s mirror lại → ESO hoặc Vault/Secrets CSI. Cần secret động (có lease) trong pod → Vault (Injector/VSO/CSI). Muốn không bao giờ ghi vào etcd → CSI driver với mount dạng file.

Ví dụ flow: Vault + Kubernetes auth

  1. Bật Kubernetes auth method trong Vault, cấu hình với địa chỉ API server và CA của cluster (Vault dùng chúng để xác thực token).
  2. Gắn một role Vault với namespace + service account cụ thể, kèm policy và TTL.
  3. Pod (qua Vault Agent, VSO, hoặc app trực tiếp) gửi projected service account JWT lên endpoint login của Vault.
  4. Vault xác thực JWT qua Kubernetes TokenReview API — chính cluster bảo chứng rằng token thuộc về service account của pod đó.
  5. Vault trả về một Vault token ngắn hạn, giới hạn theo policy của role.
  6. Workload đọc secret tĩnh của mình, hoặc yêu cầu credential DB động và nhận username/password có lease.
  7. Vault Agent/VSO renew token và các lease ở background; khi pod kết thúc, các lease được revoke.
vault auth enable kubernetes
vault write auth/kubernetes/config \
    kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443"

vault write auth/kubernetes/role/myapp \
    bound_service_account_names=myapp \
    bound_service_account_namespaces=prod \
    policies=myapp-read \
    ttl=1h

Không còn secret zero: danh tính của pod (service account token do Kubernetes ký, và Kubernetes tự rotate) chính là credential. Không có gì sống lâu được pre-share cả.

Phát hiện secret bị rò rỉ

Chạy scanner ở ba nơi: pre-commit hook (bắt trước khi rời laptop), CI pipeline (cưỡng chế để không gì merge được), và quét full-history định kỳ (bắt rò rỉ cũ và rule detector mới). Defense in depth, vì con người sẽ lọt qua bất kỳ cổng đơn lẻ nào.

Nếu secret bị lộ — rotate trước, dọn dẹp sau. Thứ tự rất quan trọng:

  1. Revoke/rotate credential ngay lập tức. Chuỗi bị lộ đã compromise ngay khi công khai; một con bot có thể đã cầm nó rồi.
  2. Audit xem đã bị dùng độc hại chưa. Kiểm tra CloudTrail/audit log xem có truy cập từ IP lạ hay vào giờ bất thường không.
  3. Chỉ sau đó mới rewrite history (git filter-repo, BFG) — và hiểu rằng đây là vệ sinh, không phải biện pháp khắc phục. Fork, clone và cache vẫn còn blob cũ, đó chính là lý do bước 1 mới là thứ thực sự bảo vệ bạn.
  4. Hậu sự cố: tìm ra nó lộ như thế nào và bịt đường đó (thêm pre-commit hook, siết CI, sửa chỗ logging đã in nó ra).

Rotation và least privilege

Mã hóa in transit và at rest

Secret phải được mã hóa cả at rest (trong backend của store, trong etcd, trong backup) lẫn in transit (TLS khắp nơi — client tới Vault, Vault tới database, ESO tới cloud API). Một secret manager mã hóa at rest nhưng bị query qua HTTP plaintext thì chỉ được bảo vệ một nửa. Envelope encryption (một data key mã hóa secret, một KMS root key mã hóa data key) là pattern chuẩn cho phép rotate root key mà không phải mã hóa lại từng secret.

Best Practices

  1. Không bao giờ commit secret — và cưỡng chế bằng tooling, không dựa vào niềm tin. Pre-commit hook gitleaks + quét trong CI + GitHub push protection. Con người sẽ sơ suất; tooling nhiều lớp phải bắt được. Một chính sách dựa vào việc developer “cẩn thận” thì không phải là một control.
  2. Tập trung vào một kho chuyên dụng. Một nguồn sự thật duy nhất (Vault hoặc cloud secret manager) có mã hóa, ACL, versioning và audit log — không phải file .env, wiki, hệ thống ticket hay chat. Phân mảnh là kẻ thù của kiểm soát.
  3. Ưu tiên credential động, thời hạn ngắn. Credential database hoặc cloud có lease 1 giờ biến một vụ lộ từ thảm họa thành phiền toái nhỏ và cho bạn khả năng truy nguyên theo consumer miễn phí.
  4. Dùng workload identity, không dùng bootstrap secret dùng chung. Kubernetes service account, cloud IAM role và OIDC federation giải quyết bài toán secret zero — platform chứng minh danh tính để bạn khỏi lưu mật khẩu bootstrap.
  5. Tự động hóa rotation và thiết kế app chịu được nó. Rotation thủ công sẽ không diễn ra. Gắn rotation vào store và làm app đọc lại secret (hoặc dùng pattern dual-credential) để rotation không gây downtime.
  6. Áp dụng least privilege theo từng workload. Mỗi app một identity, một policy, một path hẹp; không bao giờ dùng một kho “tất cả trong một”. Audit policy định kỳ để phát hiện scope creep và revoke quyền khi offboarding.
  7. Mã hóa Kubernetes secret at rest và siết RBAC. Bật encryption etcd bằng KMS provider, mount secret dạng file thay vì env var, và giới hạn chặt ai được get/list Secret trên toàn cluster.
  8. Giữ secret ngoài image, log và output CI. Không bao giờ ENV/ARG secret trong Dockerfile (nó tồn tại trong image layer); mask secret trong CI; cẩn thận với set -x, error message dài dòng và công cụ error-tracking chụp lại environment.
  9. Luôn mã hóa in transit. TLS từ client tới store và từ store tới các hệ thống backend. Mã hóa at rest vô nghĩa nếu giá trị đi qua mạng dưới dạng plaintext.
  10. Audit mọi thứ và alert khi bất thường. Bật Vault audit device / CloudTrail / Key Vault diagnostics, và cảnh báo khi có điều bất thường — một CI token đọc path production lúc 3 giờ sáng, một đợt tăng vọt số lần đọc, truy cập từ IP mới.
  11. Coi mọi secret đã lộ là đã compromise — rotate trước. Đừng tin vào “chỉ public có một phút” hay việc rewrite history. Revoke ngay, rồi mới điều tra và dọn dẹp.
  12. Version hóa secret và có kế hoạch rollback. KV v2 / secret version trên cloud giúp phục hồi tức thì khi rotation lỗi thay vì cuống cuồng dựng lại giá trị cũ.
  13. Có quy trình break-glass. Ghi rõ cách con người truy cập secret quan trọng khi automation gặp sự cố — kèm MFA, yêu cầu phê duyệt và review hậu kiểm. Test trước khi cần đến.
  14. Tách secret theo môi trường và theo tenant. Prod, staging và dev không được dùng chung credential; một vụ lộ ở môi trường ít tin cậy không bao giờ được cấp quyền vào prod.
  15. Giảm tối đa số người từng thấy plaintext. Ưu tiên secret do store sinh để plaintext không tồn tại trên laptop; dùng phê duyệt và quyền truy cập giới hạn thời gian cho vài trường hợp hiếm hoi con người phải đọc.
  16. Mỗi secret phải có chủ sở hữu và ngày hết hạn được ghi rõ. Secret vô chủ, không bao giờ hết hạn tích tụ thành gánh nặng không quản lý nổi. Kiểm kê chúng và loại bỏ những cái không dùng.
  17. Backup và DR-test cho secret store. Secret manager là một single point of failure; chạy HA, backup storage và unseal/root key một cách an toàn, và diễn tập phục hồi.

Tài liệu tham khảo

Part of the DevOps Roadmap knowledge base.

Overview

Secret management is the discipline of storing, distributing, rotating, and auditing sensitive credentials — API keys, database passwords, TLS certificates, SSH keys, encryption keys, OAuth client secrets, and access tokens. Every non-trivial system depends on dozens of these, and each one is a potential breach vector: a leaked AWS key can cost thousands of dollars in cryptomining within hours, and a leaked database password can expose your entire customer dataset. Secrets are the keys to the kingdom, and unlike a stolen server or a defaced website, a stolen credential often grants silent, authenticated access that looks exactly like legitimate traffic.

The core problem is that secrets naturally want to spread. Developers paste them into .env files, commit them to Git “just for now”, bake them into Docker images, print them in CI logs, hardcode them in Terraform, and share them over Slack. Studies of public GitHub repositories consistently find millions of live credentials committed by accident every year. Once a secret touches Git history, it must be considered compromised — rewriting history does not un-leak it from forks, clones, CI caches, and the automated scrapers that watch every public push within seconds.

For DevOps engineers, secret management sits at the intersection of security and automation: pipelines, containers, and infrastructure-as-code all need credentials at runtime, but none of them should ever contain credentials. Mature setups centralize secrets in a dedicated store (Vault, a cloud secret manager), inject them at runtime with short lifetimes, rotate them automatically, enforce least privilege per workload, and continuously scan for leaks. The mental model to internalize: treat every secret as a live, dangerous thing with a definite owner, a short lifetime, and a full audit trail — not as a static string you configure once and forget.

Why this matters more than it used to

Fundamentals

Why secrets don’t belong in code or env files

The goal: secrets should be centralized, encrypted at rest and in transit, access-controlled, audited, short-lived, and injected at runtime — never stored alongside the code that uses them, and never persisted anywhere a human can casually read them.

The secret zero problem

Every distribution scheme faces a chicken-and-egg question: to fetch a secret, a workload must first authenticate — but with what? If the answer is “another secret”, you have merely moved the problem. This is the secret zero (or bootstrap) problem. The modern answer is workload identity: the platform vouches for the workload’s identity (a Kubernetes service account token, an AWS instance role, a GCP metadata-server identity, an OIDC token from your CI), and the secret store trusts that platform-issued identity instead of a pre-shared password. Secret zero becomes something the platform manages and rotates for you, not something you store.

The secret lifecycle

  1. Creation — generated with sufficient entropy, ideally by the secret store itself so the plaintext never exists on a developer’s machine.
  2. Storage — encrypted at rest in a dedicated system, not in Git/wikis/chat/ticketing systems.
  3. Distribution — delivered to workloads at runtime via authenticated identity (not another shared secret — the secret zero problem).
  4. Usage — read into memory at the last possible moment, never written to disk in plaintext, never logged.
  5. Rotation — replaced on a schedule or on demand, ideally automatically and without downtime.
  6. Revocation — invalidated immediately on compromise or when a lease expires.
  7. Audit — every read and write logged with who/what/when/where, and alertable on anomalies.

Static vs dynamic secrets

Static secretDynamic secret
LifetimeLong-lived until manually rotatedShort lease (minutes to hours), auto-expiring
CreationCreated once, reused by manyGenerated per request/consumer on demand
Blast radius if stolenFull, until someone notices and rotatesNear-zero — credential dies on its own
AttributionShared, hard to trace which consumer leaked itEach consumer gets a unique credential
ExamplesAn API key in a vault, a KV secretA per-session Postgres user, a temporary IAM credential

Dynamic secrets are the single biggest lever in secret management: a database credential with a 1-hour lease turns a breach from a catastrophe into a nuisance, and gives you per-consumer attribution for free.

HashiCorp Vault

Vault is the de facto standard self-hosted / cloud-agnostic secret manager. Key architectural pieces:

# Static KV (v2) — versioned key-value
vault kv put secret/myapp/db password='s3cr3t' username='app'
vault kv get -field=password secret/myapp/db
vault kv get -version=3 secret/myapp/db      # recover a prior version

# Dynamic database credentials — new user per request, auto-expiring
vault read database/creds/readonly-role
# Key                Value
# lease_id           database/creds/readonly-role/abc123
# lease_duration     1h
# username           v-token-readonly-x7Kq...   (created just now)
# password           A1a-9nB2...                (dies when the lease ends)

# Encryption-as-a-service: the app never sees the key
vault write transit/encrypt/orders plaintext=$(base64 <<< "4111-1111-1111-1111")

# Incident response: revoke everything a compromised role ever issued
vault lease revoke -prefix database/creds/readonly-role

A HashiCorp Vault mental model: Vault is a broker, not a filing cabinet. Its highest value is not storing static strings (cloud managers do that too) but minting short-lived, per-consumer credentials and cryptographic operations on demand, so long-lived secrets stop existing in your fleet at all.

Cloud secret managers

ServiceStrengthsNotes / limits
AWS Secrets ManagerNative rotation via Lambda, RDS/Redshift/DocumentDB integration, cross-account access via resource policies, JSON secrets~$0.40/secret/month + $0.05 per 10k API calls
AWS SSM Parameter StoreFree tier (standard params), SecureString via KMS, hierarchical paths, good for config and secretsNo built-in rotation; 4 KB (standard) / 8 KB (advanced) size limits; lower throughput
Azure Key VaultSecrets, keys, and certificates in one service; software or HSM-backed keys; RBAC and legacy access policies; managed HSM tierDeep Entra ID (Azure AD) integration; soft-delete + purge protection
GCP Secret ManagerSimple versioned secrets, IAM-based access, CMEK support, automatic replication policiesNative rotation is notification-only (Pub/Sub) — you write the rotation function

Cloud managers win on operational simplicity (no cluster to run, no seal/unseal ceremony), native IAM integration (workload identity is built in), and pay-as-you-go. Vault wins on dynamic secrets across many systems, multi-cloud portability (one API regardless of provider), and advanced engines (transit encryption, internal PKI, SSH CA). Many organizations run both: a cloud manager for cloud-native static secrets, Vault for dynamic credentials and cross-cutting crypto.

KMS vs secret managers — a common confusion

A Key Management Service (AWS KMS, Cloud KMS, Azure Key Vault keys) manages encryption keys and performs crypto operations; the key material never leaves the KMS. A secret manager stores and distributes arbitrary secret values (and usually uses a KMS under the hood to encrypt them at rest). Rule of thumb: use a KMS when you need to encrypt/decrypt/sign data and never expose the key; use a secret manager when a workload needs the actual secret value (a password, an API token). Vault’s transit engine is KMS-like functionality inside Vault.

Key Concepts

Kubernetes Secrets and their limitations

Native K8s Secret objects are only base64-encoded, not encrypted by default — base64 is an encoding, not a cipher, and base64 -d reverses it instantly. They are stored in etcd, in plaintext unless you enable encryption at rest with an EncryptionConfiguration (and even then, prefer a KMS provider over the static aescbc key, whose key otherwise sits on the API server disk). Beyond storage, the real exposure surfaces are:

Prefer mounting secrets as files (tmpfs volumes) over env vars, scope RBAC tightly, and use one of the patterns below to keep plaintext out of Git.

ApproachHow it worksBest forWatch out for
External Secrets Operator (ESO)Syncs secrets from Vault/AWS/Azure/GCP into K8s Secret objects via ExternalSecret CRDsGitOps: manifests reference secrets by name, values live in the external storeValues still land in etcd; still enable etcd encryption
Sealed Secrets (Bitnami)kubeseal encrypts a secret with the cluster controller’s public key; the SealedSecret CR is safe to commit to GitSimple GitOps without running an external storeController’s private key is critical — back it up; decryption is cluster-scoped
SOPS (+ age/KMS)Encrypts values inside YAML/JSON while leaving keys readable; integrates with Flux/Argo CDEncrypting whole config files in Git, git-diff-friendlyKey distribution/rotation is on you; whoever holds the age/KMS key decrypts
Vault Agent Injector / Vault Secrets OperatorInjector adds a sidecar/init container that renders secrets to a shared volume; VSO syncs via CRDsClusters already using Vault; supports dynamic secrets and auto-renewSidecar adds pod overhead; VSO is the newer, more declarative path
Secrets Store CSI DriverMounts secrets from external stores as a volume; can optionally not create a K8s Secret at allAvoiding etcd storage entirely; file-based consumptionEnv-var consumption requires syncing back to a K8s Secret (defeats the point)
# External Secrets Operator example
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: myapp-db
spec:
  refreshInterval: 1h                 # re-sync so rotations propagate
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: myapp-db                    # resulting K8s Secret name
    creationPolicy: Owner
  data:
    - secretKey: password             # key in the K8s Secret
      remoteRef:
        key: secret/myapp/db          # path in Vault
        property: password

Decision guide: No external store and just want secrets out of Git → Sealed Secrets or SOPS. Already run Vault/cloud secret manager and want K8s to mirror it → ESO or Vault/Secrets CSI. Need dynamic (leased) secrets in pods → Vault (Injector/VSO/CSI). Want to never persist to etcd → CSI driver with file mounts.

Example flow: Vault + Kubernetes auth

  1. Enable the Kubernetes auth method in Vault and configure it with the cluster’s API server address and CA (Vault uses these to validate tokens).
  2. Bind a Vault role to a specific namespace + service account, attaching a policy and a TTL.
  3. The pod (via Vault Agent, VSO, or the app directly) sends its projected service account JWT to Vault’s login endpoint.
  4. Vault validates the JWT against the Kubernetes TokenReview API — the cluster itself vouches that the token belongs to that pod’s service account.
  5. Vault returns a short-lived Vault token scoped to the role’s policy.
  6. The workload reads its static secrets, or requests dynamic DB credentials and receives a leased username/password.
  7. Vault Agent/VSO renews the token and any leases in the background; on pod termination, leases are revoked.
vault auth enable kubernetes
vault write auth/kubernetes/config \
    kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443"

vault write auth/kubernetes/role/myapp \
    bound_service_account_names=myapp \
    bound_service_account_namespaces=prod \
    policies=myapp-read \
    ttl=1h

No secret zero: the pod’s identity (its Kubernetes-signed service account token, which Kubernetes rotates automatically) is the credential. Nothing long-lived was pre-shared.

Detecting leaked secrets

Run scanners in three places: a pre-commit hook (catch it before it leaves the laptop), the CI pipeline (enforce so nothing merges), and scheduled full-history scans (catch old leaks and new detector rules). Defense in depth, because humans will slip past any single gate.

If a secret leaks — rotate first, then clean up. The order matters:

  1. Revoke/rotate the credential immediately. The leaked string is compromised the instant it’s public; a bot may already have it.
  2. Audit for malicious use. Check CloudTrail/audit logs for access from unexpected IPs or at odd times.
  3. Only then rewrite history (git filter-repo, BFG) — and understand this is hygiene, not remediation. Forks, clones, and caches still hold the old blob, which is exactly why step 1 is what actually protects you.
  4. Post-incident: figure out how it leaked and close that path (add a pre-commit hook, tighten CI, fix the logging that printed it).

Rotation and least privilege

Encryption in transit and at rest

Secrets must be encrypted both at rest (in the store’s backend, in etcd, in backups) and in transit (TLS everywhere — client to Vault, Vault to database, ESO to cloud API). A secret manager that encrypts at rest but is queried over plaintext HTTP is only half-protected. Envelope encryption (a data key encrypts the secret, a KMS root key encrypts the data key) is the standard pattern that lets you rotate the root key without re-encrypting every secret.

Best Practices

  1. Never commit secrets — and enforce it with tooling, not trust. Pre-commit gitleaks hooks + CI scanning + GitHub push protection. Humans will slip; layered tooling must catch it. A policy that relies on developers “being careful” is not a control.
  2. Centralize in a dedicated store. One system of record (Vault or a cloud secret manager) with encryption, ACLs, versioning, and audit logs — not .env files, wikis, ticketing systems, or chat. Fragmentation is the enemy of control.
  3. Prefer dynamic, short-lived credentials. A database or cloud credential with a 1-hour lease turns a breach from a catastrophe into a nuisance and gives you per-consumer attribution for free.
  4. Use workload identity, not shared bootstrap secrets. Kubernetes service accounts, cloud IAM roles, and OIDC federation solve the secret zero problem — the platform proves identity so you don’t store a bootstrap password.
  5. Automate rotation and design apps to tolerate it. Manual rotation doesn’t happen. Wire rotation into the store and make apps re-read secrets (or use the dual-credential pattern) so rotation causes no downtime.
  6. Apply least privilege per workload. One identity, one policy, one narrow path per app; never a shared “everything” bucket. Audit policies regularly for scope creep and revoke access on offboarding.
  7. Encrypt Kubernetes secrets at rest and restrict RBAC. Enable etcd encryption with a KMS provider, mount secrets as files rather than env vars, and tightly limit who can get/list Secrets cluster-wide.
  8. Keep secrets out of images, logs, and CI output. Never ENV/ARG a secret in a Dockerfile (it persists in image layers); mask secrets in CI; beware set -x, verbose error messages, and error-tracking tools that capture environment.
  9. Encrypt in transit, always. TLS from client to store and from store to backing systems. At-rest encryption is meaningless if the value crosses the network in plaintext.
  10. Audit everything and alert on anomalies. Enable Vault audit devices / CloudTrail / Key Vault diagnostics, and alert on the unusual — a CI token reading production paths at 3 a.m., a spike in reads, access from a new IP.
  11. Treat any exposed secret as compromised — rotate first. Don’t rely on “it was only public for a minute” or a history rewrite. Revoke immediately, then investigate and clean up.
  12. Version secrets and plan for rollback. KV v2 / cloud secret versions let you recover instantly from a bad rotation instead of scrambling to reconstruct the old value.
  13. Have a break-glass procedure. Document how humans access critical secrets when the automation is down — gated by MFA, requiring approvals, and reviewed after the fact. Test it before you need it.
  14. Separate secrets per environment and per tenant. Prod, staging, and dev must not share credentials; a leak in a low-trust environment must never grant prod access.
  15. Minimize the number of humans who ever see plaintext. Prefer store-generated secrets so the plaintext never exists on a laptop; use approvals and time-boxed access for the rare cases a human must read one.
  16. Give every secret a documented owner and expiry. Ownerless, never-expiring secrets accumulate into an unmanageable liability. Inventory them and retire the unused.
  17. Back up and DR-test your secret store. A secret manager is a single point of failure; run it HA, back up its storage and unseal/root keys securely, and rehearse recovery.

References