integration

package
v0.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 26, 2026 License: GPL-3.0 Imports: 20 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrNotFound = errors.New("secret not found")

ErrNotFound is returned by KVRead when the secret path does not exist in vault. Callers can use errors.Is to distinguish this from transient failures.

Functions

func Bootstrap

func Bootstrap(ctx context.Context, client *Client, jwtAuthPath string, skipPolicyScopeCheck bool) error

Bootstrap verifies the vault is configured correctly for blockyard. Checks:

  1. the vault is reachable and unsealed (GET /v1/sys/health)
  2. JWT auth method is enabled at the configured path
  3. The "blockyard-user" role exists
  4. KV v2 secrets engine is mounted at "secret/"
  5. At least one attached policy uses per-user path scoping (warning only)

Returns nil if all checks pass. Returns an error describing the first failure. The caller decides whether to treat this as fatal.

func DefaultHTTPClient added in v0.0.4

func DefaultHTTPClient() *http.Client

DefaultHTTPClient returns the http.Client used by vault integration calls when no custom client is configured: 10s timeout, system CA trust. Exposed so callers (renewer, approle login) can opt in to the same defaults.

func EnrollCredential

func EnrollCredential(ctx context.Context, client *Client, sub, service string, data map[string]any) error

EnrollCredential writes a user's credential for a service into the vault 's KV v2 store at secret/data/users/{sub}/apikeys/{service}. Uses the admin token, not the user's scoped token.

func NewHTTPClient added in v0.0.4

func NewHTTPClient(caCertPath string) (*http.Client, error)

NewHTTPClient returns an *http.Client suitable for vault HTTP calls. When caCertPath is empty, the returned client uses Go's default TLS trust (the system CA bundle). When caCertPath points at a PEM file, the returned client trusts only certificates chaining to that bundle — matching vault's own VAULT_CACERT semantics. Operators who need to keep public-CA trust alongside a private CA must concatenate the bundles themselves.

func ResolveSessionSecret added in v0.0.3

func ResolveSessionSecret(ctx context.Context, client *Client) (string, error)

ResolveSessionSecret reads or generates session_secret from vault. If the key exists at secret/data/blockyard/server-secrets, it's used. Otherwise, a new 32-byte random value is generated, stored, and returned.

func ResolveWorkerKey added in v0.0.3

func ResolveWorkerKey(ctx context.Context, client *Client) ([]byte, error)

ResolveWorkerKey reads or generates the worker signing key from vault. Follows the same pattern as ResolveSessionSecret: read if exists, generate + store if not. Transient vault errors are fatal -- only ErrNotFound triggers generation.

Types

type AppRoleAuth added in v0.0.4

type AppRoleAuth struct {
	// contains filtered or unexported fields
}

AppRoleAuth maintains a vault admin token by logging in against the AppRole endpoint. It re-reads the secret_id from disk on every login (when SecretIDFile is set), so operators can rotate secret_id without restarting blockyard. A proactive timer triggers a re-login shortly before the current token expires; 403 responses on admin calls trigger an on-demand re-login through the same singleflight-coalesced path.

func NewAppRoleAuth added in v0.0.4

func NewAppRoleAuth(addr, roleID, secretIDFile string) *AppRoleAuth

NewAppRoleAuth constructs an AppRoleAuth. secretIDFile may be empty, in which case the secret_id is read from BLOCKYARD_VAULT_SECRET_ID at each login attempt. The underlying http.Client uses system CA trust and a 10s timeout; call WithHTTPClient to override (e.g. for a private CA).

func (*AppRoleAuth) Healthy added in v0.0.4

func (a *AppRoleAuth) Healthy() bool

Healthy returns true when the last login attempt succeeded and the stored token has not yet expired. Drives the "vault" readyz/preflight component.

func (*AppRoleAuth) Login added in v0.0.4

func (a *AppRoleAuth) Login(ctx context.Context) error

Login performs an AppRole login, stores the resulting token and schedules the next proactive re-login. Concurrent callers coalesce: only one login hits the wire, the others wait for and share its result. Safe to call at any time (startup, 403-retry, timer fire).

