HashiCorp Vault Python SDK
A static database password is a credential with an unbounded blast radius — one leak compromises the system until someone notices. HashiCorp Vault can mint a credential that lives for an hour, scoped to a single role, then revokes it automatically. This page authenticates to Vault with hvac and consumes dynamic, short-lived secrets in Python.
Vault is the cloud-agnostic option in the enterprise secrets section, and its dynamic credentials are the strongest form of the rotation discipline described in automated rotation patterns.
Secure implementation
# secrets/vault.py
import hvac
from pydantic import SecretStr
def vault_db_credentials(role: str, secret_id: SecretStr) -> dict[str, SecretStr]:
client = hvac.Client(url="https://vault.internal:8200")
client.auth.approle.login(
role_id="app-role-id", # non-secret, shipped with the app
secret_id=secret_id.get_secret_value(), # delivered at runtime, never in git
)
if not client.is_authenticated():
raise SystemExit("Vault authentication failed")
lease = client.secrets.database.generate_credentials(name=role)
data = lease["data"]
return { # valid only for the lease TTL
"username": SecretStr(data["username"]),
"password": SecretStr(data["password"]),
}
The role_id is not a secret and can ship with the application; the secret_id arrives at runtime. The returned credential is dynamic — Vault revokes it when the lease ends.
Configuration reference
| Element | Type | Notes | Security implication |
|---|---|---|---|
role_id |
string | Non-secret identifier | Safe to bake into config |
secret_id |
SecretStr |
Runtime-delivered | Never commit; short-lived |
| dynamic lease | TTL-scoped | Auto-revoked | Minimal blast radius |
| KV v2 read | versioned | read_secret_version |
Pin versions for audit |
| token TTL | seconds | Renew before expiry | Avoid mid-request failures |
Deployment parity: local to production
- Local dev — developers use a dev-mode Vault or a short-lived token against a non-prod mount.
- CI — the pipeline authenticates with a scoped AppRole and tears down its lease at job end.
- Staging/Production — the orchestrator injects the
secret_id; the app obtains dynamic database credentials per process and renews leases.
Security boundaries & guardrails
- Deliver
secret_idat runtime; never store it in the image or repo. - Prefer dynamic secrets engines over static KV for databases and cloud credentials.
- Wrap all returned credentials in
SecretStr. - Renew or re-fetch before the lease expires; never assume a credential is still valid.
- Scope each AppRole to the minimum policies it needs.
Troubleshooting
InvalidRequest: failed to validate SecretID— thesecret_idexpired or was already used; request a fresh one. See Vault AppRole Auth in Python.- Credential rejected by the database — the lease expired; re-fetch and renew earlier next time.
Forbiddenon a path — the AppRole policy does not grant that mount; widen the policy minimally.- Token not authenticated — clock skew or wrong
url; verify TLS and the Vault address.
Frequently asked questions
Which Vault auth method should a Python service use?
AppRole is the standard for machine authentication. The role_id is non-secret and shipped with the app; the secret_id is delivered at runtime by a trusted orchestrator, keeping a long-lived token out of the image.
What are Vault dynamic secrets?
Vault generates a credential on demand — for example a database username and password that exists only for the lease TTL, then is automatically revoked. Nothing static is stored, so a leak expires on its own.
How do I keep a Vault lease from expiring mid-request?
Renew the lease before it expires using sys.renew_lease, or re-fetch when the remaining TTL drops below a threshold. Treat the credential as ephemeral and always be ready to obtain a fresh one.
Conclusion
The invariant: authenticate with AppRole, prefer dynamic short-lived credentials, wrap them in SecretStr, and renew before expiry. A leaked Vault credential should expire on its own within the hour.