auth

package
v0.30.0 Latest Latest
Warning

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

Go to latest
Published: Aug 6, 2026 License: MIT Imports: 32 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrPeerUnreachable = errors.New("peer socket unreachable")

ErrPeerUnreachable is returned by PostFormToPeer when the peer could not be contacted at all: the socket path is empty, the socket file is missing or stale, or the dial failed. It is distinct from a *PeerStatusError, which means the peer answered but with a non-200 status.

Functions

func BaseMethod added in v0.23.0

func BaseMethod(method string) string

BaseMethod strips the "+tpm" sealing modifier, returning the underlying auth flow: "oidc", "ldap", "token", or "mtls". The login dispatch switches on this so a "+tpm" variant routes to the same flow as its base.

func Downscope added in v0.25.0

func Downscope(ctx context.Context, vc *vault.Client, token string, c PolicyConstraint) (string, error)

Downscope exchanges a freshly-minted broad login token for a least-privilege child token carrying only the configured policies, when the constraint is active. The child is minted on an isolated sibling of vc (CreateChildTokenFor) so vc itself is never set to the broad token — Vault still enforces that the requested policies are a subset of the parent's, so this can only drop privilege. Returns the token the caller should adopt and persist: the downscoped child when a constraint is active, otherwise the original token unchanged.

Downscope is a pure "maybe exchange the token" helper: it never warns and never mutates vc. The transition notice for an unrestricted login lives in WarnUnrestrictedPolicy, called only at the sites that adopt a final operational token — so the mtls bootstrap login (whose broad token is transient and never persisted) does not emit it.

On both the active-success and the failure paths the caller's shared client is left exactly as it was, so a downscope failure cannot leave the broad token installed on (or retrievable from) the web server's shared client, and there is no window in which a concurrent reader observes the broad token. The caller is therefore responsible for adopting the returned token — it must call vc.SetToken on the result itself; Downscope deliberately does not.

A downscoping failure is returned as an error rather than silently falling back to the broad token: least privilege must fail closed. The caller treats it like any other login failure.

func FetchTokenFromSocket added in v0.24.0

func FetchTokenFromSocket(ctx context.Context, socketPath string) (string, error)

func FetchTokenFromSockets added in v0.30.0

func FetchTokenFromSockets(ctx context.Context, socketPaths []string) (string, string)

FetchTokenFromSocket retrieves a Vault token from a peer dotvault daemon's web API exposed over a Unix-domain socket — the programmatic equivalent of

curl --unix-socket <socketPath> http://localhost/api/v1/token

It backs dotvault-to-dotvault token sharing: a remote-forwarded socket (e.g. ~/.ssh/dotvault.sock created by an SSH RemoteForward from a machine running the dotvault web UI) lets a host with no interactive login facility borrow the live token from a peer that has one.

It is best-effort and never fatal. An empty path, an unexpandable ~, a missing socket file, a stale socket (file present but no listener), a non-200 response (the peer holds no token), or a malformed body all resolve to ("", nil) so the caller simply carries on with its normal auth flow. The returned token is deliberately NOT validated here — callers run LookupSelf before adopting it, exactly as they do for the token file and DOTVAULT_TOKEN. FetchTokenFromSockets tries each socket in order and returns the first token any peer yields, together with the socket path it came from (both "" when no peer produced one). Callers pass the list in most-stable-first order — see config.TokenBorrowSockets, which puts the long-lived local API socket ahead of an SSH-forwarded peer precisely because the forwarded one disappears when the session drops.

Like FetchTokenFromSocket it is best-effort and never fatal: unusable entries are skipped and an exhausted list is ("", ""). The returned token is NOT validated here; callers run LookupSelf before adopting it, exactly as they do for the token file and DOTVAULT_TOKEN. Returning the source lets them say which peer answered, which is the difference between a diagnosable borrow and a mystery.

func IsExpired

func IsExpired(err error) bool

IsExpired reports whether err is the token-expired sentinel. The Start loop uses this to route expired tokens through the same recovery path as a 403 response.

func IsRenewFailed added in v0.30.0

func IsRenewFailed(err error) bool

IsRenewFailed reports whether err came from a failed renewal of a token that still passes lookup-self. The Start loop uses it to attempt a peer borrow: a token at its max TTL cannot be extended, so the only way to stay authenticated without an interactive login is to pick up someone else's.

func PeerSocketClient added in v0.28.0

func PeerSocketClient(socketPath string) (*http.Client, string, error)

