mrkeyoor.com_
Sat 08 Aug 17:41 UTC
PyPISecurityupdated 08 Aug 2026

hvac

hvac is a synchronous Python client for the HashiCorp Vault HTTP API. Its namespaced interface covers common authentication methods, KV v1 and v2, Transit, PKI, database credentials, cloud secret engines, token operations, policies, health, Raft, and other system endpoints. Responses remain Vault-shaped dictionaries, so the client saves endpoint plumbing without hiding mounts, namespaces, token leases, secret versions, or policy decisions. It uses requests underneath and supports custom sessions and adapters.

Verdict

The default Python choice when an application genuinely must speak to Vault, with broad endpoint coverage and transparent response shapes. Prefer Vault Agent or injected secrets when possible, and do not mistake the client for a token lifecycle manager.

API stability4/5The current 2.x interface consistently groups operations under client.auth, client.secrets, and client.sys, with explicit v1 and v2 KV namespaces and raw Vault response dictionaries. That structure is predictable, but it mirrors an evolving server API: renamed or deprecated Vault endpoints, mount-version differences, and authentication changes can still affect callers even when hvac's Python method names remain available.
Docs5/5The Read the Docs site includes getting started material, advanced TLS and adapter guidance, source-generated references, a changelog, and separate worked pages for authentication methods, secret engines, and system backends. It documents non-obvious details such as KV response nesting, custom mount_point values, retrying 412 responses, and setting raise_on_status=False, though users still need the Vault server documentation for policy and operational semantics.
Maintenance3/5Version 2.4.0 was uploaded on October 30, 2025 and the repository's latest push was January 6, 2026. The project is not archived and declares an active Vault compatibility policy, but GitHub reports 182 open issues and pull requests and there has been no repository push for roughly seven months as of this guide. That is adequate for a mature wrapper, but it merits pinning and integration tests against the exact Vault release you run.
Ecosystem4/5PyPIStats records 6,642,753 downloads in the latest week, and hvac covers far more than KV: cloud authentication, AppRole, Kubernetes, Transit, PKI, database credentials, identity, Raft, policies, and audit configuration are represented. Its requests foundation makes network customization familiar. The package remains Python-specific, and Vault Agent plus HashiCorp's CLI often provide better cross-language operational boundaries.

Use it if

  • A Python service needs to authenticate to Vault and read versioned KV secrets without maintaining raw endpoint paths
  • You use several Vault features such as Transit encryption, dynamic database credentials, PKI, or token renewal
  • Your environment needs Enterprise namespaces, private CA verification, client certificates, or a custom requests session
  • Operations tooling needs typed method names for Vault system and secret-engine APIs while retaining access to raw responses
Skip it if

Setup reality

pip install hvac installs the client and requests; Python 3.8 through 3.x below 4.0 is accepted. The optional parser extra adds pyhcl only for methods that return HCL. A real deployment needs VAULT_ADDR, an authentication method, a least-privilege policy, and TLS trust. The default local-looking URL is not a production configuration. Pass verify with a CA bundle for private PKI instead of setting it to false; mutual TLS additionally needs the client certificate and key. Tokens should come from workload identity, AppRole, Kubernetes, Vault Agent, or another managed login, not a source file. KV is the first common trap: dev Vault mounts secret/ as v2, a normal server may mount nothing, and v1 versus v2 changes both the method and where data appears in the response. Custom mounts require mount_point, and Vault Enterprise users must set namespace. Authentication methods mounted under custom paths need the matching mount_point too. hvac sets the client token after most login calls when use_token remains true, but your process must still renew renewable tokens and leases or log in again. Calls are synchronous. Configure requests timeouts and a deliberate retry policy; the docs call out Vault's 412 eventual-consistency response and advise raise_on_status=False so hvac can raise its own exceptions. Do not broadly retry non-idempotent POST requests. High availability, sealed nodes, standby responses, quotas, permissions, and secret-engine configuration remain Vault operational concerns. Vault Agent is often simpler for applications because it can own authentication and renewal outside the process.

Patterns

Connect with a token and private CAconnect-with-token

import os
import hvac

client = hvac.Client(
    url=os.environ["VAULT_ADDR"],
    token=os.environ["VAULT_TOKEN"],
    verify=os.environ["VAULT_CACERT"],
    namespace=os.getenv("VAULT_NAMESPACE"),
    timeout=10,
)
if not client.is_authenticated():
    raise RuntimeError("Vault authentication failed")

verify should point to the issuing CA bundle. Setting verify=False hides certificate mistakes and exposes secrets to interception.

Authenticate with AppRolelogin-with-approle

response = client.auth.approle.login(
    role_id=os.environ["VAULT_ROLE_ID"],
    secret_id=os.environ["VAULT_SECRET_ID"],
    mount_point="approle",
)
lease_seconds = response["auth"]["lease_duration"]

