Documentation
¶
Overview ¶
Package secrets stores the per-project credentials that must never land in the config file: passwords, refresh tokens and cached id tokens.
The OS keychain is the default backing store, but it cannot be the only one. A GitHub Linux runner has no Secret Service, so go-keyring fails there outright; a headless Linux desktop may have none either. Store is therefore an interface with four implementations, selected at startup.
Index ¶
- Constants
- Variables
- func AllKinds() []string
- func DefaultFilePath() (string, error)
- func DeleteAll(s Store, project string) error
- func Has(s Store, project string) bool
- func Key(project, kind string) string
- func ParseKey(key string) (project, kind string, ok bool)
- func ValidateProjectName(name string) error
- type MemStore
- type Store
Constants ¶
const ( KindPassword = "password" KindRefreshToken = "refreshToken" KindIDToken = "idToken" KindIDTokenExp = "idTokenExp" // KindAPIKey is the Firebase Web API key. It confers no authorization by // itself and only ever travels to Google's identity endpoints, but it is kept // out of the plaintext config file and held here alongside the credentials all // the same. KindAPIKey = "apiKey" )
Kinds of secret held per project. Each one is stored under its own key.
They are deliberately not bundled into a single JSON blob: the Windows Credential Manager caps one credential at roughly 2.5 KB and a Firebase id token alone is about 1 KB, so a combined blob would silently fail to save on Windows.
Variables ¶
var ErrNotFound = errors.New("secret not found")
ErrNotFound is returned by Get when the key holds no secret. Every implementation normalises its backend's "missing" error to this one so that callers can branch on it without knowing which store they got.
var ErrReadOnly = errors.New("secret store is read-only")
ErrReadOnly is returned by Set and Delete on a store that cannot be written, such as the environment-backed store used in CI.
Functions ¶
func AllKinds ¶
func AllKinds() []string
AllKinds lists every secret kind a project may have. `fft project remove` walks it to make sure nothing is left behind in the keychain.
func DefaultFilePath ¶
DefaultFilePath is the credentials file fft falls back to when the keychain is unavailable: $XDG_STATE_HOME/fft/credentials.json, or ~/.local/state/fft/ credentials.json when XDG_STATE_HOME is unset.
func DeleteAll ¶
DeleteAll removes every secret belonging to project. It keeps going after a failure so that one unreadable entry cannot strand the rest, and reports all the failures together.
func Has ¶
Has reports whether the project has any credential at all in the store — a password to sign in with, or an id token to use directly.
func Key ¶
Key builds the storage key for one secret of one project, for example "fft:staging:refreshToken".
The key format reserves the colon as its separator, so a project name that itself contains a colon would not round-trip: ParseKey cuts on the first colon after the prefix and would read the tail back as part of the kind. ValidateProjectName holds that invariant at the one door names enter through.
func ValidateProjectName ¶ added in v0.6.0
ValidateProjectName rejects a name that would not round-trip through ParseKey, or that a keychain backend would mangle.
The key format reserves the colon as its separator (fft:<project>:<kind>), so a colon in the name breaks the round-trip — ParseKey, which the env-backed store uses to recover a secret's kind, would mis-split it. A control character has no place in a keychain item name and a keyring backend may refuse it. The check lives at the one door names enter through — `fft project add` — so Key can stay a pure join.
Types ¶
type MemStore ¶
type MemStore struct {
// contains filtered or unexported fields
}
MemStore keeps secrets in memory for the lifetime of the process. It backs the specs — which is why it is exported: they need MemStore.Snapshot to assert on exactly which keys were written.
func NewMem ¶
func NewMem() *MemStore
NewMem returns an in-memory Store. It is safe for concurrent use.
type Store ¶
type Store interface {
// Get returns the secret at key, or ErrNotFound if there is none.
Get(key string) (string, error)
// Set writes val to key, replacing any previous value.
Set(key, val string) error
// Delete removes key. Deleting a key that holds no secret is not an error.
Delete(key string) error
// Kind names the backing store for display: "keyring", "env", "file" or
// "memory".
Kind() string
}
Store keeps a secret per key. Keys are built with Key; a store must never interpret them.
func NewEnv ¶
NewEnv returns a read-only Store backed by the FFT_* environment variables. lookup may be nil, in which case os.LookupEnv is used.
func NewFile ¶
NewFile returns a Store backed by the JSON file at path. The file and its parent directory are created on first write, with modes 0600 and 0700.
func Open ¶
Open returns the Store implied by the environment.
The keychain is the default, because it is the only place a secret is protected by something other than a file mode. noKeyring (--no-keyring, or FFT_NO_KEYRING=1) selects the 0600 file fallback instead, for machines where there is no keychain to talk to.
The environment-backed store is not reachable from here: it belongs to the ephemeral project synthesized in headless mode, which the caller detects before it ever asks for a store.