Documentation
¶
Overview ¶
Package secrets resolves the secrets a service document declares, by name.
A document never holds a secret: it names one (`valkey.passwordSecret: valkey/password`), and `secrets.source` says how a name is delivered (truvity/policy docs/contracts/config.md, section 5):
env the variable SLUIS_SECRET_<NAME>, the name upper-cased with every
character that is not a letter or a digit made an underscore:
`valkey/password` is SLUIS_SECRET_VALKEY_PASSWORD. For a local run.
file the file <root>/<name>, read on every use, so a rotated Secret
mounted by the platform takes effect without a restart.
ssm the SecureString <root>/internal/config/<name> of AWS SSM Parameter
Store (root `/sluis/<instance>`): a name is read by itself,
decrypted, when it is first asked for (nothing is read at
start), and read again when its copy is older than the refresh
(one minute), so a rotation reaches a function within that.
The names are the layout's (docs/decisions/0041): `clients/<id>/secret`, `providers/google/<id>/client-id` and `client-secret`, `issuer/state-secret`, `recovery/password`, `directory/<id>/key`, `valkey/password`. Nothing here logs or returns a value in an error.
Index ¶
- Constants
- Variables
- func Check(name string) error
- func CheckEnvNames(names []string) error
- func CheckRoot(root string) error
- func ClientSecret(clientID string) string
- func EnvName(name string) string
- func Named(f *config.Serve) []string
- func ParseStateSecret(raw []byte) ([]byte, error)
- func ProviderClientID(provider string) string
- func ProviderClientSecret(provider string) string
- type Env
- type File
- type Legacy
- type Location
- type ParametersAPI
- type SSM
- type Source
Constants ¶
const ( KindEnv = "env" KindFile = "file" KindSSM = "ssm" )
The kinds of source, as `secrets.source` spells them.
const ConfigPrefix = "/internal/config/"
ConfigPrefix is where, under an installation's root, the secrets a document names live: `<root>/internal/config/<name>` (ADR 0041).
const DefaultMaxStale = 24 * time.Hour
DefaultMaxStale is how old a copy may grow while every read again fails before the source fails closed: an instance whose read access was revoked stops serving the secrets it read, a day later at the latest.
const DefaultRefresh = time.Minute
DefaultRefresh is how old the SSM source's copy of one secret may be before it is read again: a rotated secret reaches every instance within it.
const EnvPrefix = "SLUIS_SECRET_"
EnvPrefix begins every variable the env source reads.
Variables ¶
var ErrNotFound = errors.New("secrets: not delivered")
ErrNotFound is a name the source does not deliver.
Functions ¶
func Check ¶
Check refuses a name that is not one: segments of letters, digits, '.', '_' and '-', separated by '/', none empty and none a dot or two.
func CheckEnvNames ¶
CheckEnvNames refuses names two of which the env source would read from one variable (`a/b` and `a-b` are both SLUIS_SECRET_A_B): one would silently be the other's secret.
func CheckRoot ¶
CheckRoot refuses an SSM root that would nest under another installation's tree, or under the old /sluis/private and /sluis/export: no segment of it may be `private`, `export`, `internal` or `external`.
func ClientSecret ¶
ClientSecret is a confidential client's secret.
func ParseStateSecret ¶ added in v1.75.0
ParseStateSecret reads a state secret: one trailing newline trimmed, then hex or base64 for at least 32 bytes that look random. A file of one repeated character, or of a few distinct bytes, is a placeholder and not a secret, and is refused.
func ProviderClientID ¶
ProviderClientID and ProviderClientSecret are a Google OAuth client's halves.
func ProviderClientSecret ¶
ProviderClientSecret is a Google OAuth client's secret.
Types ¶
type File ¶
type File struct{ Root string }
File reads <Root>/<name> on every use, so a rotated mount is seen at once.
type Legacy ¶
Legacy is what a v1 document named, secret by secret, in front of another source: a v1 document keeps reading the variables and the files it named until it is moved to v2. Dir, when set, holds one file per client (v1's `clientSecretsDir`).
type Location ¶
Location is where a converted v1 document said one secret was: a variable or a file.
type ParametersAPI ¶
type ParametersAPI interface {
GetParameter(ctx context.Context, in *awsssm.GetParameterInput, opts ...func(*awsssm.Options)) (*awsssm.GetParameterOutput, error)
}
ParametersAPI is the part of the SSM client the source calls.
type SSM ¶
type SSM struct {
API ParametersAPI
Root string
Refresh time.Duration
// MaxStale is how old a copy may grow while reads fail. Zero is
// DefaultMaxStale.
MaxStale time.Duration
// Now is the clock; nil is time.Now.
Now func() time.Time
// contains filtered or unexported fields
}
SSM reads one SecureString at a time, <Root>/internal/config/<name>, decrypted, when a name is first asked for, and again when its copy is older than Refresh. Nothing is read when the source is built, and a name nobody asks for is never read.
A read again happens outside any lock, by one caller at a time per name; everybody else asking for that name is answered from the copy meanwhile, or waits for the read when there is none. A read that fails keeps the copy, and the next is not tried before retryAfter. An absent name is remembered as absent for retryAfter. A copy older than MaxStale with every read again failing is not served: the source fails closed.
type Source ¶
type Source interface {
// Get returns the secret, or an error wrapping ErrNotFound when the
// source has none by that name. The value is never in an error.
Get(ctx context.Context, name string) (string, error)
// Describe says where a name is read from, for a log line: never a
// value.
Describe(name string) string
}
Source delivers secrets by name.
func Open ¶
Open builds the source a serve document's `secrets` names: env when it names none. A document converted from v1 reads, in front of it, every secret where v1 said it was. more are names the document does not hold that are read too (each confidential client's): with the env source no two names may share a variable.