blob: 8d19a3ef236bbbfb8960f19b48544d73d6a51c91 [file] [log] [blame]
// Copyright 2015 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secrets
import (
"bytes"
"context"
"errors"
)
var (
// ErrNoSuchSecret indicates the store can't find the requested secret.
ErrNoSuchSecret = errors.New("secret not found")
// ErrNoStoreConfigured indicates there's no Store in the context.
ErrNoStoreConfigured = errors.New("secrets.Store is not in the context")
)
var contextKey = "secrets.Store"
// Use installs a Store implementation into the context.
func Use(ctx context.Context, s Store) context.Context {
return context.WithValue(ctx, &contextKey, s)
}
// CurrentStore returns a store installed in the context or nil.
func CurrentStore(ctx context.Context) Store {
store, _ := ctx.Value(&contextKey).(Store)
return store
}
// RandomSecret returns a random secret using Store in the context.
//
// If the context doesn't have Store set, returns ErrNoStoreConfigured.
func RandomSecret(ctx context.Context, name string) (Secret, error) {
if store := CurrentStore(ctx); store != nil {
return store.RandomSecret(ctx, name)
}
return Secret{}, ErrNoStoreConfigured
}
// StoredSecret returns a stored secret using Store in the context.
//
// If the context doesn't have Store set, returns ErrNoStoreConfigured.
func StoredSecret(ctx context.Context, name string) (Secret, error) {
if store := CurrentStore(ctx); store != nil {
return store.StoredSecret(ctx, name)
}
return Secret{}, ErrNoStoreConfigured
}
// AddRotationHandler registers a callback called when the secret is updated.
//
// If the context doesn't have Store set, returns ErrNoStoreConfigured.
func AddRotationHandler(ctx context.Context, name string, cb RotationHandler) error {
if store := CurrentStore(ctx); store != nil {
return store.AddRotationHandler(ctx, name, cb)
}
return ErrNoStoreConfigured
}
// Store knows how to retrieve or autogenerate a secret given its name.
//
// See SecretManagerStore for a concrete implementation usually used in
// production.
type Store interface {
// RandomSecret returns a random secret given its name.
//
// The store will auto-generate the secret if necessary. Its value is
// a random high-entropy blob.
RandomSecret(ctx context.Context, name string) (Secret, error)
// StoredSecret returns a previously stored secret given its name.
//
// How it was stored depends on the concrete implementation of the Store. The
// difference from RandomSecret is that the Store will never try to
// auto-generate such secret if it is missing and will return ErrNoSuchSecret
// instead.
StoredSecret(ctx context.Context, name string) (Secret, error)
// AddRotationHandler registers a callback called when the secret is updated.
//
// Useful when a value of StoredSecret(...) is used to derive something else.
// The callback allows the store to notify the consumer of the secret when
// it changes.
AddRotationHandler(ctx context.Context, name string, cb RotationHandler) error
}
// RotationHandler is called from an internal goroutine after the store fetches
// a new version of a stored secret.
type RotationHandler func(context.Context, Secret)
// Secret represents multiple versions of some secret blob.
//
// There's a current version (which is always set) that should be used for all
// kinds of operations: active (like encryption, signing, etc) and passive
// (like decryption, checking signatures, etc).
//
// And there's zero or more other versions that should be used only for passive
// operations. Other versions contain previous or future values of the secret.
// They are important for implementing graceful rotation of the secret.
type Secret struct {
Active []byte // current value of the secret, always set
Passive [][]byte // optional list of other values, in no particular order
}
// Blobs returns the active version and all passive versions as one array.
func (s Secret) Blobs() [][]byte {
out := make([][]byte, 0, 1+len(s.Passive))
out = append(out, s.Active)
out = append(out, s.Passive...)
return out
}
// Equal returns true if secrets are equal.
//
// Does *not* run in constant time. Shouldn't be used in a cryptographic
// context due to susceptibility to timing attacks.
func (s Secret) Equal(a Secret) bool {
switch {
case len(s.Passive) != len(a.Passive):
return false
case !bytes.Equal(s.Active, a.Active):
return false
}
for i, blob := range s.Passive {
if !bytes.Equal(blob, a.Passive[i]) {
return false
}
}
return true
}