API reference¶
Every exported symbol in gitlab.com/phpboyscout/go/credentials and its two
subpackages, with what it does, what it defaults to, and what happens when it goes
wrong. Signatures and doc comments are also generated on
pkg.go.dev; this page
adds the behaviour that a signature does not show.
Package credentials¶
Store¶
Writes secret under the service/account pair on the registered
Backend, overwriting any existing entry.
- Default backend: the stub, which returns
ErrCredentialUnsupportedand writes nothing. - Context: forwarded to the backend. The shipped OS-keychain backend bounds the call by the context and abandons it on expiry — see why keychain calls are bounded.
- On error: nothing was written, unless the error is a context error, in which case the abandoned call may still complete later.
Retrieve¶
Reads the secret stored under service/account.
| Outcome | Return |
|---|---|
| Entry found | the secret, nil |
| Backend healthy, no such entry | "", ErrCredentialNotFound |
| No keychain-capable backend registered | "", ErrCredentialUnsupported |
| Any other backend failure | "", a wrapped backend error |
| Context cancelled or deadline exceeded | "", the context error |
Distinguish the first two error cases with errors.Is — they mean different things
to a resolver, and only ErrCredentialNotFound says the store is working.
Delete¶
Removes the entry under service/account. Idempotent against a real backend:
deleting an entry that does not exist returns nil.
The stub backend is the exception — it returns ErrCredentialUnsupported from
Delete like everything else, because a process with no backend never stored
anything to delete.
KeychainAvailable¶
Reports whether the registered backend claims it can serve calls. It is the
registered Backend's own Available() answer, so it is a cheap static check with
no I/O: false under the default stub, true once the credentials/keychain
subpackage is imported or a custom backend is registered.
It does not mean the keychain works. Use Probe before offering
keychain storage to a user.
AvailableModes¶
The modes this process is capable of: ModeEnvVar, then ModeKeychain when
KeychainAvailable() is true, then ModeLiteral — always, including under CI. It
does not filter by CI and does not probe. See
storage modes.
Probe¶
Performs a live canary round-trip to decide whether keychain storage is usable
right now: Store → Retrieve → Delete under the reserved service
credentials-keychain-probe, with a per-invocation account of the form
probe-<pid>-<8 hex digits> and the value probe. The result is discarded.
- Returns
falseimmediately, touching nothing, whenKeychainAvailable()is false. - Returns
falsewhen any of the three steps fails or the context expires. - Returns
trueonly when all three succeed.
Each step is bounded by the caller's context independently of whether the backend
honours it, so a wedged keychain cannot stall a setup flow. Pass a context with a
deadline — KeychainOpTimeout is the suggested value.
A probe that times out can leave an entry behind
If the canary write is abandoned at the deadline but later completes, the
credentials-keychain-probe entry is not cleaned up: the cleanup Delete runs
only when the write and read both returned. The entry is inert — the value is
the literal string probe — but it will be visible in the user's keychain.
Clean up manually with secret-tool clear service credentials-keychain-probe
on Linux, or the platform equivalent.
RegisterBackend¶
Swaps the process-wide backend. Safe to call at any time from any goroutine — the
registry is an atomic pointer — including from an init() while the program is
already running credential calls.
- A nil
Backendis ignored and the previous backend stays in force. - The last registration wins. There is no chain and no fallback; composing
backends means writing a
Backendthat wraps others. - The nil guard only catches a nil interface. A typed nil
(
var b *MyBackend; RegisterBackend(b)) is not nil as an interface, so it is installed and any method that dereferences the receiver panics on the next credential call. Register a real value.
ModeChoices¶
func ModeChoices(ci, keychainUsable bool, envLabel, keychainLabel, literalLabel string) []ModeChoice
Builds the selectable mode list for a UI. Filtering and ordering are documented under storage modes. Labels are used verbatim and never defaulted.
DefaultPrompter¶
The stdlib Prompter: reads os.Stdin, writes prompts to
os.Stderr (so prompts never pollute a piped stdout), and masks secret entry with
golang.org/x/term when stdin is a terminal.
SelectMode¶
Prints the title, a numbered menu with the first entry marked *, and
Select [1-N] (default 1):. An empty line selects the first choice. A non-numeric
or out-of-range entry prints invalid selection … and re-prompts. An empty
choices slice returns the error no storage modes offered.
InputEnvVarName¶
Prints title [placeholder]:, or title: when placeholder is empty. An empty
line takes the placeholder. When validate is non-nil and returns an error, the
error is printed and the prompt repeats; pass
ValidateEnvVarName to enforce the POSIX shape. A nil
validate accepts anything, including an empty string when there is no placeholder.
InputSecret¶
Prints title: and reads without echo only when stdin is a terminal. On a
pipe — tests, echo secret | mytool, most CI — there is no terminal echo to
suppress, so it falls back to a plain line read.
Cancellation limits of the default prompter¶
The context is checked before each prompt, not during a read. Once
DefaultPrompter is blocked waiting for a line, cancelling the context does not
unblock it; the check takes effect on the next iteration. A closed or exhausted
input stream surfaces as a wrapped io.EOF, so a prompt against a closed stdin
returns an error rather than looping.
IsCI¶
True when the environment variable CI is exactly the string true. See
configuration for the case-sensitivity trap.
ValidateEnvVarName¶
Enforces ^[A-Z][A-Z0-9_]{0,63}$ — uppercase ASCII start, then uppercase ASCII,
digits and underscores, 1 to 64 characters total.
| Input | Result |
|---|---|
"" |
error: env var name is required |
| anything not matching the pattern | error: env var name must match ^[A-Z][A-Z0-9_]{0,63}$ |
| a matching name | nil |
Both errors are written for direct display in a prompt. Lowercase names, leading digits or underscores, and names over 64 characters are rejected — see configuration.
RefuseLiteralUnderCI¶
Returns an error when mode is exactly ModeLiteral and IsCI() is true;
nil otherwise. The error carries a cockroachdb/errors hint:
- message:
literal credential storage is refused under CI - hint:
CI environments must use platform-injected secrets referenced via env-var mode.
Nothing calls this for you. It is the single place the rule lives, so call it on every path that can select a mode — including paths that read a mode from a config file rather than a menu.
ClearKeysExcept¶
Blanks every key in all that is not listed in keep, by calling w.Set(key, "").
Setup flows call it after writing the selected mode's key so that re-running setup
in a different mode cannot leave a prior secret — or a stale reference — behind to
mask the new one.
- Keys are set to the empty string, not deleted — Viper has no unset primitive. Callers must treat an empty value as absent.
- Empty strings in
alland inkeepare ignored. - It returns nothing and cannot fail; whether the write reaches disk is the
KeyWriter's business.
Mode¶
The three constants and their string values are in storage modes.
ModeChoice¶
A mode paired with the human-facing label you supplied to
ModeChoices. Plain data, so any UI can render it.
KeychainOpTimeout¶
The suggested bound on a single backend operation. It is advisory: nothing in
the package applies it. Derive your own context from it before calling Probe,
Store, Retrieve or Delete, or a locked keychain will block for as long as the
platform takes.
Backend¶
type Backend interface {
Store(ctx context.Context, service, account, secret string) error
Retrieve(ctx context.Context, service, account string) (string, error)
Delete(ctx context.Context, service, account string) error
Available() bool
}
The contract every credential store satisfies. The obligations of each method — in
particular that Retrieve MUST return ErrCredentialNotFound rather than an empty
string, and that Delete MUST be idempotent — are tabulated in
implement a custom backend.
Available must be a cheap static check and must not perform I/O; Probe is the
live check.
Prompter¶
type Prompter interface {
SelectMode(ctx context.Context, title string, choices []ModeChoice) (Mode, error)
InputEnvVarName(ctx context.Context, title, placeholder string, validate func(string) error) (string, error)
InputSecret(ctx context.Context, title string) (string, error)
}
The interactive-capture seam. Implementations MUST NOT echo or log a secret, and SHOULD honour cancellation where the underlying reader allows it. See theme the credential prompts.
KeyWriter¶
The minimal config-write surface ClearKeysExcept needs. A
Viper instance satisfies it, as does any wrapper with a Set(string, any) method,
so the module needs no dependency on a config package.
Sentinel errors¶
ErrCredentialUnsupported and ErrCredentialNotFound have their own page:
errors reference.
Package credentials/keychain¶
Blank-importing this package registers an OS-keychain backend at init():
keychain.Backend¶
A credentials.Backend over
go-keyring: macOS Keychain, Linux Secret
Service (GNOME Keyring, KWallet) over D-Bus, Windows Credential Manager. The zero
value is usable, so keychain.Backend{} can be registered explicitly or embedded in
a wrapping backend.
Available()returnstrueunconditionally — importing the package is the declaration that you want keychain behaviour. It is not a health check.Retrievemaps go-keyring'sErrNotFoundtocredentials.ErrCredentialNotFound; other failures are wrapped askeyring.Get <service>/<account>.DeletemapsErrNotFoundtonil, making it idempotent.- Every call is bounded by the caller's context and abandoned on expiry — see why keychain calls are bounded.
- Neither the service, the account nor the secret is ever interpolated into an
error message beyond the
service/accountpair.
Package credentials/test¶
Import aliased, so the reading context is obvious:
test.MemoryBackend¶
An in-process credentials.Backend backed by a mutex-guarded map. The zero value is
usable and it is safe for concurrent use, so tests that inject it directly may run
in parallel. It performs no IPC and never pulls in go-keyring, so linking it does not
compromise a keychain-free build.
It mirrors the real contract: Retrieve on an unknown pair returns
ErrCredentialNotFound, Delete is idempotent, Store overwrites. It differs in
one respect — a cancelled context makes every method return ctx.Err()
immediately, where the OS backend may already have committed an abandoned call.
Available() returns true, so KeychainAvailable and Probe both succeed under
it and your keychain-mode branches are exercised.
test.Install¶
Registers a fresh MemoryBackend as the process-wide backend and returns it, so a
test can seed entries directly. On t.Cleanup it registers a stub backend.
Install is not parallel-safe, and cleanup restores a stub
Install mutates process-wide state, so a test that calls it must not call
t.Parallel(), and an installed backend must not be shared across parallel
subtests. Cleanup registers a stub rather than restoring whatever was registered
before, so in a test binary that blank-imports credentials/keychain, the first
Install permanently replaces the real keychain backend with a stub for the rest
of the run. Register the keychain backend again yourself if a later test needs
it.