CoursesAdvanced secrets managementArchitecture & threat model

Secure introduction: solving secret-zero

AppRole, response wrapping, and platform-native workload identity.

Expert35 min · lesson 3 of 15

This lesson turns the secret-zero problem from the previous section into concrete mechanisms. There are three broad approaches, in ascending order of how well they solve the bootstrap: a shared secret you inject (AppRole with a delivered SecretID), a single-use handoff (response wrapping), and platform-native identity (Kubernetes, cloud IAM, SPIFFE). Real systems combine them — but the goal is always the same: the workload proves who it is using something it already legitimately has, and receives a short-lived, scoped token in return.

AppRole: RoleID + SecretID, and why it is only half a solution

AppRole splits the bootstrap credential into a non-secret RoleID (like a username, safe to bake into config) and a secret SecretID (like a password). The workload logs in with both and gets a token. This is better than one static token because you can rotate, constrain, and CIDR-bind the SecretID — but on its own it just moves secret-zero to "how did the SecretID get there?" The answer that makes AppRole safe is to deliver the SecretID out-of-band and single-use, which is where response wrapping comes in.

terminal — AppRole with a delivered SecretID
# platform (trusted) generates a SecretID and hands it over wrapped:
$ vault write -wrap-ttl=90s -f auth/approle/role/payments/secret-id
wrapping_token: hvs.CAESI... # single-use, 90s to live
# the workload, holding only its RoleID + the wrapping token, unwraps once:
$ VAULT_TOKEN=$WRAP vault unwrap
secret_id: 7e2f...
$ vault write auth/approle/login role_id=$ROLE_ID secret_id=7e2f...
token: hvs.CAE... ttl: 20m # short-lived, scoped by the role policy

Response wrapping: proof of single delivery

Response wrapping stores a secret in a temporary, single-use "cubbyhole" and returns a wrapping token instead of the secret. Only the holder of that token can unwrap it, exactly once, within a short TTL. Its real power is tamper-evidence: if the intended consumer tries to unwrap and finds the token already used, you know it was intercepted — the delivery itself becomes a detection mechanism. This converts "did anyone see this secret in transit?" from unknowable into an alarm.

Secure introduction with wrapping
1trusted platform
CI / orchestrator generates SecretID
2wrap
Vault returns a single-use token, not the secret
3deliver token
to the workload out-of-band
4unwrap once
reuse = interception = alarm
The secret never travels in the clear, and a stolen wrapping token that was already used betrays the attacker.

Platform identity: the real answer

The strongest solution ships no secret at all. A Kubernetes pod already holds a signed service-account JWT; an EC2 instance already has an instance role; a properly bootstrapped node has a SPIFFE SVID. The secrets manager is configured to trust that platform and verify the identity token against it — the Kubernetes auth method validates the JWT with the cluster’s TokenReview API, AWS auth verifies a signed IAM identity, and so on. The workload authenticates with something it was born with, so there is no secret-zero to steal.

terminal — Kubernetes auth: no shipped secret
# one-time: trust the cluster and bind a role to a service account + namespace
$ vault write auth/kubernetes/role/payments \
bound_service_account_names=payments \
bound_service_account_namespaces=prod \
policies=payments-read ttl=20m
# in the pod: log in with the SA token it already has mounted
$ JWT=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
$ vault write auth/kubernetes/login role=payments jwt=$JWT
token: hvs.CAE... # scoped to payments-read, 20 min, auto-renewable
Bind tightly, and keep token TTLs short
Platform auth is only as good as its binding. A Kubernetes role bound to bound_service_account_names=* or to a shared "default" service account authenticates half the cluster. Bind to a specific service account and namespace, keep the resulting token TTL in minutes with renewal, and never grant the auth-mount’s role the ability to escalate. Short TTL plus tight binding is what turns "identity" into "least privilege."