feat(hashivault): enhance SignerVerifier to accept Vault options in addition to VAULT envs#103
feat(hashivault): enhance SignerVerifier to accept Vault options in addition to VAULT envs#103dmmordvi wants to merge 11 commits into
Conversation
…ddition to VAULT envs Signed-off-by: Dmitry Mordvinov <dmitry.mordvinov@flant.com>
Add table-driven tests for newAuthSettings/newAuthenticator across the env and opts sources: method priority (AppRole > OIDC > static JWT > token), transport-only opts preserving env auth, opts credentials ignoring env, incomplete opts returning an error without env/file token fallback, and auth-path resolution (opts over WERF_VAULT_AUTH_PATH, ar/jwt defaults). Env is isolated per test by unsetting Vault variables, since the getters chain WERF_* over VAULT_* via os.LookupEnv where an empty-but-present value would mask the fallback. Signed-off-by: Dmitry Mordvinov <dmitry.mordvinov@flant.com>
Add Ginkgo specs proving KeyOpts.SignerVerifierOpts.VaultOpts is threaded to the Vault loader for hashivault:// refs (incomplete opts surface the "incomplete Vault auth options" error before any network call), that non-Vault refs take a different path, and that an empty KeyRef is rejected up front. Signed-off-by: Dmitry Mordvinov <dmitry.mordvinov@flant.com>
Add a README section covering KeyOpts.SignerVerifierOpts.VaultOpts: the field-to-env mapping, the transport/auth separation, the all-or-nothing auth source selection, the auth-method priority and auth-path resolution, the incomplete-opts error (no env/file token fallback), and that OIDC still reads the GitHub-injected ACTIONS_ID_TOKEN_REQUEST_* variables. Signed-off-by: Dmitry Mordvinov <dmitry.mordvinov@flant.com>
Signed-off-by: Dmitry Mordvinov <dmitry.mordvinov@flant.com>
staticAuthenticator.Login was a no-op that never called client.SetToken, so in opts-mode a configured TokenAuth was silently dropped: the Vault SDK auto-loads VAULT_TOKEN at client construction, so requests were signed under the environment token (wrong identity) or, with VAULT_TOKEN unset, sent unauthenticated. This violated the opts-mode guarantee that credentials come only from the selected variant and never from the host environment. Login now calls client.SetToken(s.tokenID) on every invocation, which also fixes the pre-existing latent ~/.vault-token env-mode path. Add a Login-level regression test on a real *vault.Client covering both a conflicting VAULT_TOKEN and an unset VAULT_TOKEN; existing tests only asserted the constructed tokenID field and never exercised Login, so the bug was invisible to CI. Signed-off-by: Dmitry Mordvinov <dmitry.mordvinov@flant.com>
…leak vault.NewClient auto-loads VAULT_TOKEN from the environment. For AppRole/JWT/OIDC in opts-mode, baseAuthenticator.login writes to /auth/<path>/login before SetToken, leaking the host token in the X-Vault-Token header of the login request — violating the documented opts-mode isolation guarantee. Clear the token right after client construction; each authenticator installs its own token before signing. Add a regression test asserting the login request carries no host token, and correct the README's JWT-source note for opts-mode. Signed-off-by: Dmitry Mordvinov <dmitry.mordvinov@flant.com>
Signed-off-by: Dmitry Mordvinov <dmitry.mordvinov@flant.com>
Signed-off-by: Dmitry Mordvinov <dmitry.mordvinov@flant.com>
Signed-off-by: Dmitry Mordvinov <dmitry.mordvinov@flant.com>
dmmordvi
left a comment
There was a problem hiding this comment.
Automated code review (via pi-pi).
The change is correct, well-tested, and mergeable as-is: programmatic VaultOpts/VaultAuth configuration is cleanly threaded through KeyOpts.SignerVerifierOpts, and two real latent bugs are fixed along the way (host-token leak in the login request header; static-token Login no-op). go build, go vet, and go test -race ./pkg/signver/... all pass. No blocking issues were found. Three minor polish items are noted as inline comments.
(generated by pi-pi)
| @@ -20,7 +20,7 @@ type baseAuthenticator struct { | |||
| } | |||
|
|
|||
| func (b *baseAuthenticator) login(client *vault.Client, data map[string]interface{}) error { | |||
There was a problem hiding this comment.
MINOR: the auth mount path is now captured once when the authenticator is constructed and used immutably here, whereas previously it was re-read from WERF_VAULT_AUTH_PATH on every login. A change to that env var after construction but before a token refresh no longer takes effect. In practice the authenticator is built fresh per LoadSignerVerifier call, so this only matters for a long-lived instance whose environment mutates mid-lifetime — practically never. Informational; the immutable design is arguably cleaner. If per-login env re-read is a required behavior, restore a dynamic path resolver for the env path and add a refresh-after-mutation test.
(generated by pi-pi)
There was a problem hiding this comment.
so this only matters for a long-lived instance whose environment mutates mid-lifetime — practically never
I think we can ignore it, it is not our case
| // validReference returns a non-nil error if the reference string is invalid | ||
| func validReference(ref string) error { | ||
| // ValidReference returns a non-nil error if the reference string is invalid | ||
| func ValidReference(ref string) error { |
There was a problem hiding this comment.
MINOR: validReference was renamed to the exported ValidReference, but its only caller is internal (client.go). Exporting it commits the function name and its error semantics to the public API surface. Consider keeping it unexported unless an external consumer actually needs it; if external use is intended, document that intent.
(generated by pi-pi)
There was a problem hiding this comment.
It is needed for KeyRef validation for ELF signing trdl
| @@ -69,6 +69,15 @@ type SignerVerifier struct { | |||
| // It also can verify signatures (via a remote vall to the Vault instance). The hashFunc will be | |||
| // automatically set to crypto.Hash(0) if the key referred to by referenceStr is an ED25519 signing key. | |||
| func LoadSignerVerifier(referenceStr string, hashFunc crypto.Hash, opts ...signature.RPCOption) (*SignerVerifier, error) { | |||
There was a problem hiding this comment.
MINOR: LoadSignerVerifier accepts opts ...signature.RPCOption but never uses it (pre-existing, not introduced here). Sitting next to the new LoadSignerVerifierWithOpts — whose options do change behavior — this is now a confusing API with two differently-typed "opts", one ignored and one honored. At minimum rename the unused parameter to _ ...signature.RPCOption; better, either wire it through or deprecate/remove it in a breaking release.
(generated by pi-pi)
Signed-off-by: Dmitry Mordvinov <dmitry.mordvinov@flant.com>
Summary
Adds programmatic configuration of the HashiCorp Vault SignerVerifier via a new VaultOpts / VaultAuth options API, in addition to the existing environment-variable configuration. Callers can now select the Vault address, transit engine path, and authentication method directly in code through KeyOpts.SignerVerifierOpts.VaultOpts, without relying on VAULT_* / WERF_* env vars. Along the way it fixes two latent authentication bugs.
Motivation
Previously the Vault SignerVerifier could only be configured through environment variables (VAULT_ADDR, VAULT_TOKEN, WERF_VAULT_AUTH_, ACTIONS_, …). This makes it hard for embedders to configure Vault per-call, run with multiple Vault identities in one process, or avoid leaking host credentials. This PR introduces an explicit, typed options API while keeping the env-based behavior fully backward-compatible.
What changed
New programmatic API
Configuration semantics
OIDC still reads the runner-injected ACTIONS_ID_TOKEN_REQUEST_URL / ACTIONS_ID_TOKEN_REQUEST_TOKEN.)
Bug fixes
Docs & tests
Minor
Backward compatibility
Fully backward compatible: existing env-var-only callers are unaffected (Auth == nil reproduces the previous behavior). LoadSignerVerifier's signature is unchanged.