login uses the returned token on the client by default. Treat Secret IDs as credentials and use the actual auth mount path.

Authenticate from a Kubernetes podlogin-from-kubernetes

from pathlib import Path

jwt = Path(
    "/var/run/secrets/kubernetes.io/serviceaccount/token"
).read_text().strip()
client.auth.kubernetes.login(
    role=os.environ["VAULT_K8S_ROLE"],
    jwt=jwt,
    mount_point="kubernetes",
)

The service account, audience, Vault role, and Kubernetes auth configuration must agree. The token file is rotated by Kubernetes, so re-read it when reauthenticating.

Read the latest KV v2 secretread-kv2-secret

response = client.secrets.kv.v2.read_secret_version(
    path="apps/payments",
    mount_point="secret",
)
secret = response["data"]["data"]
version = response["data"]["metadata"]["version"]

KV v2 nests values under data.data and metadata beside them. KV v1 has a different method and response shape.

Update a KV v2 secret with check-and-setwrite-kv2-with-cas

current = client.secrets.kv.v2.read_secret_version(
    path="apps/payments", mount_point="secret"
)
client.secrets.kv.v2.create_or_update_secret(
    path="apps/payments",
    secret={"api_key": new_key},
    cas=current["data"]["metadata"]["version"],
    mount_point="secret",
)

CAS prevents overwriting a concurrent update. cas=0 succeeds only when the path has never been written.

List child keys under a KV v2 pathlist-kv2-paths

response = client.secrets.kv.v2.list_secrets(
    path="apps",
    mount_point="secret",
)
for key in response["data"]["keys"]:
    print(key)

Vault policies need list capability on the metadata path for KV v2. A trailing slash in a returned key denotes another folder-like level.

Read from a KV v1 mountread-kv1-secret

response = client.secrets.kv.v1.read_secret(
    path="legacy/app",
    mount_point="kv-v1",
)
password = response["data"]["password"]

Do not use this shape against KV v2. Confirm the mount's options with Vault rather than guessing from its name.

Encrypt and decrypt with Transittransit-encrypt-decrypt

import base64

plaintext = base64.b64encode(b"customer-42").decode()
enc = client.secrets.transit.encrypt_data(
    name="app-key", plaintext=plaintext
)
ciphertext = enc["data"]["ciphertext"]
dec = client.secrets.transit.decrypt_data(
    name="app-key", ciphertext=ciphertext
)
value = base64.b64decode(dec["data"]["plaintext"])

Transit plaintext and decrypted output are base64 strings. Vault retains the encryption key; it does not store your plaintext.

Request dynamic database credentialsgenerate-database-credentials

response = client.secrets.database.generate_credentials(
    name="readonly",
    mount_point="database",
)
username = response["data"]["username"]
password = response["data"]["password"]
lease_id = response["lease_id"]
lease_seconds = response["lease_duration"]

These credentials expire with the lease. The application must reconnect or renew before expiry and must never log the returned password.

Inspect and renew the current tokenrenew-client-token

lookup = client.auth.token.lookup_self()
if lookup["data"]["renewable"]:
    renewed = client.auth.token.renew_self(increment="1h")
    next_ttl = renewed["auth"]["lease_duration"]

Renewal can be denied by token policy or maximum TTL. Schedule from the returned TTL and handle reauthentication when renewal stops.

Retry transient and standby consistency responsesconfigure-retries

from requests import Session
from requests.adapters import HTTPAdapter
from urllib3.util import Retry

retry = Retry(
    total=3, backoff_factor=0.2,
    status_forcelist=[412, 500, 502, 503],
    raise_on_status=False,
)
session = Session()
session.mount("https://", HTTPAdapter(max_retries=retry))
client = hvac.Client(url=vault_addr, session=session, verify=ca_file)

The hvac docs require raise_on_status=False to preserve hvac exceptions. urllib3 does not retry POST by default, which protects many non-idempotent operations.

Handle permission and missing-secret errorshandle-vault-errors

import hvac

try:
    response = client.secrets.kv.v2.read_secret_version(
        path="apps/payments"
    )
except hvac.exceptions.Forbidden:
    raise RuntimeError("Vault policy denied this path")
except hvac.exceptions.InvalidPath:
    response = None

A missing path and a forbidden path are different operational states. Avoid exposing raw Vault error text in user-facing responses.

Alternatives

PackageRegistryPick it when
async-hvacPyPIAn asyncio service accepts a smaller third-party client in exchange for nonblocking Vault calls
vault-cliPyPIHumans and shell automation need a Python-installed command line rather than an application SDK
requestsPyPIA tiny integration calls one or two stable Vault endpoints and can own paths, errors, and authentication itself