System designCore10 min

Secrets management

Database passwords, API keys and signing keys end up in code, config files, container images and chat messages, and one leaked key can open everything behind it. Secrets management keeps them in one guarded place, hands them out by identity, makes them short-lived where possible, rotates the rest, and plans for the day one leaks anyway.

1 · The problem

Secrets spread everywhere, and one leaked key opens the door

An online travel agency runs on secrets: passwords for its booking database, API keys for 40 airlines and a payment provider, the key that signs customers' login tokens, TLS certificates, credentials for a partner's file server. Such secrets usually end up hardcoded in source code in plain text, scattered through configuration files and configuration tools, and often shared by several services, so nobody can tell which one leaked.

Leaks are common and expensive. In 2016 intruders reached Uber's cloud storage using an access key that an engineer had posted on a code-sharing website, and downloaded files containing more than 25 million names and email addresses and 22 million names and phone numbers. One key, one repository, millions of people.

Measure

Value

Counted over

Public repositories with a leaked secret

4.6%

Public repositories, 2024

Private repositories with a leaked secret

35%

Repositories of companies scanning their own code

Secrets leaked in 2022 that still worked

70%

Secrets found in 2022, rechecked later

In 2024, 23.8 million new secrets were found in public commits across 69.6 million public repositories; private repositories, wrongly assumed safe, were worse; 70% of the secrets leaked in 2022 still worked. Each figure counts a different population, so compare them loosely.

The last bar is the real lesson. Leaks happen; what turns a leak into a breach is a secret that still works. Secrets management is as much about making leaked secrets useless quickly as about preventing leaks.

2 · One store

Keep secrets in one store, and hand them out by identity

The first step is a secrets manager: one service that stores secrets encrypted, decides who may read each one, and logs every read. HashiCorp Vault, AWS Secrets Manager, Google Secret Manager and Azure Key Vault are common choices. Such a system needs a few properties:

Property

At the travel agency

Fine-grained access, least privilege

The booking service may read the booking database password, not the payment key

No routine human access

Engineers deploy code; they don't need to see production passwords

Audit logs of every access

Who read the payment key, from which service, when

Encryption, with keys in a KMS

The store's own data is envelope-encrypted (Encryption in transit & at rest)

High availability

If the store is down, services can't start

Services fetch secrets at startup or when needed, keep them in memory, and never write them to disk, logs or images. For containers: never bake a secret into an image with Docker's ENV or ARG instructions, which can leak with the image's definition; inject secrets at runtime instead, ideally by fetching them directly from the store.

3 · The first secret

The first secret should come from the platform, not a file

A service that fetches its secrets must first prove who it is to the secrets manager. If that proof is itself a password in a config file, the problem has only moved: the first secret is now the one to steal.

Cloud platforms solve it by giving workloads an identity they never have to store. A virtual machine has an instance profile; a Kubernetes pod can be bound to a cloud role. With Amazon EKS Pod Identity, for example, a service account is associated with an IAM role, and an agent on each node hands the pod temporary credentials, instead of credentials being created and distributed to the containers:

Booking service podno stored credentialsPlatform identitytemporary credentialsSecrets managerpolicy: booking-svcBooking databasewho am I?signed requestDB credentialsconnect

The pod proves its identity with short-lived credentials from the platform, and the secrets manager's policy decides what that identity may read. Nothing long-lived sits in the image or the configuration.

Even better is not needing a secret at all. If the database accepts the platform's identity directly, or services authenticate to each other with workload certificates (Network segmentation & zero trust), there is no password to leak in the first place.

4 · Dynamic secrets

Dynamic secrets are created per use and expire on their own

A database password shared by every instance of every service for years is the worst kind of secret: widely copied, rarely changed, and impossible to trace. Dynamic secrets turn that around. Vault's database secrets engine, for example, creates a new database user with its own password when a service asks, and attaches a lease with a time to live:

Each instance gets its own short-lived database user. A stolen password stops working when its lease expires, and suspicious queries trace back to one instance by its user name.

Three things change. A stolen credential works only until its lease ends, and after a reboot, dynamically created credentials have simply expired. Every instance has unique credentials, so a strange query in the database log names the instance that sent it. And revoking access means revoking leases, not hunting down every copy of a shared password. Use dynamic secrets wherever possible.

5 · Rotation

