Documentation
¶
Overview ¶
Package secrets stores small dejima-held secrets (webhook HMAC keys and the like) at rest in the OS keychain when one is available — the macOS login Keychain via `security`, or the Secret Service (libsecret) via `secret-tool` on Linux — and falls back to a 0600 file under ~/.dejima/secrets otherwise.
The fallback is deliberate, not a failure mode: a `--system` daemon starts at boot *before any login*, when the login keychain is still locked, so a keychain write/read can fail until someone logs in once. Rather than hard-fail (the trap the roadmap calls out — reporting "never configured" when the keychain is merely locked), every operation degrades to the file store. So a secret set while the keychain was locked lands in the file and is still readable; secrets set while unlocked live in the keychain.
Index ¶
- Constants
- Variables
- func Fingerprint(value string) string
- func Reserved(name string) bool
- func ValidateName(name string) error
- type IslandStore
- func (s *IslandStore) Backend() string
- func (s *IslandStore) Get(island, name string) (string, error)
- func (s *IslandStore) List(island string) ([]Meta, error)
- func (s *IslandStore) Names(island string) ([]string, error)
- func (s *IslandStore) Purge(island string) error
- func (s *IslandStore) Redact(island, text string) string
- func (s *IslandStore) Redactor(island string) func(string) string
- func (s *IslandStore) Remove(island, name string) error
- func (s *IslandStore) Set(island, name, value, setBy string) (Meta, error)
- func (s *IslandStore) Values(island string) (map[string]string, error)
- type Meta
- type Store
Constants ¶
const BackendEnvVar = "DEJIMA_SECRETS_BACKEND"
BackendEnvVar forces the storage backend. Set it to "file" to skip the OS keychain entirely.
Two real uses beyond preference: tests must not write into the operator's login Keychain (entries would survive the run and need manual cleanup), and an operator on a headless box may prefer the file store rather than a keychain that is locked more often than not.
Variables ¶
var ErrNotFound = errors.New("secret not found")
ErrNotFound is returned when a named secret isn't in the island's store.
Functions ¶
func Fingerprint ¶ added in v0.8.28
Fingerprint is the first 8 hex characters of the value's SHA-256.
func Reserved ¶ added in v0.8.28
Reserved reports whether name is rejected, without building an error — for callers that only need the predicate (e.g. UI hints).
func ValidateName ¶ added in v0.8.28
ValidateName reports whether name may be used as a secret. The error names the reason, since "rejected" without a why invites the operator to work around it.
Matching is CASE-INSENSITIVE. Environment variables are case-sensitive on Unix, but `http_proxy` and `HTTP_PROXY` are both honoured by common tooling, so a case-sensitive check would leave the lowercase spelling wide open.
Types ¶
type IslandStore ¶ added in v0.8.28
type IslandStore struct {
// contains filtered or unexported fields
}
IslandStore manages per-island secrets on top of the keychain-backed Store.
func OpenIsland ¶ added in v0.8.28
func OpenIsland() (*IslandStore, error)
OpenIsland builds an IslandStore over the platform keychain (with the file fallback the Store already provides).
func (*IslandStore) Backend ¶ added in v0.8.28
func (s *IslandStore) Backend() string
Backend names where values are actually kept, for diagnostics.
func (*IslandStore) Get ¶ added in v0.8.28
func (s *IslandStore) Get(island, name string) (string, error)
Get returns a secret's VALUE. Daemon-internal only — this is what materialization uses. It must never be reachable from an API handler; the value has no route outward by design.
func (*IslandStore) List ¶ added in v0.8.28
func (s *IslandStore) List(island string) ([]Meta, error)
List returns the island's secrets as metadata, name-sorted. Values are not included and cannot be — Meta has no field for one.
func (*IslandStore) Names ¶ added in v0.8.28
func (s *IslandStore) Names(island string) ([]string, error)
Names returns the island's secret names, sorted. Safe for an in-island caller: knowing WHICH secrets exist is useful to an agent and reveals nothing the environment wouldn't already.
func (*IslandStore) Purge ¶ added in v0.8.28
func (s *IslandStore) Purge(island string) error
Purge removes an island's secrets — values and metadata — so they never outlive the island they were scoped to.
func (*IslandStore) Redact ¶ added in v0.8.28
func (s *IslandStore) Redact(island, text string) string
Redact replaces this island's stored values in text with a named placeholder. Used to mask secrets in `dejima logs`, which is the likeliest real leak — a tool echoing its own configuration.
func (*IslandStore) Redactor ¶ added in v0.8.28
func (s *IslandStore) Redactor(island string) func(string) string
Redactor snapshots this island's values ONCE and returns a masking function.
Use it for streams. Redact re-reads the store on every call, which is fine for a one-shot string but wrong for a log follow that may run for hours: it would re-read the keychain per line, costing both throughput and (on macOS) a stream of keychain access prompts.
The snapshot means a secret set mid-stream isn't masked until the next call — acceptable, since it also wasn't in the environment of the process that wrote those lines. Returns identity when there's nothing to mask, so the common case costs nothing.
func (*IslandStore) Remove ¶ added in v0.8.28
func (s *IslandStore) Remove(island, name string) error
Remove deletes a secret. Removing one that isn't there is not an error — teardown paths shouldn't have to check first.
func (*IslandStore) Set ¶ added in v0.8.28
func (s *IslandStore) Set(island, name, value, setBy string) (Meta, error)
Set stores or rotates a secret.
Rotation preserves CreatedAt and bumps UpdatedAt, so the TUI can show both "added" and "last rotated" — rotation being the actual defence here, whether it's happening should be visible.
func (*IslandStore) Values ¶ added in v0.8.28
func (s *IslandStore) Values(island string) (map[string]string, error)
Values returns every name/value pair for materialization. Daemon-internal, same rule as Get.
A name present in metadata whose value can't be read is SKIPPED rather than failing the whole set: on a --system daemon the keychain may still be locked at boot, and one unreadable secret shouldn't stop an island from starting with the others.
type Meta ¶ added in v0.8.28
type Meta struct {
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
SetBy string `json:"set_by,omitempty"`
// Fingerprint is sha256(value)[:8]. Values are never shown after entry —
// not even a prefix, since leading bytes are often the identifying,
// high-entropy part and are exactly what leaks into screenshots. A
// fingerprint leaks nothing and still lets an operator confirm the stored
// secret matches their copy by hashing it locally.
Fingerprint string `json:"fingerprint"`
// RequireApproval is stored from v1 but NOT enforced: per-use approval needs
// brokered fetch (the agent must ask), which environment injection cannot
// observe. Persisting it now means that lands without a migration.
// See docs/secrets-manager-spec.md § Deferred.
RequireApproval bool `json:"require_approval,omitempty"`
}
Meta is everything about a secret EXCEPT its value. This is what the API, the TUI and the SDK see; there is deliberately no field for a value, so no code path can serialize one outward by accident.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store reads/writes secrets, preferring the OS keychain and falling back to a file. It's safe for concurrent use.
func Open ¶
Open builds a Store: the platform keychain backend if its CLI is on PATH, plus the always-available file fallback.
func (*Store) Backend ¶
Backend names the active keychain backend ("macos-keychain", "secret-service", or "file" when no keychain is usable) — for diagnostics.