Skip to content

feat: fetch GPG passphrases from HashiCorp Vault - #184

Open
anfimovdm wants to merge 1 commit into
masterfrom
feat/vault-passphrases
Open

feat: fetch GPG passphrases from HashiCorp Vault#184
anfimovdm wants to merge 1 commit into
masterfrom
feat/vault-passphrases

Conversation

@anfimovdm

@anfimovdm anfimovdm commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Adds a HashiCorp Vault KV v2 provider as an alternative to Bitwarden for resolving GPG key passphrases at startup, so unattended restarts don't fall back to an interactive prompt.

Resolves: AlmaLinux/build-system#548

Implements the sign-node half of that issue. The albs-sign-file half is AlmaLinux/albs-sign-file#71.

What changed

  • sign_node/utils/vault.py — KV v2 fetcher. Static token (inline or from a file), AppRole, or VAULT_ADDR/VAULT_TOKEN from the environment for hosts already running a Vault agent. hvac is imported lazily, so it's only needed when the provider is enabled.
  • sign_node/utils/secrets.py — provider resolver. Passphrase resolution for both Bitwarden and Vault now goes through here.
  • sign_node/config.py — twelve vault_* keys plus cerberus schema entries.
  • almalinux_sign_node.py — calls the resolver instead of Bitwarden directly.
  • README, node-config/sign_node.yml, hvac==2.4.0 in requirements.txt.

Secret layout

One secret per keyid at <vault_mount>/<vault_path_prefix>/<keyid>, passphrase in the passphrase field:

vault kv put secret/albs/sign-keys/7C3955C2A345DA89 passphrase='...'

The field name is configurable via vault_passphrase_field for operators with an existing layout.

Two design decisions worth reviewing

  1. Only one provider may be enabled at a time. Enabling Bitwarden and Vault together is a configuration error, not a precedence chain — a signing key silently resolved from the wrong vault is a bad failure mode. If a migration period needs both configured at once, this rule has to soften.
  2. The Vault field is named passphrase, not password. Bitwarden's wrapper hardcodes the item's password field; Vault has no such constraint, so the field name describes the value. vault_passphrase_field covers anyone who wants literal parity.

Out of scope

Storing the GPG private keys themselves in Vault, Vault Transit as a signing backend, and the albs-deploy Ansible wiring — all called out in the issue as follow-ups.

Testing

Run in the CI Docker target (sign-node-tests), since building pycurl locally needs system libs:

  • 59 passed — 23 new (test_vault.py, test_secrets.py), against a stubbed hvac client.
  • Verified against real SignNodeConfig objects, not only mocks: defaults load correctly, and enabling both providers raises Only one secret provider may be enabled at a time, but these are enabled: bitwarden, vault.
  • New modules are black/isort-clean and bandit-clean. Remaining pylint notes are the deliberate lazy imports (C0415) and the local count in a function with twelve config options (R0914). Pre-existing black violations in config.py and almalinux_sign_node.py were left alone — both were already non-clean at master.

Adds a HashiCorp Vault KV v2 provider as an alternative to Bitwarden
for resolving GPG key passphrases at startup, so unattended restarts
do not fall back to an interactive prompt.

Passphrase resolution now goes through sign_node/utils/secrets.py,
which picks the enabled provider and refuses to run with more than
one enabled: signing keys should have a single unambiguous source of
truth, so that is a configuration error rather than a fallback chain.

Each keyid maps to a secret at <mount>/<path_prefix>/<keyid> holding
the passphrase in a configurable field ('passphrase' by default).
Supports a static token (inline or from a file), AppRole, and the
VAULT_ADDR/VAULT_TOKEN environment variables for hosts already
running a Vault agent. hvac is imported lazily so it is only needed
when the provider is enabled.

Passphrases are read once at startup, so a short-lived token is
enough and no Vault session is renewed while the node runs.

Resolves: AlmaLinux/build-system#548
@anfimovdm
anfimovdm force-pushed the feat/vault-passphrases branch from fe6fba1 to ff96348 Compare August 25, 2026 15:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

albs-sign-node, albs-sign-file: support fetching GPG key passphrases from HashiCorp Vault

1 participant