Documentation
¶
Overview ¶
Package setec is a client library to access and manage secrets stored remotely in a secret management service.
Index ¶
- Variables
- type Cache
- type Client
- func (c Client) Activate(ctx context.Context, name string, version api.SecretVersion) error
- func (c Client) Delete(ctx context.Context, name string) error
- func (c Client) DeleteVersion(ctx context.Context, name string, version api.SecretVersion) error
- func (c Client) Get(ctx context.Context, name string) (*api.SecretValue, error)
- func (c Client) GetIfChanged(ctx context.Context, name string, oldVersion api.SecretVersion) (*api.SecretValue, error)
- func (c Client) GetVersion(ctx context.Context, name string, version api.SecretVersion) (*api.SecretValue, error)
- func (c Client) Info(ctx context.Context, name string) (*api.SecretInfo, error)
- func (c Client) List(ctx context.Context) ([]*api.SecretInfo, error)
- func (c Client) Put(ctx context.Context, name string, value []byte) (version api.SecretVersion, err error)
- type Fields
- type FileCache
- type FileClient
- type MemCache
- type Secret
- type Store
- type StoreClient
- type StoreConfig
- type Struct
- type Ticker
- type Updater
Constants ¶
This section is empty.
Variables ¶
var ErrNoFields = errors.New("no setec-tagged fields")
ErrNoFields is a sentinel error reported by ParseFields when its argument is a valid pointer to a struct, but does not contain any matching setec-tagged fields. The caller should use errors.Is to check for this error.
Functions ¶
This section is empty.
Types ¶
type Cache ¶
type Cache interface { // Write persists the given bytes for future retrieval. // // The data written to a Cache by the Store are a JSON object having the // following structure: // // { // "secret-name": { // "secret": {"Value": <bytes>, "Version": <version>}, // "lastAccess": <last-access-time>, // }, // ... // } // // The "secret" field is an api.SecretValue for the latest known value // obtained from the service. The "lastAccess" field is a Unix epoch // timestamp in seconds for the last time this secret name was requested // from the store, used to expire stale undeclared secrets. Write([]byte) error // Read returns previously persisted bytes, if any are available. If the // cache is empty, Read must report an empty slice or nil without error. Read() ([]byte, error) }
Cache is an interface that lets a Store persist one piece of data locally. The persistence need not be perfect (i.e., it's okay to lose previously written data).
type Client ¶
type Client struct { // Server is the URL of the secrets server to talk to. Server string // DoHTTP is the function to use to make HTTP requests. If nil, // http.DefaultClient.Do is used. DoHTTP func(*http.Request) (*http.Response, error) }
Client is a raw client to the secret management server. If you're just consuming secrets, you probably want to use a Store instead.
func (Client) Activate ¶
Activate changes the active version of the secret called name to version.
Access requirement: "activate"
func (Client) Delete ¶
Delete deletes all versions of the named secret.
Note: Delete will delete all versions of the secret, including the active one, if the caller has permission to do so.
Access requirement: "delete"
func (Client) DeleteVersion ¶
DeleteVersion deletes the specified version of the named secret.
Note: DeleteVersion will report an error if the caller attempts to delete the active version, even if they have permission to do so.
Access requirement: "delete"
func (Client) GetIfChanged ¶
func (c Client) GetIfChanged(ctx context.Context, name string, oldVersion api.SecretVersion) (*api.SecretValue, error)
GetIfChanged fetches a secret value by name, if the active version on the server is different from oldVersion. If the active version on the server is the same as oldVersion, it reports api.ErrValueNotChanged without returning a secret. As a special case, if oldVersion == 0 then GetIfVersion behaves as Get and retrieves the current active version.
Access requirement: "get"
func (Client) GetVersion ¶
func (c Client) GetVersion(ctx context.Context, name string, version api.SecretVersion) (*api.SecretValue, error)
GetVersion fetches a secret value by name and version. If version == 0, GetVersion retrieves the current active version.
Access requirement: "get"
type Fields ¶
type Fields struct {
// contains filtered or unexported fields
}
Fields is a helper for plumbing secrets to the fields of struct values. The ParseFields function recognizes fields of a struct with a tag like:
setec:"base-secret-name[,json]"
The resulting Fields value fetches the secrets identified by these tags from a setec.Store, and injects their values into the fields.
Background ¶
A program that uses multiple secret values has to plumb those secrets down to the code that needs them. One way to manage this is to bundle the secrets together into fields of a struct, and to make that struct accessible via a shared configuration library or through a context argument.
To simplify the manual process of adding fields and hooking them up to the secrets service, the Fields type uses reflection to discover the names of secrets declared via struct tags, and to handle the boilerplate of plumbing secret values to those fields.
Basic usage ¶
Populate the Structs field of the StoreConfig with pointers to the struct values to be populated. You may optionally provide a prefix to prepend to secret names, so that it can use different secrets in different environments (for example dev vs. prod):
st, err := setec.NewStore(ctx, setec.StoreConfig{ Client: client, Structs: []setec.Struct{{Value: &v, Prefix: "dev/program-name"}}, }) // ...
Once the store is ready, the secret values are automatically copied to the corresponding fields. It is also possible to explicitly populate struct fields after the store is constructed, see ParseFields. The store must be constructed with the AllowLookup option enabled to add new secrets after the store has been constructed.
Field Types ¶
The Fields type can handle struct fields of the following types:
A field of type []byte receives a copy of the secret value.
A field of type string receives a copy of the secret as a string.
A field of type setec.Secret is populated with a handle to the secret.
A field whose (pointer) type implements the encoding.BinaryUnmarshaler interface has its UnmarshalBinary method called with the secret value. This may be used to handle structured data, or to add validation.
In addition, a field may have any type that supports JSON encoding, provided the secret value is also encoded as JSON, if its tag includes the optional "json" verb. For example, given:
type Key struct { Salt []byte `json:"iv"` Data []byte `json:"data"` }
the following is a valid field declaration:
SecretKey Key `setec:"secret-key,json"` // note "json" verb
and accepts a secret value formatted like:
{"iv":"aGVsbG8sIHdvcmxk","data":"c3VwZXIgc2VjcmV0IHNxdWlycmVsIHN0dWZm"}
The ParseFields function will report an error for a tagged field whose type does not fit within these constraints.
func ParseFields ¶
ParseFields parses information about setec-tagged fields from v. The concrete type of v must be a pointer to a struct value with at least one tagged field. The namePrefix, if non-empty, is joined to the front of each tagged name, separated with a slash ("/").
See Fields for a description of the struct tags and types recognized.
func (*Fields) Apply ¶
Apply fetches and applies the secret values required by f to the corresponding fields of the input struct. Each secret must either be known to s at initialization, or s must be configured to allow lookups. Apply will attempt to process all tagged fields before reporting an error.
Note: When applying secrets to struct fields from an existing Store, the AllowLookup option of the Store must be enabled, or else Apply will report an error for any field that refers to a secret not already available.
type FileCache ¶
type FileCache string
FileCache is an implementation of the Cache interface that stores a value in a file at the specified path.
func NewFileCache ¶
NewFileCache constructs a new file cache associated with the specified path. The cache file is not created, but an error is reported if the enclosing directory cannot be created, or if the path exists but is not a plain file.
type FileClient ¶
type FileClient struct {
// contains filtered or unexported fields
}
FileClient is an implementation of the StoreClient interface that vends secrets from a static collection of data stored locally on disk.
This is intended for use in bootstrapping and deployments without access to a separate secrets server.
func NewFileClient ¶
func NewFileClient(path string) (*FileClient, error)
NewFileClient constructs a new FileClient using the contents of the specified local file path. The file must contain a JSON object having the following structure:
{ "secret-name-1": { "secret": {"Value": "b3BlbiBzZXNhbWU=", "Version": 1} }, "secret-name-2": { "secret": {"TextValue": "xyzzy", "Version": 5} }, ... }
The secret values are encoded either as base64 strings ("Value") or as plain text ("TextValue"). A cache file written out by a FileCache can also be used as input. Unlike a cache, however, a FileClient only reads the file once, and subsequent modifications of the file are not observed.
func (*FileClient) Get ¶
func (fc *FileClient) Get(_ context.Context, name string) (*api.SecretValue, error)
Get implements the corresponding method of StoreClient.
func (*FileClient) GetIfChanged ¶
func (fc *FileClient) GetIfChanged(_ context.Context, name string, oldVersion api.SecretVersion) (*api.SecretValue, error)
GetIfChanged implements the corresponding method of StoreClient.
type MemCache ¶
type MemCache struct {
// contains filtered or unexported fields
}
MemCache is a trivial implementation of the Cache interface that stores a value in a byte slice. This is intended for use in testing. The methods of a MemCache never report an error.
func NewMemCache ¶
NewMemCache constructs a new memory cache whose initial contents are s.
type Secret ¶
type Secret func() []byte
A Secret is a function that fetches the current active value of a secret. The caller should not cache the value returned; the function does not block and will always report a valid (if possibly stale) result.
The Secret retains ownership of the bytes returned, but the store will never modify the contents of the secret, so it is safe to share the slice without copying as long as the caller does not modify them.
func StaticFile ¶
StaticFile returns a Secret that vends the contents of path. The contents of the file are returned exactly as stored.
This is useful as a placeholder for development, migration, and testing. The value reported by this secret is the contents of path at the time this function is called, and never changes.
func StaticSecret ¶
StaticSecret returns a Secret that vends a static string value. This is useful as a placeholder for development, migration, and testing. The value reported by a static secret never changes.
func StaticTextFile ¶
StaticTextFile returns a secret that vends the contents of path, which are treated as text with leading and trailing whitespace trimmed.
This is useful as a placeholder for development, migration, and testing. The value reported by a static secret never changes.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is a store that provides named secrets.
func NewStore ¶
func NewStore(ctx context.Context, cfg StoreConfig) (*Store, error)
NewStore creates a secret store with the given configuration. The service URL of the client must be set.
NewStore blocks until all the secrets named in cfg.Secrets are available for retrieval by the Secret method, or ctx ends. The context passed to NewStore is only used for initializing the store. If a cache is provided, cached values are accepted even if stale, as long as there is a value for each of the secrets in cfg.
func (*Store) LookupSecret ¶
LookupSecret returns a fetcher for the named secret. If name is already known by s, this is equivalent to Secret; otherwise, s attempts to fetch the latest active version of the secret from the service and either adds it to the collection or reports an error. LookupSecret does not automatically retry in case of errors.
func (*Store) Metrics ¶
Metrics returns a map of metrics for s. The caller is responsible for publishing the map to the metrics exporter.
func (*Store) Refresh ¶
Refresh synchronously checks for new versions of all the secrets currently known by s. It blocks until the refresh is complete or until ctx ends.
Updates are managed automatically when a Store is created and by the polling mechanism, but a caller may invoke Refresh directly if it wants to check for new secret values at a specific moment.
type StoreClient ¶
type StoreClient interface { // Get fetches the current active secret value for name. See [Client.Get]. Get(ctx context.Context, name string) (*api.SecretValue, error) // GetIfChanged fetches a secret value by name, if the active version on the // server is different from oldVersion. See [Client.GetIfChanged]. GetIfChanged(ctx context.Context, name string, oldVersion api.SecretVersion) (*api.SecretValue, error) }
StoreClient is the interface to the setec API used by the Store.
type StoreConfig ¶
type StoreConfig struct { // Client is the API client used to fetch secrets from the service. // The service URL must be non-empty. Client StoreClient // Secrets are the names of secrets this Store should retrieve. // // Unless AllowLookup is true, only secrets named here or in the Structs // field can be read out of the store and an error is reported if no secrets // are listed. Secrets []string // Structs are optional struct values with tagged fields that should be // populated with secrets from the store at initialization time. // // Unless AllowLookup is true, only secrets named here or in the Secrets // field can be read out of the store and an error is reported if no secrets // are listed. Structs []Struct // AllowLookup instructs the store to allow the caller to look up secrets // not known to the store at the time of construction. If false, only // secrets pre-declared in the Secrets and Structs slices can be fetched, // and the Lookup method will report an error for all un-listed secrets. AllowLookup bool // Cache, if non-nil, is a cache that persists secrets locally. // // Depending on the implementation, local caching may degrade security // slightly by making secrets easier to get at, but in return allows the // Store to initialize and run during outages of the secrets management // service. // // If no cache is provided, the Store caches secrets in-memory for the // lifetime of the process only. Cache Cache // PollInterval is the interval at which the store will poll the service for // updated secret values. If zero, a default value is used. If negative, the // store does not automatically poll and the caller must explicitly call the // Refresh method to effect an update. // // This field is ignored if PollTicker is set. PollInterval time.Duration // ExpiryAge is a duration beyond which undeclared secrets that have not // been accessed in that time are eligible for expiration from the cache. // A zero value means secrets do not expire. ExpiryAge time.Duration // Logf is a logging function where text logs should be sent. If nil, logs // are written to the standard log package. Logf logger.Logf // PollTicker, if set is a ticker that is used to control the scheduling of // update polls. If nil, a time.Ticker is used based on the PollInterval. PollTicker Ticker // TimeNow, if set, is a function that reports a Time to be treated as the // current wallclock time. If nil, time.Now is used. TimeNow func() time.Time }
StoreConfig is the configuration for Store.
type Struct ¶
type Struct struct { // Value must be a non-nil pointer to a value of struct type, having at // least one field tagged with a "setec" field tag. // See Fields for a description of the tag format. Value any // Prefix is an optional prefix that should be prepended to each secret // described by a tag in Value to obtain the secret name to look up. Prefix string }
Struct describes a struct value with tagged fields that should be populated with secrets from a Store.
type Ticker ¶
type Ticker interface { // Chan returns a channel upon which time values are delivered to signal // that a poll is required. Chan() <-chan time.Time // Stop signals that the ticker should stop and deliver no more values. Stop() // Done is invoked when a signaled poll is complete. Done() }
A Ticker is used to inject time control in to the polling loop of a store.
type Updater ¶
type Updater[T any] struct { // contains filtered or unexported fields }
An Updater tracks a value whose state depends on a secret. It watches for updates to the secret, and invokes a caller-provided function to update the value when a new version of the secret is delivered.
func NewUpdater ¶
func NewUpdater[T any](ctx context.Context, s *Store, name string, newValue func([]byte) (T, error)) (*Updater[T], error)
NewUpdater creates a new Updater that maintains a value based on the specified secret in s. The newValue function constructs a value of type T from the bytes of a secret.
The initial value is constructed using newValue on the current secret version when NewUpdater is called. If this initial call reports an error, NewUpdater returns nil and that error. Otherwise, the Updater begins with that value.
Once constructed, call the Get method to fetch the current value. It is safe to call Get concurrently from multiple goroutines. See Updater.Get for details of how updates are handled.
If s has lookups enabled, NewWatcher will attempt to look up name if it is not already declared in s. If lookups are not enabled, or of the secret is not found, NewUpdater reports an error. It does not retry in case of lookup errors.
func StaticUpdater ¶
StaticUpdater returns an Updater that vends the specified fixed value. The value reported by the updater never changes.
func (*Updater[T]) Get ¶
func (u *Updater[T]) Get() T
Get fetches the current value of u, first updating it if the secret has changed. It is safe to call Get concurrently from multiple goroutines.
If Get receives an error while trying to update u, it returns the previous value. Use the Err method to check for an update error. If T implements the io.Closer interface, Get calls Close on the old value before updating.