go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/secrets/secrets.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package secrets 16 17 import ( 18 "bytes" 19 "context" 20 "errors" 21 ) 22 23 var ( 24 // ErrNoSuchSecret indicates the store can't find the requested secret. 25 ErrNoSuchSecret = errors.New("secret not found") 26 // ErrNoStoreConfigured indicates there's no Store in the context. 27 ErrNoStoreConfigured = errors.New("secrets.Store is not in the context") 28 ) 29 30 var contextKey = "secrets.Store" 31 32 // Use installs a Store implementation into the context. 33 func Use(ctx context.Context, s Store) context.Context { 34 return context.WithValue(ctx, &contextKey, s) 35 } 36 37 // CurrentStore returns a store installed in the context or nil. 38 func CurrentStore(ctx context.Context) Store { 39 store, _ := ctx.Value(&contextKey).(Store) 40 return store 41 } 42 43 // RandomSecret returns a random secret using Store in the context. 44 // 45 // If the context doesn't have Store set, returns ErrNoStoreConfigured. 46 func RandomSecret(ctx context.Context, name string) (Secret, error) { 47 if store := CurrentStore(ctx); store != nil { 48 return store.RandomSecret(ctx, name) 49 } 50 return Secret{}, ErrNoStoreConfigured 51 } 52 53 // StoredSecret returns a stored secret using Store in the context. 54 // 55 // If the context doesn't have Store set, returns ErrNoStoreConfigured. 56 func StoredSecret(ctx context.Context, name string) (Secret, error) { 57 if store := CurrentStore(ctx); store != nil { 58 return store.StoredSecret(ctx, name) 59 } 60 return Secret{}, ErrNoStoreConfigured 61 } 62 63 // AddRotationHandler registers a callback called when the secret is updated. 64 // 65 // If the context doesn't have Store set, returns ErrNoStoreConfigured. 66 func AddRotationHandler(ctx context.Context, name string, cb RotationHandler) error { 67 if store := CurrentStore(ctx); store != nil { 68 return store.AddRotationHandler(ctx, name, cb) 69 } 70 return ErrNoStoreConfigured 71 } 72 73 // Store knows how to retrieve or autogenerate a secret given its name. 74 // 75 // See SecretManagerStore for a concrete implementation usually used in 76 // production. 77 type Store interface { 78 // RandomSecret returns a random secret given its name. 79 // 80 // The store will auto-generate the secret if necessary. Its value is 81 // a random high-entropy blob. 82 RandomSecret(ctx context.Context, name string) (Secret, error) 83 84 // StoredSecret returns a previously stored secret given its name. 85 // 86 // How it was stored depends on the concrete implementation of the Store. The 87 // difference from RandomSecret is that the Store will never try to 88 // auto-generate such secret if it is missing and will return ErrNoSuchSecret 89 // instead. 90 StoredSecret(ctx context.Context, name string) (Secret, error) 91 92 // AddRotationHandler registers a callback called when the secret is updated. 93 // 94 // Useful when a value of StoredSecret(...) is used to derive something else. 95 // The callback allows the store to notify the consumer of the secret when 96 // it changes. 97 AddRotationHandler(ctx context.Context, name string, cb RotationHandler) error 98 } 99 100 // RotationHandler is called from an internal goroutine after the store fetches 101 // a new version of a stored secret. 102 type RotationHandler func(context.Context, Secret) 103 104 // Secret represents multiple versions of some secret blob. 105 // 106 // There's a current version (which is always set) that should be used for all 107 // kinds of operations: active (like encryption, signing, etc) and passive 108 // (like decryption, checking signatures, etc). 109 // 110 // And there's zero or more other versions that should be used only for passive 111 // operations. Other versions contain previous or future values of the secret. 112 // They are important for implementing graceful rotation of the secret. 113 type Secret struct { 114 Active []byte // current value of the secret, always set 115 Passive [][]byte // optional list of other values, in no particular order 116 } 117 118 // Blobs returns the active version and all passive versions as one array. 119 func (s Secret) Blobs() [][]byte { 120 out := make([][]byte, 0, 1+len(s.Passive)) 121 out = append(out, s.Active) 122 out = append(out, s.Passive...) 123 return out 124 } 125 126 // Equal returns true if secrets are equal. 127 // 128 // Does *not* run in constant time. Shouldn't be used in a cryptographic 129 // context due to susceptibility to timing attacks. 130 func (s Secret) Equal(a Secret) bool { 131 switch { 132 case len(s.Passive) != len(a.Passive): 133 return false 134 case !bytes.Equal(s.Active, a.Active): 135 return false 136 } 137 for i, blob := range s.Passive { 138 if !bytes.Equal(blob, a.Passive[i]) { 139 return false 140 } 141 } 142 return true 143 }