func (*AppRoleAuth) Run added in v0.0.4

func (a *AppRoleAuth) Run(ctx context.Context)

Run blocks until ctx is cancelled, re-logging in shortly before each token expires. Failed re-logins mark the component unhealthy but do not exit the loop — a later retry (or a 403-driven retry via Login) can recover. Retries use exponential backoff (1s → 60s cap) because the 403-retry path on admin calls provides the actual recovery signal, so the proactive loop only needs to keep trying without flooding vault.

A Login that completes outside this loop (startup bootstrap, 403 retry) signals via a.kick so Run re-arms against the fresh nextAt, avoiding a redundant second login at the previously-scheduled time.

func (*AppRoleAuth) Token added in v0.0.4

func (a *AppRoleAuth) Token() string

Token returns the current vault admin token. Empty until a successful login has completed. Safe for concurrent use.

func (*AppRoleAuth) WithHTTPClient added in v0.0.4

func (a *AppRoleAuth) WithHTTPClient(h *http.Client) *AppRoleAuth

WithHTTPClient replaces the underlying http.Client. Returns the receiver to allow chaining from NewAppRoleAuth.

func (*AppRoleAuth) WithSecretIDWrapped added in v0.0.4

func (a *AppRoleAuth) WithSecretIDWrapped(b bool) *AppRoleAuth

WithSecretIDWrapped enables response-wrap mode: the secret_id file is treated as a vault wrap token, and the real secret_id is fetched via sys/wrapping/unwrap on every login for which the file has changed. Requires SecretIDFile to be set. Returns the receiver to allow chaining from NewAppRoleAuth.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a lightweight HTTP client for the Vault-compatible REST API (HashiCorp Vault and its OpenBao fork expose the same wire protocol). It targets only the endpoints blockyard needs: JWT auth login, KV v2 read/write, and sys/health.

func NewClient

func NewClient(addr string, adminTokenFunc func() string, relogin func(context.Context) error) *Client

NewClient creates a new vault client. adminTokenFunc returns the current admin token on each call (so token rotation is transparent to the client); relogin is invoked when an admin-scoped call receives a 403, after which the same request is retried once with a fresh token. Pass nil for relogin when admin auth is static (deprecated admin_token path). The underlying http.Client uses system CA trust and a 10s timeout; call WithHTTPClient to override (e.g. for a private CA).

func (*Client) Addr

func (c *Client) Addr() string

Addr returns the vault server address.

func (*Client) AdminToken

func (c *Client) AdminToken() string

AdminToken returns the current admin token. Satisfies config.SecretResolver.

func (*Client) AuthMountAccessor added in v0.0.4

func (c *Client) AuthMountAccessor(ctx context.Context, path string) (string, error)

AuthMountAccessor returns the opaque accessor of the auth method mounted at `path` (e.g. "jwt"). Accessors are vault-internal identifiers distinct from mount paths; the alias lookup below requires them and operators rarely know them out-of-band.

