Skip to main content

HashiCorp Vault secret source for Hermes Agent (bulk KV v2).

Project description

hermes-vault-secret-source

CI

A HashiCorp Vault secret source for Hermes Agent, packaged as a standalone pip plugin.

At Hermes startup it reads one Vault KV v2 secret path and injects every key found there as an environment variable, the same way the bundled Bitwarden Secrets Manager source injects a whole project. This is a bulk source, so explicit per-variable bindings from mapped sources (like 1Password) take precedence over it.

Install

pip install hermes-vault-secret-source

This pulls in hvac, the official Python client for Vault. Install it into the same environment as hermes-agent.

To track the latest unreleased code, install from the repository instead:

pip install "hermes-vault-secret-source @ git+https://github.com/cryptoyasenka/hermes-vault-secret-source"

Hermes discovers the plugin automatically through the hermes_agent.plugins entry point, but plugins are opt-in. Enable it once:

hermes plugins enable hermes-hashicorp-vault

Configure

Add a vault block under secrets in your Hermes config:

secrets:
  vault:
    enabled: true
    # addr is read from $VAULT_ADDR by default; set `addr` to hard-code it.
    path: apps/my-agent          # KV v2 path whose keys become env vars
    mount_point: secret          # KV v2 mount (default: secret)
    auth_method: token           # "token" (default) or "approle"
    cache_ttl_seconds: 300       # 0 disables caching

Credentials are never written into config; they come from environment variables:

Auth method Env vars used
token VAULT_ADDR, VAULT_TOKEN
approle VAULT_ADDR, VAULT_ROLE_ID, VAULT_SECRET_ID

The env-var names are overridable per field (addr_env, token_env, role_id_env, secret_id_env) if your deployment uses different names.

All config keys

Key Default Meaning
enabled false Master switch
addr "" Vault URL; overrides addr_env when set
addr_env VAULT_ADDR Env var holding the Vault URL
path "" KV v2 path to read (required)
mount_point secret KV v2 secrets-engine mount
auth_method token token or approle
token_env VAULT_TOKEN Env var with the token (token auth)
role_id_env VAULT_ROLE_ID Env var with the role_id (approle)
secret_id_env VAULT_SECRET_ID Env var with the secret_id (approle)
namespace "" Vault Enterprise namespace
verify true TLS verify: true/false or a CA-bundle path
cache_ttl_seconds 300 Disk+memory cache TTL; 0 disables caching
request_timeout_seconds 30 Per-request Vault HTTP timeout
override_existing true Vault values overwrite existing env values

How it behaves

  • Never raises. A misconfigured or unreachable Vault produces a clean FetchResult with an ErrorKind (not_configured, auth_failed, ref_invalid, network, timeout, ...), never a startup crash.
  • Protects its own credentials. The auth env vars (VAULT_ADDR, VAULT_TOKEN, VAULT_ROLE_ID, VAULT_SECRET_ID) are marked protected, so a secret stored in Vault cannot clobber the credential used to reach Vault.
  • Caches values, never tokens. The optional on-disk cache stores only the resolved secret values; the token/secret_id are SHA-256 fingerprinted before they touch the cache key.
  • Skips unsafe keys and values. A KV key that is not a valid environment variable name, or one that could hijack how the process resolves binaries, libraries, or interpreter startup (PATH, LD_*, DYLD_*, PYTHONPATH, ...), is skipped with a warning rather than injected. Only scalar values (strings and numbers) are injected; a structured value (list or object) is skipped with a warning instead of being stringified into an unusable value.

Threat model

This plugin runs on the Hermes startup path with access to Vault credentials, so its security posture is deliberately narrow. Every protection below is already implemented; each maps to a concrete mechanism in the source.

Startup cannot be crashed by this source

fetch() never raises. Every configuration problem returns a FetchResult with a machine-readable ErrorKind (not_configured, auth_failed, ref_invalid, network, timeout, binary_missing, internal), and the backend in _client.py only ever raises RuntimeError, which fetch() catches and classifies. A misconfigured, unreachable, or unauthenticated Vault degrades to a clean skip, never a traceback on the non-interactive startup path. Any other unexpected error (for example a corrupt cache file surfaced by the shared cache layer) is caught as well and reported as internal, so nothing can escape fetch().