PeerSocketClient builds an http.Client that dials the peer dotvault web-API Unix-domain socket at socketPath (a leading ~ is expanded), returning the client and the expanded path. It is the shared transport seam for every peer-socket consumer — the token borrow below and `dotvault browse` — so "dial the peer's web API" has exactly one implementation. Callers own the error policy: this reports why the socket is unusable (empty path, unexpandable ~, missing file) and the caller decides whether that is fatal (browse falls back to the local browser) or silently skipped (the borrow). A missing socket file is checked here so callers neither log a connection error nor pay a dial timeout for the common "peer not connected" case.

func PostFormToPeer added in v0.29.0

func PostFormToPeer(ctx context.Context, socketPath, apiPath string, form url.Values) error

PostFormToPeer posts form values to a peer dotvault's web API over its Unix-domain socket — the programmatic equivalent of

curl --unix-socket <socketPath> http://localhost/<apiPath> -d k=v …

It is the shared transport for the remote peer-action surfaces: the `dotvault browse`/`dotvault notify` CLIs and the client facade's Browse/Notify. It reuses PeerSocketClient (the same stat-before-dial unix transport the token borrow uses).

It lives in internal/auth for cohesion with PeerSocketClient and FetchTokenFromSocket — all three speak the peer's web API over the same unix transport — even though a browse/notify form-POST is not itself an auth concept. If a third non-auth peer-action surface appears, consider promoting the peer-socket transport to its own internal/peer package rather than cementing internal/auth as the catch-all.

Errors are typed so callers can react: ErrPeerUnreachable (wrapped) when the peer could not be contacted, or a *PeerStatusError when it answered non-200. The request is bounded by peerPostTimeout unless ctx carries a shorter deadline.

func ReadTokenEnv

func ReadTokenEnv() string

ReadTokenEnv reads the Vault token from the DOTVAULT_TOKEN environment variable. VAULT_TOKEN is deliberately ignored — it belongs to the `vault` CLI, and honouring it would let an unrelated shell session's token leak into the daemon (the Vault SDK's own VAULT_TOKEN pickup is likewise neutralised in internal/vault.NewClient).

An env-var token is necessarily plaintext: there is no way to TPM-seal an environment value. The "+tpm" sealing applies only to the token file.

func ReadTokenFile

func ReadTokenFile(path string) (string, error)

ReadTokenFile reads a Vault token from a file, trimming whitespace. Returns empty string (not error) if the file doesn't exist.

A file written by WriteTokenFile with sealing on carries sealedTokenPrefix; this transparently unseals it via the TPM. A plaintext file is returned verbatim, so existing token files keep working and migration is free.

func ResolveToken

func ResolveToken(tokenFilePath string) string

ResolveToken returns a Vault token, checking the DOTVAULT_TOKEN env var first, then the token file. Returns empty string if neither is set. A sealed token file that cannot be unsealed resolves to "" (the warning is logged in ReadTokenFile), which the auth flow treats as "no usable token, re-authenticate" — never a silent plaintext fallback.

func SealTokenAtRest added in v0.23.0

func SealTokenAtRest(method string) bool

SealTokenAtRest reports whether the auth method requests TPM-sealing of the token file — the "+tpm" suffix. When true, WriteTokenFile seals the token under the TPM and writes a self-describing sealed envelope, which ReadTokenFile transparently unseals on read (so the daemon, the CLI, and the public client/ facade all consume it without knowing the method).

func WarnUnrestrictedPolicy added in v0.25.0

func WarnUnrestrictedPolicy(c PolicyConstraint)

WarnUnrestrictedPolicy logs the one-line transition warning when no least-privilege constraint is configured, nudging the operator toward vault.policies before a future release makes restriction the default. It is a no-op once the operator has opted in.

Call it only at the sites that adopt a final operational token from a downscopeable login: the real CLI oidc/ldap logins, the mtls operational login, and the web oidc/ldap handlers. It is deliberately NOT called on the mtls bootstrap sub-login (whose broad token is transient, unpersisted, and about to be replaced by the downscoped cert-auth token), nor for the `token` auth method / web token-login (a user-supplied token dotvault never downscopes — the nudge to set vault.policies would not apply). Keeping the notice tied to the operational token is why it lives here rather than inside Downscope, which the bootstrap path also runs.

func WriteTokenFile

func WriteTokenFile(path string, token string, seal bool) error

WriteTokenFile writes a Vault token to a file with 0600 permissions. When seal is true the token is sealed under the TPM (machine-bound) and written as a self-describing sealed envelope that ReadTokenFile transparently unseals. Sealing requires a working TPM (Linux/Windows); seal=true on a host with no hardware backend returns an error rather than silently writing plaintext — the same no-silent-fallback contract as mtls+tpm key sealing.

An empty path is a deliberate no-op: it means "do not persist this token". The mTLS bootstrap login uses this so its broad, PKI-capable bootstrap token lives only in memory (long enough to mint the certificate) and is never left in the on-disk cache if a later step fails — only the final cert-auth working token is persisted, by certLogin with the real path.

