sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/config/secret/agent.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package secret implements an agent to read and reload the secrets. 18 package secret 19 20 import ( 21 "fmt" 22 "sync" 23 24 "github.com/sirupsen/logrus" 25 "k8s.io/apimachinery/pkg/util/sets" 26 27 "sigs.k8s.io/prow/pkg/logrusutil" 28 "sigs.k8s.io/prow/pkg/secretutil" 29 ) 30 31 // secretAgent is the singleton that loads secrets for us 32 var secretAgent *agent 33 34 func init() { 35 secretAgent = &agent{ 36 secretsMap: map[string]secretReloader{}, 37 ReloadingCensorer: secretutil.NewCensorer(), 38 } 39 logrus.SetFormatter(logrusutil.NewFormatterWithCensor(logrus.StandardLogger().Formatter, secretAgent.ReloadingCensorer)) 40 } 41 42 // Start creates goroutines to monitor the files that contain the secret value. 43 // Additionally, Start wraps the current standard logger formatter with a 44 // censoring formatter that removes secret occurrences from the logs. 45 func (a *agent) Start(paths []string) error { 46 a.secretsMap = make(map[string]secretReloader, len(paths)) 47 a.ReloadingCensorer = secretutil.NewCensorer() 48 49 for _, path := range paths { 50 if err := a.Add(path); err != nil { 51 return fmt.Errorf("failed to load secret at %s: %w", path, err) 52 } 53 } 54 55 logrus.SetFormatter(logrusutil.NewFormatterWithCensor(logrus.StandardLogger().Formatter, a.ReloadingCensorer)) 56 57 return nil 58 } 59 60 // Add registers a new path to the agent. 61 func Add(paths ...string) error { 62 for _, path := range paths { 63 if err := secretAgent.Add(path); err != nil { 64 return err 65 } 66 } 67 return nil 68 } 69 70 // AddWithParser registers a new path to the agent. The secret will only be updated if it can 71 // be successfully parsed. The returned getter must be kept, as it is the only way of accessing 72 // the typed secret. 73 func AddWithParser[T any](path string, parsingFN func([]byte) (T, error)) (func() T, error) { 74 loader := &parsingSecretReloader[T]{ 75 path: path, 76 parsingFN: parsingFN, 77 } 78 return loader.get, secretAgent.add(path, loader) 79 } 80 81 // GetSecret returns the value of a secret stored in a map. 82 func GetSecret(secretPath string) []byte { 83 return secretAgent.GetSecret(secretPath) 84 } 85 86 // GetTokenGenerator returns a function that gets the value of a given secret. 87 func GetTokenGenerator(secretPath string) func() []byte { 88 return func() []byte { 89 return GetSecret(secretPath) 90 } 91 } 92 93 func Censor(content []byte) []byte { 94 return secretAgent.Censor(content) 95 } 96 97 // agent watches a path and automatically loads the secrets stored. 98 type agent struct { 99 sync.RWMutex 100 secretsMap map[string]secretReloader 101 *secretutil.ReloadingCensorer 102 } 103 104 type secretReloader interface { 105 getRaw() []byte 106 start(reloadCensor func()) error 107 } 108 109 // Add registers a new path to the agent. 110 func (a *agent) Add(path string) error { 111 return a.add(path, &parsingSecretReloader[[]byte]{ 112 path: path, 113 parsingFN: func(b []byte) ([]byte, error) { return b, nil }, 114 }) 115 } 116 117 func (a *agent) add(path string, loader secretReloader) error { 118 if err := loader.start(a.refreshCensorer); err != nil { 119 return err 120 } 121 122 a.setSecret(path, loader) 123 124 return nil 125 } 126 127 // GetSecret returns the value of a secret stored in a map. 128 func (a *agent) GetSecret(secretPath string) []byte { 129 a.RLock() 130 defer a.RUnlock() 131 if val, set := a.secretsMap[secretPath]; set { 132 return val.getRaw() 133 } 134 return nil 135 } 136 137 // setSecret sets a value in a map of secrets. 138 func (a *agent) setSecret(secretPath string, secretValue secretReloader) { 139 a.Lock() 140 a.secretsMap[secretPath] = secretValue 141 a.Unlock() 142 a.refreshCensorer() 143 } 144 145 // refreshCensorer should be called when the secrets map changes 146 func (a *agent) refreshCensorer() { 147 var secrets [][]byte 148 a.RLock() 149 for _, value := range a.secretsMap { 150 secrets = append(secrets, value.getRaw()) 151 } 152 a.RUnlock() 153 a.ReloadingCensorer.RefreshBytes(secrets...) 154 } 155 156 // GetTokenGenerator returns a function that gets the value of a given secret. 157 func (a *agent) GetTokenGenerator(secretPath string) func() []byte { 158 return func() []byte { 159 return a.GetSecret(secretPath) 160 } 161 } 162 163 // Censor replaces sensitive parts of the content with a placeholder. 164 func (a *agent) Censor(content []byte) []byte { 165 a.RLock() 166 defer a.RUnlock() 167 if a.ReloadingCensorer == nil { 168 // there's no constructor for an agent so we can't ensure that everyone is 169 // trying to censor *after* actually loading a secret ... 170 return content 171 } 172 return secretutil.AdaptCensorer(a.ReloadingCensorer)(content) 173 } 174 175 func (a *agent) getSecrets() sets.Set[string] { 176 a.RLock() 177 defer a.RUnlock() 178 secrets := sets.New[string]() 179 for _, v := range a.secretsMap { 180 secrets.Insert(string(v.getRaw())) 181 } 182 return secrets 183 }