A Vault secret cannot hijack the credential used to reach Vault

protected_env_vars() marks the auth env vars (VAULT_ADDR, VAULT_TOKEN, VAULT_ROLE_ID, VAULT_SECRET_ID, or their configured overrides) as protected. The orchestrator will not overwrite a protected variable, so a key that happens to be named VAULT_TOKEN inside the Vault payload cannot clobber the token the process is already using to authenticate. Names that are not valid env-var identifiers are dropped from the protected set, so a misconfigured section cannot silently weaken it.

Tokens never reach the on-disk cache in the clear

The optional two-layer cache stores only resolved secret values. The Vault token (or the AppRole role_id and secret_id) is SHA-256 fingerprinted by _fingerprint() before it becomes part of the cache key, so neither the in-memory key nor the vault_cache.json file on disk contains the raw credential. Caching is opt-out: cache_ttl_seconds: 0 disables it entirely.

Only environment-safe keys are injected

Each KV v2 key is validated with the host's is_valid_env_name() before it is contributed. A key that is not a valid environment-variable name is skipped with a warning instead of being injected, so a stray Vault key cannot produce a malformed process environment. Keys that are valid identifiers but could hijack how the process resolves binaries, shared libraries, or interpreter startup (PATH, LD_*, DYLD_*, PYTHONPATH, BASH_ENV, ...) are refused as well, even when override_existing is on. Only scalar values are injected; a structured value is skipped rather than stringified.

TLS verification is on by default

verify defaults to true. It is disabled only when the operator explicitly sets a false-y value, and a string value is treated as a CA-bundle path. TLS therefore fails closed rather than open.

Development

Requires a hermes-agent source checkout (for the agent.* packages and the conformance kit at tests/secret_sources/conformance.py).

# host = hermes-agent checkout, plugin = this repo. Put the host on PYTHONPATH so
# `import tests` resolves to the host conformance package. This repo keeps its own
# tests in `checks/` (not `tests/`) precisely so it cannot shadow that package.
PYTHONPATH="/path/to/hermes-agent:$(pwd)" pytest

checks/test_conformance.py runs the host SecretSourceConformance contract; checks/test_vault_source.py covers behavior with hvac mocked (no network, no Vault binary needed).

To run against a real dev server:

vault server -dev            # prints VAULT_ADDR + a root token
export VAULT_ADDR=http://127.0.0.1:8200
export VAULT_TOKEN=<root-token-from-output>
vault kv put secret/apps/my-agent API_KEY=abc DB_URL=postgres://x

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hermes_vault_secret_source-0.1.2.tar.gz (15.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hermes_vault_secret_source-0.1.2-py3-none-any.whl (13.5 kB view details)

Uploaded Python 3

File details

Details for the file hermes_vault_secret_source-0.1.2.tar.gz.

File metadata

File hashes

Hashes for hermes_vault_secret_source-0.1.2.tar.gz
Algorithm Hash digest
SHA256 b1618c53f9e37d25677c9acffc33308f46dc54bf65ac5550a58504b2e411a877
MD5 567cb2aca9ec5195e048ed7d7ae3ec4b
BLAKE2b-256 90151c7e933fe008b56d6f9a6676f7a341d04bee5fd06f2fb75235b06b933517

See more details on using hashes here.

Provenance

The following attestation bundles were made for hermes_vault_secret_source-0.1.2.tar.gz:

Publisher: release.yml on cryptoyasenka/hermes-vault-secret-source

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file hermes_vault_secret_source-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for hermes_vault_secret_source-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 f79d88eace01ca7bb4e8e583000aeb8e648bb7e76a8fdc1db33c861033fc91cf
MD5 a007a446f866c790964337be69ee80f6
BLAKE2b-256 819249f68da84a46a41e0a4b289985c02aace5df4184696adcd79f7983f89e5f

See more details on using hashes here.

Provenance

The following attestation bundles were made for hermes_vault_secret_source-0.1.2-py3-none-any.whl:

Publisher: release.yml on cryptoyasenka/hermes-vault-secret-source

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page