Types

type LifecycleManager

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

LifecycleManager manages token TTL checks and renewal.

func NewLifecycleManager

func NewLifecycleManager(client *vault.Client, checkInterval time.Duration, disableRenewal bool) *LifecycleManager

NewLifecycleManager creates a new token lifecycle manager. When disableRenewal is true the manager still monitors TTL and signals re-auth when the token expires, but never calls RenewSelf.

func (*LifecycleManager) NeedsReauth

func (lm *LifecycleManager) NeedsReauth() bool

NeedsReauth returns true if the token is expired or needs re-authentication.

func (*LifecycleManager) Reload

func (lm *LifecycleManager) Reload()

Reload signals the lifecycle goroutine to perform an immediate tryReload on the next scheduling pass — used by the SIGHUP handler and by the in-process token-file watcher (internal/tokenwatch) so a freshly-written token file (the configured tokenFilePath, by default ~/.dotvault-token) is picked up without waiting for the 5-minute lifecycle tick. Coalescing: concurrent or back-to-back calls collapse into a single reload. Safe to call before or after Start; calls made before Start are buffered and consumed by the goroutine on its first select.

func (*LifecycleManager) SetOnReauth

func (lm *LifecycleManager) SetOnReauth(fn func())

SetOnReauth registers a callback fired when the manager transitions into the needs-reauth state. The callback runs synchronously on the lifecycle goroutine — keep it short. In web mode this is used to clear the in-memory Vault token so the SPA's status check reflects "logged out".

func (*LifecycleManager) SetTokenFilePath

func (lm *LifecycleManager) SetTokenFilePath(p string)

SetTokenFilePath wires a token file path so that on detection of an invalid/expired token the manager will attempt to reload (and re-validate) the token from disk or DOTVAULT_TOKEN before declaring re-auth necessary. This lets an external facility (e.g. a tty session running `dotvault login`) recover a running daemon without a restart.

func (*LifecycleManager) SetTokenSockets added in v0.30.0

func (lm *LifecycleManager) SetTokenSockets(paths []string)

SetTokenSockets wires the ordered peer dotvault web-API Unix sockets so the recovery path can borrow a peer's live token (dotvault-to-dotvault sharing) before declaring re-auth necessary. Empty disables it. See FetchTokenFromSockets.

func (*LifecycleManager) Start

func (lm *LifecycleManager) Start(ctx context.Context) <-chan error

Start begins the token lifecycle goroutine. Returns a channel that receives errors (e.g., when re-auth is needed). The goroutine stops when ctx is cancelled.

type LoginStatus

type LoginStatus struct {
	State      string            `json:"state"`
	Token      string            `json:"-"`
	Error      string            `json:"error,omitempty"`
	MFAMethods []vault.MFAMethod `json:"mfa_methods,omitempty"`
}

LoginStatus represents the current state of an async login attempt.

type LoginTracker

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

LoginTracker manages async login attempts keyed by session ID.

func NewLoginTracker

func NewLoginTracker(vc *vault.Client) *LoginTracker

NewLoginTracker creates a new LoginTracker.

func (*LoginTracker) Clear

func (lt *LoginTracker) Clear(sessionID string)

Clear removes a completed login session.

func (*LoginTracker) Close

func (lt *LoginTracker) Close()

Close stops the background GC goroutine.

func (*LoginTracker) GetStatus

func (lt *LoginTracker) GetStatus(sessionID string) *LoginStatus

GetStatus returns the current login status for a session. Returns nil if the session does not exist.

func (*LoginTracker) StartLogin

func (lt *LoginTracker) StartLogin(sessionID, mount, username, password string)

StartLogin begins an async LDAP login attempt. The login runs in a background goroutine with a 5-minute timeout. Poll GetStatus to check progress.

func (*LoginTracker) SubmitTOTP

func (lt *LoginTracker) SubmitTOTP(sessionID, passcode string)

SubmitTOTP submits a TOTP passcode for an in-progress MFA login.

type MTLSParams added in v0.23.0

type MTLSParams struct {
	// Connectivity for building a cert-presenting login client.
	VaultAddress  string
	CACert        string
	TLSSkipVerify bool

	Method          string // "mtls" | "mtls+tpm"
	BootstrapMethod string
	BootstrapMount  string
	CertMount       string
	CertRole        string
	PKIMount        string
	PKIRole         string
	KeyType         string
	CommonName      string // template over {{.user}}
	TTL             string
	ReissueBefore   time.Duration
	SealToPCRs      bool
	StorageDir      string
	BYOCert         string
	BYOKey          string
}

