AppRole and response wrapping
Machines without a browser, delivered once.
In short. Not every machine lives in Kubernetes. CI runners, VMs, and legacy app servers still need a Vault login path that does not involve a browser.
Not every machine lives in Kubernetes. CI runners, VMs, and legacy app servers still need a Vault login path that does not involve a browser. AppRole is Vault's purpose-built machine auth: a role identifier that is not secret, plus a secret ID that is. Response wrapping delivers that secret ID once, through a single-use token, so it never sits long in a ticket or chat.
AppRole done wrong becomes "static password with extra steps." AppRole done right keeps RoleID in config, injects SecretID at provision time via wrap, sets tight TTLs and use limits, and binds CIDR or other constraints when the platform allows.
Enable AppRole and define a role
Enable the auth method, write a role with token_policies, token_ttl, secret_id_ttl, and token_num_uses as appropriate. Prefer short SecretID lifetimes for CI. For long-running hosts, pair with periodic tokens carefully — or better, prefer platform identity (cloud IAM, Kubernetes) when available.
vault auth enable approlevault write auth/approle/role/ci-deploy \token_policies="ci-deploy" \token_ttl=15m \token_max_ttl=30m \secret_id_ttl=10m \secret_id_num_uses=1vault read auth/approle/role/ci-deploy/role-id
Key Value--- -----role_id a1b2c3d4-role-id-example
Response wrapping for SecretID delivery
Generate a SecretID with -wrap-ttl=2m. Vault returns a wrapping token instead of the SecretID. Only the intended runner should unwrap it (vault unwrap). If unwrap fails because the wrap was already used, treat it as interception — rotate and investigate. This is the practical answer to secret-zero for AppRole bootstraps.
vault write -wrap-ttl=2m -force auth/approle/role/ci-deploy/secret-id# deliver wrapping_token to the runner out of band, then on the runner:vault unwrap <wrapping_token>vault write auth/approle/login role_id=$ROLE_ID secret_id=$SECRET_ID
Key Valuewrap_info ...# after unwrap + logintoken hvs.CAESIJcitoken_policies [ci-deploy]
Going deeper
Bind CIDRs on SecretID or tokens when runners have stable egress. It will not stop a compromised runner inside the range, but it blocks stolen SecretIDs used from a laptop on another network. Combine with secret_id_num_uses and short TTLs so replay windows stay tiny.
CI systems should mint wrapped SecretIDs in a privileged bootstrap job, then unwrap only on the job that needs Vault — never echo the SecretID into logs. Prefer OIDC/JWT auth to Vault when your Vault version and platform support it; AppRole remains the bridge for legacy fleets.
Rotate RoleIDs rarely and SecretID generation often. If a RoleID leaks with a long-lived unlimited SecretID, you have reinvented a password. Audit auth/approle/login failures for spraying, and alert on unwrap failures that suggest interception.
Try this
In a lab, mint a wrapped SecretID, unwrap it, login, then try unwrap again and confirm failure.
WRAP=$(vault write -wrap-ttl=2m -force -field=wrapping_token auth/approle/role/ci-deploy/secret-id)vault unwrap $WRAPvault unwrap $WRAP || true
secret_id ...Error unwrapping: wrapping token is not valid or has already been used# SUCCESS — single delivery proven
Takeaway
AppRole plus response wrapping gives non-Kubernetes machines a least-privilege login without parking forever-SecretIDs in git. Prefer platform-native identity when you can; use AppRole when you must.
Next: Vault Agent — keep applications from speaking Vault's API themselves.