Called once at startup from board-storage provisioning (#285) to cache the OIDC mount accessor — identity/lookup/entity expects it whereas operators configure the mount by path. GET {addr}/v1/sys/auth

func (*Client) DatabaseStaticCredsRead added in v0.0.4

func (c *Client) DatabaseStaticCredsRead(
	ctx context.Context, mount, name string,
) (username, password string, ttl time.Duration, err error)

DatabaseStaticCredsRead fetches the current username/password for a vault static DB role. Vault manages the PG user out-of-band (the operator registers the role; vault rotates its password on a schedule) — this call just reads the current credentials, which are stable between rotations.

Unlike dynamic creds (`{mount}/creds/{role}`), static-creds leases are not tied to the caller's token, so blockyard can read with its own AppRole token without inheriting a lease that expires with the token. Returned TTL reflects vault's time-to-next-rotation, not a lease bound to this caller.

Used for admin creds (#238, via cfg.Database.VaultRole) and by workers for per-user creds (#284, `{mount}/static-creds/user_<id>`). GET {addr}/v1/{mount}/static-creds/{name}

func (*Client) DatabaseStaticRoleCreate added in v0.0.4

func (c *Client) DatabaseStaticRoleCreate(
	ctx context.Context,
	mount, name, username, dbName, rotationPeriod string,
) error

DatabaseStaticRoleCreate registers (or updates) a static DB role on vault's `database` secrets engine. Called from blockyard's first-login flow for board storage (#284): after the per-user PG role user_<sub> exists, this tells vault to adopt it and start rotating its password on the given period. Vault immediately rotates the temporary password set at creation time; subsequent reads of `{mount}/static-creds/{name}` return the current one.

Idempotent: vault returns 200/204 on update of an existing role.

Uses the admin AppRole token configured via [vault]. POST {addr}/v1/{mount}/static-roles/{name}

func (*Client) Health

func (c *Client) Health(ctx context.Context) error

Health checks if the vault is reachable and unsealed. GET {addr}/v1/sys/health

func (*Client) IdentityLookupEntityByAlias added in v0.0.4

func (c *Client) IdentityLookupEntityByAlias(
	ctx context.Context, aliasName, aliasMountAccessor string,
) (string, error)

IdentityLookupEntityByAlias resolves the vault entity ID for the given (aliasName, aliasMountAccessor) pair. Vault assigns entity UUIDs the first time it sees an alias (e.g. on OIDC first login), so this call is well-defined only after the user has logged in through the given auth mount at least once.

Used by board-storage provisioning (#285) to derive a stable PG role name `user_<entity-id>` — the entity ID is the same identifier the templated per-user vault policy resolves at the ACL layer, so the two sides agree without blockyard writing anything on vault's identity side. POST {addr}/v1/identity/lookup/entity

func (*Client) JWTLogin

func (c *Client) JWTLogin(ctx context.Context, mountPath, accessToken string) (token string, ttl time.Duration, err error)

JWTLogin exchanges an IdP access token for a scoped vault token. POST {addr}/v1/auth/{mountPath}/login

func (*Client) KVRead

func (c *Client) KVRead(ctx context.Context, path string, token string) (map[string]any, error)

KVRead reads a secret from the KV v2 secrets engine. GET {addr}/v1/secret/data/{path}

func (*Client) KVWrite

func (c *Client) KVWrite(ctx context.Context, path string, data map[string]any) error

KVWrite writes a secret to the KV v2 secrets engine using the admin token. PUT {addr}/v1/secret/data/{path}

func (*Client) SecretExists

func (c *Client) SecretExists(ctx context.Context, path string) (bool, error)

SecretExists checks whether a secret exists at the given KV v2 data path without reading its value. It queries the metadata endpoint (secret/metadata/...) which requires only metadata-read permission. GET {addr}/v1/secret/metadata/{path}

func (*Client) WithHTTPClient added in v0.0.4

func (c *Client) WithHTTPClient(h *http.Client) *Client

WithHTTPClient replaces the underlying http.Client. Returns the receiver to allow chaining from NewClient.

type VaultTokenCache

type VaultTokenCache struct {
	// contains filtered or unexported fields
}

VaultTokenCache caches vault tokens keyed by user sub. Avoids calling the vault 's JWT login endpoint on every proxied request.

func NewVaultTokenCache

func NewVaultTokenCache() *VaultTokenCache

NewVaultTokenCache creates an empty token cache.

func (*VaultTokenCache) Delete

func (c *VaultTokenCache) Delete(sub string)

Delete removes a cached token (e.g. on logout).

func (*VaultTokenCache) Get

func (c *VaultTokenCache) Get(sub string) (string, bool)

Get returns a cached token if it exists and has at least renewalBuffer of remaining validity. Returns ("", false) on miss or near-expiry.

func (*VaultTokenCache) Set

func (c *VaultTokenCache) Set(sub string, token string, ttl time.Duration)

Set stores a token with the given TTL.

func (*VaultTokenCache) Sweep

func (c *VaultTokenCache) Sweep() int

Sweep removes all expired tokens from the cache.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL