Secure introduction: solving secret-zero
AppRole, response wrapping, and platform-native workload identity.
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.
# platform (trusted) generates a SecretID and hands it over wrapped:$ vault write -wrap-ttl=90s -f auth/approle/role/payments/secret-idwrapping_token: hvs.CAESI... # single-use, 90s to live# the workload, holding only its RoleID + the wrapping token, unwraps once:$ VAULT_TOKEN=$WRAP vault unwrapsecret_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.
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.
# 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=$JWTtoken: hvs.CAE... # scoped to payments-read, 20 min, auto-renewable