Documentation
¶
Overview ¶
Package credentials describes how a user-supplied secret (VCS token, AI-provider API key, Bitbucket dual-credential blob) is persisted by the interactive setup wizard and resolved by the runtime config chain.
Storage modes ¶
Three modes are supported, in descending order of preference:
- ModeEnvVar — the config records the NAME of an environment variable. The actual secret lives outside the config file, typically in the user's shell profile or a CI platform's secret-injection mechanism. This is the recommended default and the only mode permitted under CI.
- ModeKeychain — the config records a keychain reference (`<service>/<account>`). The secret lives in the OS keychain (macOS Keychain, Linux Secret Service via godbus, Windows Credential Manager). Available only when the process has a keychain-capable Backend registered — typically by blank- importing the optional gitlab.com/phpboyscout/go-tool-base/pkg/credentials/keychain subpackage. Without such a registration, Store / Retrieve / Delete return ErrCredentialUnsupported and resolvers fall through.
- ModeLiteral — the secret is written as plaintext in the config file. Supported for backward compatibility and for air-gapped or throwaway environments. Refused under CI.
Why a dedicated package ¶
The setup wizard, config masking, doctor checks, and migration tooling all reason about "which storage modes are available" and "how do we retrieve a credential stored in each mode". Consolidating those concerns here avoids scattering the same switch statement across eight call sites.
Separation of the keychain backend ¶
The go-keyring-backed implementation lives in a dedicated subpackage (gitlab.com/phpboyscout/go-tool-base/pkg/credentials/ keychain) that registers itself via RegisterBackend during its init(). Downstream tools that want OS keychain support blank- import that subpackage from their cmd/main; regulated or compliance-constrained downstreams omit the import and run with the stub Backend, so linker dead-code elimination keeps go- keyring, godbus, and wincred out of their binary even though the packages exist in the module. Binary-level SBOM review (syft, cyclonedx-gomod on the linked artefact) shows the opt-out surface unambiguously.
See docs/development/specs/2026-04-02-credential-storage-hardening.md for the full threat model, trust-model guidance, and test matrix.
Index ¶
- Variables
- func Delete(ctx context.Context, service, account string) error
- func KeychainAvailable() bool
- func Probe(ctx context.Context) bool
- func RegisterBackend(b Backend)
- func Retrieve(ctx context.Context, service, account string) (string, error)
- func Store(ctx context.Context, service, account, secret string) error
- type Backend
- type Mode
Constants ¶
This section is empty.
Variables ¶
var ErrCredentialNotFound = errors.New("credential not found in keychain")
ErrCredentialNotFound is returned by Retrieve when the backend is reachable but no entry exists for the given service/account pair. Distinguished from ErrCredentialUnsupported so resolvers can decide whether to fall through to a literal config value.
var ErrCredentialUnsupported = errors.New("keychain support not compiled (import pkg/credentials/keychain or register a custom Backend)")
ErrCredentialUnsupported is returned by Store, Retrieve, and Delete when no keychain-capable Backend has been registered — either because the optional pkg/credentials/keychain subpackage was not imported, or because a custom backend has opted out. Callers that want to fall through to a literal or env-var step should errors.Is against this sentinel.
Functions ¶
func Delete ¶
Delete removes a secret from the registered Backend. Idempotent: returns nil when the entry is missing. Returns ErrCredentialUnsupported when no real backend is registered.
func KeychainAvailable ¶
func KeychainAvailable() bool
KeychainAvailable reports whether a keychain-capable Backend is registered. Returns false in the default (stub-backend) process. Setup wizards use this as a first-pass gate before Probe.
func Probe ¶
Probe reports whether the registered Backend is reachable at the time of the call — useful for setup wizards that want to hide the ModeKeychain option on hosts where the backend is compiled in but locked, unreachable (headless Linux without a Secret Service provider, Vault unreachable), or otherwise unusable. It performs a canary Set → Get → Delete round-trip under the reserved "gtb-keychain-probe" service with a per-invocation random account and discards the result.
With the stub backend, Probe short-circuits to false without touching anything. A true return therefore means both "a backend is registered" AND "the backend accepts round-trip calls right now", which is the precise guard the wizard needs before offering keychain as a storage option.
Callers SHOULD pass a context with a short timeout (a few seconds is reasonable) so a misbehaving remote backend cannot stall the setup wizard indefinitely.
func RegisterBackend ¶
func RegisterBackend(b Backend)
RegisterBackend swaps the active backend. The keychain subpackage calls this from its init(); custom implementations (Vault, AWS SSM, …) may call it from anywhere safe, typically a tool's main() before the first credential call.
func Retrieve ¶
Retrieve reads a secret from the registered Backend. Returns ErrCredentialUnsupported when no real backend is registered, ErrCredentialNotFound when the backend is present but the entry is missing, or a wrapped backend error for other failures. A cancelled context aborts the lookup.
func Store ¶
Store writes a secret to the registered Backend. Returns ErrCredentialUnsupported when no real backend is registered. The context is forwarded to the backend so network-backed implementations (Vault, AWS SSM) can honour deadlines and cancellation; OS-keychain backends ignore it.
Types ¶
type Backend ¶
type Backend interface {
// Store writes a secret under the service/account pair. Must
// overwrite any existing entry with the same key. Neither argument
// may be logged by the implementation — resolvers rely on being
// able to pass these to DEBUG log surfaces safely. Implementations
// SHOULD return ctx.Err() when the context is cancelled before
// the write commits.
Store(ctx context.Context, service, account, secret string) error
// Retrieve reads a secret. MUST return [ErrCredentialNotFound] when
// the backend is healthy but the entry does not exist, so resolvers
// can distinguish "missing" from "unavailable" and fall through
// cleanly. Other failures should wrap the underlying error. A
// cancelled context MUST abort the call.
Retrieve(ctx context.Context, service, account string) (string, error)
// Delete removes a secret. Idempotent: must return nil when the
// entry does not exist. Only surface real failures (e.g. keychain
// locked, remote unreachable) as errors.
Delete(ctx context.Context, service, account string) error
// Available reports whether this backend can currently satisfy
// Store/Retrieve/Delete without an immediate [ErrCredentialUnsupported].
// A freshly-registered keychain backend typically returns true; the
// default stub returns false. Available SHOULD be a cheap static
// check — use [Probe] for a live round-trip.
Available() bool
}
Backend is the minimal contract the credential store presents to the setup wizards, resolvers, and doctor checks. The default implementation (see default_backend.go) returns ErrCredentialUnsupported from every call, so callers that never register a real backend behave exactly as if the keychain feature were compiled out entirely.
Real backends are registered by a downstream optional module — gitlab.com/phpboyscout/go-tool-base/pkg/credentials/keychain — via RegisterBackend. Tool authors may also plug in a custom backend (Vault, AWS SSM, 1Password Connect, …) by implementing this interface and calling RegisterBackend during program init. See docs/how-to/custom-credential-backend.md for a worked example.
Every method takes a context.Context so backends that perform network I/O (remote secret stores, hardware security modules) can honour deadlines and cancellation. OS-keychain backends ignore the context — local IPC is fast — but implementers SHOULD still accept it for uniformity.
type Mode ¶
type Mode string
Mode identifies how a credential is persisted by the setup wizard and resolved at runtime. See package doc for the full trust model.
const ( // ModeEnvVar stores the name of an environment variable that // contains the credential. Recommended default; the only mode // permitted under CI. ModeEnvVar Mode = "env" // ModeKeychain stores the credential in the OS keychain. The // config records a keychain reference; the secret never hits // disk. Available only when the process has registered a // keychain-capable [Backend] — typically by importing the // optional pkg/credentials/keychain subpackage. ModeKeychain Mode = "keychain" // ModeLiteral stores the credential value directly in the // config file. Supported for backward compatibility and // throwaway environments. Refused under CI. ModeLiteral Mode = "literal" )
func AvailableModes ¶
func AvailableModes() []Mode
AvailableModes returns the credential storage modes supported by this process. ModeKeychain is present only when a keychain-capable Backend is registered.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package credtest provides test-only credential backends so downstream tests can exercise keychain-mode behaviour without pulling in github.com/zalando/go-keyring.
|
Package credtest provides test-only credential backends so downstream tests can exercise keychain-mode behaviour without pulling in github.com/zalando/go-keyring. |
|
Package keychain is the optional OS-keychain backend for gitlab.com/phpboyscout/go-tool-base/pkg/credentials.
|
Package keychain is the optional OS-keychain backend for gitlab.com/phpboyscout/go-tool-base/pkg/credentials. |