Documentation
¶
Overview ¶
Package credstore provides secure storage for LFX CLI credentials and non-sensitive device state.
Secrets (refresh token, cached access token and its expiry) are stored in the operating system's credential store via github.com/99designs/keyring: macOS Keychain, Windows Credential Manager, Linux Secret Service/KWallet, or the `pass` password store. Which of these keyring.Open actually selects can vary between invocations on the same machine (e.g. Secret Service reachable in one shell session but not another); --backend pins it to one, and once pinned, the DeviceState.Backend it was pinned to must match on every later command. keyring's own encrypted-file backend is deliberately excluded from this list: it isn't a real system keychain, and its Remove behavior doesn't match keyring.ErrKeyNotFound (see keyringSecrets.Delete). If none of the allowed backends are available, New returns an error rather than silently falling back to a file.
When --insecure-storage is passed explicitly, the system keychain is bypassed entirely in favor of a plain (unencrypted), owner-only (0600 on POSIX; on Windows, Go's Chmod maps 0600 to the read-only attribute instead of a real ACL, so confidentiality there relies on the file's inherited directory permissions) JSON file under the state directory. This is intended for headless/CI use where no passphrase prompt is acceptable, and is deliberately less secure than the keyring-backed storage.
Non-sensitive state (environment, IdP domain, and audience used at login) is always stored as plain JSON under the XDG state directory (~/.local/state/lfx-cli/ by default), per XDG Base Directory conventions for mutable runtime state.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNotFound = errors.New("credstore: not found")
ErrNotFound is returned by Load methods when no value has been stored yet.
Functions ¶
This section is empty.
Types ¶
type Backend ¶
type Backend struct {
// Name is the keyring.BackendType identifier (e.g. "keychain").
Name string
// DisplayName is a human-readable label for Name.
DisplayName string
}
Backend describes one of the system credential-store backends compiled into this binary for the current OS, as reported by `lfx auth backends`.
func AvailableBackends ¶
func AvailableBackends() []Backend
AvailableBackends reports the system credential-store backends compiled into this binary for the current OS (Go build tags determine which backends are even possible per-platform; see the per-backend source files in github.com/99designs/keyring), in the same priority order (see systemBackends) that New passes to keyring.Open as AllowedBackends. It does not attempt to open any backend, so a backend listed here may still fail at login time if it isn't actually usable at runtime (e.g. no D-Bus session for Secret Service, `pass` not initialized, etc.).
type Credentials ¶
type Credentials struct {
RefreshToken string `json:"refresh_token"`
AccessToken string `json:"access_token,omitempty"`
AccessTokenExpiry time.Time `json:"access_token_expiry,omitempty"`
}
Credentials holds the secrets needed to authenticate with the LFX platform: the long-lived Auth0 refresh token, and an optional cached access token with its expiry.
func (Credentials) ValidAccessToken ¶
func (c Credentials) ValidAccessToken() bool
ValidAccessToken reports whether the cached access token is present and not yet expired, allowing for a small clock-skew buffer.
type DeviceState ¶
type DeviceState struct {
IDPDomain string `json:"idp_domain,omitempty"`
// Environment is the `--env` value used at login (prod, staging, or
// development), determining which compiled-in client ID is used to
// refresh the access token.
Environment string `json:"environment,omitempty"`
// Audience is the `--audience` value used at login. Auth0's
// refresh_token grant automatically ties the refreshed access token
// to the audience it was originally issued for, so this isn't sent
// back on refresh; it's persisted purely for display in
// `lfx auth status`.
Audience string `json:"audience,omitempty"`
// Insecure records whether `--insecure-storage` was passed at login,
// i.e. whether Credentials live in the plain-file backend rather than
// the system keychain. state.json itself is not namespaced by
// backend (both share the same file), so callers must check this
// against the invocation's own --insecure-storage flag before trusting
// the rest of the state: without that check, logging into one backend
// silently overwrites the metadata (env, IdP domain) that the other
// backend's still-present credentials depend on.
Insecure bool `json:"insecure,omitempty"`
// Backend records the keyring.BackendType (e.g. "keychain") pinned via
// --backend at login, or "" if the backend was left to
// keyring.Open's own auto-detection. Unlike Insecure, an empty value
// here is not itself trustworthy: keyring.Open can silently select a
// *different* system backend across invocations (e.g. Secret Service
// is reachable in one shell session but not another, falling back to
// pass), so an unpinned login can't be protected against later landing
// on a different backend with the same state.json. Once a backend has
// been pinned, though, callers must require the same --backend
// value on every later command against this state, the same way
// Insecure is enforced.
Backend string `json:"backend,omitempty"`
}
DeviceState holds non-sensitive information persisted between CLI invocations so that commands like `lfx auth token` don't need to re-specify the environment, IdP domain, or audience used at login.
Note: this deliberately does not include a persistent "device ID". One was considered on the assumption that `gh` uses one as part of its OAuth device flow, but `gh`'s `~/.local/state/gh/device-id` is actually just an anonymous telemetry identifier (see `internal/telemetry.getOrCreateDeviceID` in github.com/cli/cli) -- it plays no role in the OAuth device authorization grant and isn't sent to GitHub's API. Since the LFX CLI has no telemetry pipeline, and Auth0's device flow has no concept of a device ID at all, there's nothing here for one to do. Revisit if/when opt-in CLI telemetry is added.
type Options ¶
type Options struct {
// Insecure bypasses the system keychain in favor of a plain, owner-only
// (0600; see writeOwnerOnlyFile for the Windows caveat) JSON file.
// Intended for headless/CI use where a passphrase prompt is
// unacceptable. Corresponds to the CLI's --insecure-storage
// flag.
Insecure bool
// StateDir overrides the computed state directory. Intended for tests;
// leave empty to use $XDG_STATE_HOME/lfx-cli (or ~/.local/state/lfx-cli
// if $XDG_STATE_HOME is unset).
StateDir string
// Backend pins keyring.Open to a single system backend (e.g.
// "keychain"; see AvailableBackends for the valid values on this OS),
// instead of letting it probe systemBackends in priority order and
// silently use whichever one currently opens. Ignored when Insecure is
// set. Leave empty to keep the previous auto-detecting behavior.
Backend string
}
Options configures a Store returned by New.
type Store ¶
type Store interface {
// SaveCredentials persists secrets to the system keychain (or, with
// Options.Insecure, the plain-file backend).
SaveCredentials(creds Credentials) error
// LoadCredentials returns the persisted secrets, or ErrNotFound if none
// have been saved.
LoadCredentials() (Credentials, error)
// DeleteCredentials removes any persisted secrets. It is a no-op if
// none exist.
DeleteCredentials() error
// SaveDeviceState persists non-sensitive device state as plain JSON.
SaveDeviceState(state DeviceState) error
// LoadDeviceState returns the persisted device state, or ErrNotFound if
// none has been saved.
LoadDeviceState() (DeviceState, error)
// DeleteDeviceState removes any persisted device state. It is a no-op
// if none exists.
DeleteDeviceState() error
}
Store is the credential storage abstraction used by the auth commands.