Documentation
¶
Index ¶
- Variables
- func Bootstrap(ctx context.Context, client *Client, jwtAuthPath string, ...) error
- func DefaultHTTPClient() *http.Client
- func EnrollCredential(ctx context.Context, client *Client, sub, service string, data map[string]any) error
- func NewHTTPClient(caCertPath string) (*http.Client, error)
- func ResolveSessionSecret(ctx context.Context, client *Client) (string, error)
- func ResolveWorkerKey(ctx context.Context, client *Client) ([]byte, error)
- type AppRoleAuth
- func (a *AppRoleAuth) Healthy() bool
- func (a *AppRoleAuth) Login(ctx context.Context) error
- func (a *AppRoleAuth) Run(ctx context.Context)
- func (a *AppRoleAuth) Token() string
- func (a *AppRoleAuth) WithHTTPClient(h *http.Client) *AppRoleAuth
- func (a *AppRoleAuth) WithSecretIDWrapped(b bool) *AppRoleAuth
- type Client
- func (c *Client) Addr() string
- func (c *Client) AdminToken() string
- func (c *Client) AuthMountAccessor(ctx context.Context, path string) (string, error)
- func (c *Client) DatabaseStaticCredsRead(ctx context.Context, mount, name string) (username, password string, ttl time.Duration, err error)
- func (c *Client) DatabaseStaticRoleCreate(ctx context.Context, mount, name, username, dbName, rotationPeriod string) error
- func (c *Client) Health(ctx context.Context) error
- func (c *Client) IdentityLookupEntityByAlias(ctx context.Context, aliasName, aliasMountAccessor string) (string, error)
- func (c *Client) JWTLogin(ctx context.Context, mountPath, accessToken string) (token string, ttl time.Duration, err error)
- func (c *Client) KVRead(ctx context.Context, path string, token string) (map[string]any, error)
- func (c *Client) KVWrite(ctx context.Context, path string, data map[string]any) error
- func (c *Client) SecretExists(ctx context.Context, path string) (bool, error)
- func (c *Client) WithHTTPClient(h *http.Client) *Client
- type VaultTokenCache
Constants ¶
This section is empty.
Variables ¶
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:
- the vault is reachable and unsealed (GET /v1/sys/health)
- JWT auth method is enabled at the configured path
- The "blockyard-user" role exists
- KV v2 secrets engine is mounted at "secret/"
- 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
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
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
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
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) AdminToken ¶
AdminToken returns the current admin token. Satisfies config.SecretResolver.
func (*Client) AuthMountAccessor ¶ added in v0.0.4
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 ¶
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 ¶
KVRead reads a secret from the KV v2 secrets engine. GET {addr}/v1/secret/data/{path}
func (*Client) KVWrite ¶
KVWrite writes a secret to the KV v2 secrets engine using the admin token. PUT {addr}/v1/secret/data/{path}
func (*Client) SecretExists ¶
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}
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.