credentials

package
v0.17.0 Latest Latest
Warning

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

Go to latest
Published: Jun 15, 2026 License: MIT Imports: 10 Imported by: 0

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:

  1. 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.
  2. 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.
  3. 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

Constants

View Source
const KeychainOpTimeout = 5 * time.Second

KeychainOpTimeout bounds any single credentials-backend operation (probe, store, retrieve, delete) performed by a setup wizard so a misbehaving remote or locked keychain cannot stall first-run setup indefinitely. Wizards SHOULD derive a context with this timeout before calling Probe, Store, Retrieve, or Delete.

Variables

View Source
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.

View Source
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

func Delete(ctx context.Context, service, account string) error

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 IsCI added in v0.17.0

func IsCI() bool

IsCI reports whether the process appears to be running under a CI system. The single source of truth for every setup wizard's CI gate — in particular the ModeLiteral refusal enforced by RefuseLiteralUnderCI.

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

func Probe(ctx context.Context) bool

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 RefuseLiteralUnderCI added in v0.17.0

func RefuseLiteralUnderCI(mode Mode) error

RefuseLiteralUnderCI returns a hinted error when literal credential storage is selected while running under CI, and nil otherwise. This is the single source of truth for the CI-literal-refusal invariant: literal-mode writes in CI almost certainly leak the secret to build artefacts or logs, so every wizard funnels its defence-in-depth check through this helper rather than re-deriving it.

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

func Retrieve(ctx context.Context, service, account string) (string, error)

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 StorageModeOptions added in v0.17.0

func StorageModeOptions(ci, keychainUsable bool, envVarLabel, keychainLabel, literalLabel string) []huh.Option[Mode]

StorageModeOptions returns the huh.Option list for a storage-mode selector, filtered by CI state and a live keychain probe. The keychain option surfaces only when keychainUsable is true (backend compiled in AND reachable — a locked keychain or a headless Linux host without D-Bus must not leave the user stuck on a dead option). The literal option is hidden under CI so the wizard never offers a mode that RefuseLiteralUnderCI would immediately reject.

envVarLabel, keychainLabel, and literalLabel let each wizard tailor the option captions (Bitbucket's dual-credential model phrases them differently from the single-secret AI/GitHub wizards).

func Store

func Store(ctx context.Context, service, account, secret string) error

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.

func ValidateEnvVarName added in v0.17.0

func ValidateEnvVarName(name string) error

ValidateEnvVarName enforces a conservative `^[A-Z][A-Z0-9_]{0,63}$` shape so the chosen name is a valid POSIX environment variable and fits downstream shell/YAML contexts without quoting. Returns a descriptive error suitable for surfacing directly in a TUI form.

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.

Jump to

Keyboard shortcuts

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