Secrets that can't be dynamic must be rotated without downtime

Many secrets can't be created per use: an airline's API key, the payment provider's key, the token-signing key. They should be rotated regularly, so a stolen copy works only for a while, and should have an expiration date that forces rotation if nobody does it. One exception: people's own passwords should not be rotated on a schedule, only when there is evidence of compromise.

The hard part is rotating without breaking running services, which may still hold the old secret. AWS Secrets Manager offers two strategies for database credentials. A single user whose password is changed is simplest, but a request made during the change can briefly fail. Alternating users keeps two users and updates the one not currently in use:

  1. Two users

    booking_a and booking_b both have the same permissions; the secret currently points to booking_a.

  2. Rotate the idle one

    Set a new password on booking_b, and point the secret at booking_b.

  3. Both work for a while

    Services that still hold booking_a's password keep working; new fetches get booking_b.

  4. Next rotation

    Change booking_a's password, and point the secret back to it.

Alternating users suit applications that need high availability: a service fetching the secret mid-rotation always gets a valid pair. The same pattern works for keys: an API that accepts both the old and the new key during a changeover, or a token signer that publishes both public keys until old tokens expire (Authentication & authorization).

6 · Leaks

Catch leaks before the push, and revoke first when one gets out

Detection belongs as early as possible. Scan for secrets at the developer's machine, in the editor or a pre-commit hook, before anything is committed. Code hosts add a second net: GitHub's push protection blocks pushes that contain recognized secrets before they reach the repository, rather than alerting afterwards, and secret scanning alerts on secrets already in the history.

Developer's machinepre-commit scanPushpush protection blocks itRepository historyscanning alertsLeakedrevoke firstmissedmissedcopied elsewhere

Each net catches a secret earlier and more cheaply than the next. Past the push, assume it is public.

Some leaks are built into tools. Kubernetes Secrets are, by default, stored unencrypted in the cluster's data store, readable by anyone with API access to them, and by anyone allowed to create a pod in that namespace, since the pod can mount any Secret there. In Kubernetes, enable encryption at rest for Secrets and restrict access with role-based access control.

When a secret does leak, the order is clear, and revocation comes first:

  • Revoke the exposed secret immediately, so it stops working.
  • Rotate: create and deploy a replacement, ideally automatically.
  • Investigate the audit logs: what was the secret used for, and by whom, since it leaked.
  • Remove it from the code and history, knowing that copies may already exist elsewhere.

Deleting the commit alone achieves little: a pushed secret must be treated as public from the moment of the push.

7 · In practice

Make every secret short-lived, traceable and easy to replace

For the travel agency, the checklist:

  • One secrets manager, with least-privilege policies per service and audit logs of every read.
  • No secrets in code, images, logs or chat; inject at runtime from the store.
  • Platform identity for the first secret, never a password in a config file.
  • Dynamic, per-instance credentials for databases where possible.
  • Automated rotation without downtime for the rest, with expiry dates as a backstop.
  • Scan before commit and block on push.
  • Encrypt Kubernetes Secrets at rest and restrict who can read them.
  • A rehearsed leak runbook: revoke, rotate, investigate, then clean up.

Secret at the travel agency

How it is handled

How a service proves who it is

Platform identity, never a password in a config file

Booking database password

A dynamic user per instance, on a one-hour lease

API keys for 40 airlines

Rotated automatically, with an expiry date as a backstop

The token-signing key

Rotated with old and new accepted during the changeover

Check yourself

5 questions

1. An engineer pushes an API key to a repository and deletes the commit ten minutes later. What should happen next?
2. Why shouldn't a service authenticate to the secrets manager with a password stored in its config file?
3. Why are dynamic database credentials safer than one shared password?
4. Why does alternating-users rotation avoid failures that single-user rotation can cause?
5. Who can read a Kubernetes Secret in a namespace by default?

Takeaways

Remember this

  • Secrets leak from code, images, config and chat; what turns a leak into a breach is a secret that still works.
  • Keep secrets in one manager with least-privilege access and audit logs, and inject them at runtime.
  • Let the platform prove each service's identity with temporary credentials, never a stored password.
  • Prefer dynamic, per-instance secrets with leases; rotate the rest automatically without downtime.
  • Scan before commit, block on push, and when a secret leaks, revoke it first.