MTLSParams carries everything the cert-auth flow needs. It is populated by the daemon (cmd/dotvault) from the validated vault.mtls config and attached to a Manager whose AuthMethod is "mtls" or "mtls+tpm".

type Manager

type Manager struct {
	VaultClient   *vault.Client
	TokenFilePath string
	// AuthMethod is the configured method: "oidc", "ldap", "token", "mtls", or
	// "mtls+tpm". A "+tpm" suffix on any base method (e.g. "oidc+tpm") also
	// requests TPM-sealing of the cached token file at rest; for "mtls+tpm"
	// that is in addition to the cert key the cert flow already seals.
	AuthMethod string
	AuthMount  string // auth mount path
	AuthRole   string // optional role
	Username   string
	// OIDCCallbackPort is the fixed local TCP port authenticateOIDC binds for
	// the OAuth redirect_uri (vault.oidc_callback_port). Zero defaults to
	// 8250 (the `vault` CLI's own default); if that port is unavailable,
	// authenticateOIDC falls back to a random port. See oidc.go.
	OIDCCallbackPort int
	// TokenSockets is an ordered list of peer dotvault web-API Unix sockets.
	// When non-empty, Login first tries to borrow a live token from each in
	// turn (dotvault-to-dotvault sharing) before running the configured
	// interactive flow. Missing or stale entries are skipped. Callers build
	// the list most-stable-first via config.TokenBorrowSockets — the local
	// API socket ahead of an SSH-forwarded peer. See FetchTokenFromSockets.
	TokenSockets []string
	// Policy narrows a freshly-minted login token to a least-privilege child
	// token (vault.policies / vault.no_default_policy). The zero value applies
	// no narrowing — the token carries every policy the auth role granted,
	// today's behaviour. Consulted by the oidc/ldap/mtls flows; the bootstrap
	// login that mints an mtls cert is deliberately left un-narrowed because it
	// needs the pki/sign capability.
	Policy PolicyConstraint
	// MTLS is required when the base auth method is "mtls".
	MTLS *MTLSParams
}

Manager orchestrates Vault authentication.

func (*Manager) Authenticate

func (m *Manager) Authenticate(ctx context.Context) error

Authenticate attempts to authenticate with Vault. It first tries to reuse an existing token, then falls back to the configured method.

func (*Manager) Login

func (m *Manager) Login(ctx context.Context) error

Login runs the configured fresh-auth flow unconditionally, without attempting to reuse an existing token. Used by `dotvault login` and as the fallback path inside Authenticate.

func (*Manager) ReissueIfDue added in v0.23.0

func (m *Manager) ReissueIfDue(ctx context.Context) error

ReissueIfDue rotates the certificate when it is inside the re-issue window, using the current operational Vault token (no human). It is a no-op for non-cert methods, when no credential exists, or when the cert is not yet due for rotation. The daemon calls this periodically so a long-running process whose token keeps renewing still rotates its certificate before expiry — without this, a warm daemon never re-enters the cert flow and the cert could expire unrotated. Safe to call repeatedly: after one successful rotation the fresh NotAfter moves out of the window and subsequent calls return nil.

type PeerStatusError added in v0.29.0

type PeerStatusError struct {
	Status  int
	Message string
}

PeerStatusError reports that the peer's web API answered a PostFormToPeer request with a non-200 status. Message carries the peer's {"error": …} body when present. Callers distinguish a 400 (the peer rejected the request as invalid) from a 5xx/502/503 (the peer could not perform the action) via Status.

func (*PeerStatusError) Error added in v0.29.0

func (e *PeerStatusError) Error() string

type PolicyConstraint added in v0.25.0

type PolicyConstraint struct {
	// Policies is the explicit set of Vault policies the working token should
	// carry. When non-empty the login token is exchanged for a child token
	// restricted to exactly these policies (a subset of the login token's own
	// policies — Vault enforces the subset rule). Empty means "carry whatever
	// the auth role granted".
	Policies []string
	// NoDefaultPolicy, when true, strips the implicit `default` policy from the
	// downscoped token.
	NoDefaultPolicy bool
}

PolicyConstraint describes the least-privilege downscoping applied to a freshly-minted login token. Its two fields come straight from the vault config (vault.policies / vault.no_default_policy).

The zero value applies no narrowing — the working token carries every policy the auth role granted, which is dotvault's historical behaviour. Operators opt in to least privilege by populating Policies (and, increasingly, NoDefaultPolicy); see docs/configuration/config-reference.md for the staged rollout that ends with no_default_policy forced on at 1.0.

func (PolicyConstraint) Active added in v0.25.0

func (c PolicyConstraint) Active() bool

Active reports whether any narrowing is requested. With neither an explicit policy set nor no_default_policy, the login token is adopted verbatim.

Jump to

Keyboard shortcuts

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