github.com/influxdata/telegraf@v1.30.3/config/secret.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "log" 6 "regexp" 7 "strings" 8 "sync/atomic" 9 10 "github.com/influxdata/telegraf" 11 ) 12 13 // unlinkedSecrets contains the list of secrets that contain 14 // references not yet linked to their corresponding secret-store. 15 // Those secrets must later (after reading the config) be linked 16 // by the config to their respective secret-stores. 17 // Secrets containing constant strings will not be found in this 18 // list. 19 var unlinkedSecrets = make([]*Secret, 0) 20 21 // secretStorePattern is a regex to validate secret-store IDs 22 var secretStorePattern = regexp.MustCompile(`^\w+$`) 23 24 // secretPattern is a regex to extract references to secrets store in a secret-store 25 var secretPattern = regexp.MustCompile(`@\{(\w+:\w+)\}`) 26 27 // secretCandidatePattern is a regex to find secret candidates to warn users on invalid characters in references 28 var secretCandidatePattern = regexp.MustCompile(`@\{.+?:.+?}`) 29 30 // secretCount is the number of secrets use in Telegraf 31 var secretCount atomic.Int64 32 33 // selectedImpl is the configured implementation for secrets 34 var selectedImpl secretImpl = &protectedSecretImpl{} 35 36 // secretImpl represents an abstraction for different implementations of secrets 37 type secretImpl interface { 38 Container(secret []byte) secretContainer 39 EmptyBuffer() SecretBuffer 40 Wipe(secret []byte) 41 } 42 43 func EnableSecretProtection() { 44 selectedImpl = &protectedSecretImpl{} 45 } 46 47 func DisableSecretProtection() { 48 selectedImpl = &unprotectedSecretImpl{} 49 } 50 51 // secretContainer represents an abstraction of the container holding the 52 // actual secret value 53 type secretContainer interface { 54 Destroy() 55 Equals(ref []byte) (bool, error) 56 Buffer() (SecretBuffer, error) 57 AsBuffer(secret []byte) SecretBuffer 58 Replace(secret []byte) 59 } 60 61 // SecretBuffer allows to access the content of the secret 62 type SecretBuffer interface { 63 // Size returns the length of the buffer content 64 Size() int 65 // Grow will grow the capacity of the underlying buffer to the given size 66 Grow(capacity int) 67 // Bytes returns the content of the buffer as bytes. 68 // NOTE: The returned bytes shall NOT be accessed after destroying the 69 // buffer using 'Destroy()' as the underlying the memory area might be 70 // wiped and invalid. 71 Bytes() []byte 72 // TemporaryString returns the content of the buffer as a string. 73 // NOTE: The returned String shall NOT be accessed after destroying the 74 // buffer using 'Destroy()' as the underlying the memory area might be 75 // wiped and invalid. 76 TemporaryString() string 77 // String returns a copy of the underlying buffer's content as string. 78 // It is safe to use the returned value after destroying the buffer. 79 String() string 80 // Destroy will wipe the buffer's content and destroy the underlying 81 // buffer. Do not access the buffer after destroying it. 82 Destroy() 83 } 84 85 // Secret safely stores sensitive data such as a password or token 86 type Secret struct { 87 // container is the implementation for holding the secret. It can be 88 // protected or not depending on the concrete implementation. 89 container secretContainer 90 91 // resolvers are the functions for resolving a given secret-id (key) 92 resolvers map[string]telegraf.ResolveFunc 93 94 // unlinked contains all references in the secret that are not yet 95 // linked to the corresponding secret store. 96 unlinked []string 97 98 // notempty denotes if the secret is completely empty 99 notempty bool 100 } 101 102 // NewSecret creates a new secret from the given bytes 103 func NewSecret(b []byte) Secret { 104 s := Secret{} 105 s.init(b) 106 return s 107 } 108 109 // UnmarshalText creates a secret from a toml value following the "string" rule. 110 func (s *Secret) UnmarshalText(b []byte) error { 111 // Unmarshal secret from TOML and put it into protected memory 112 s.init(b) 113 114 // Keep track of secrets that contain references to secret-stores 115 // for later resolving by the config. 116 if len(s.unlinked) > 0 && s.notempty { 117 unlinkedSecrets = append(unlinkedSecrets, s) 118 } 119 120 return nil 121 } 122 123 // Initialize the secret content 124 func (s *Secret) init(secret []byte) { 125 // Keep track of the number of secrets... 126 secretCount.Add(1) 127 128 // Remember if the secret is completely empty 129 s.notempty = len(secret) != 0 130 131 // Find all secret candidates and check if they are really a valid 132 // reference. Otherwise issue a warning to let the user know that there is 133 // a potential issue with their secret instead of silently ignoring it. 134 candidates := secretCandidatePattern.FindAllString(string(secret), -1) 135 s.unlinked = make([]string, 0, len(candidates)) 136 for _, c := range candidates { 137 if secretPattern.MatchString(c) { 138 s.unlinked = append(s.unlinked, c) 139 } else { 140 log.Printf("W! Secret %q contains invalid character(s), only letters, digits and underscores are allowed.", c) 141 } 142 } 143 s.resolvers = nil 144 145 // Setup the container implementation 146 s.container = selectedImpl.Container(secret) 147 } 148 149 // Destroy the secret content 150 func (s *Secret) Destroy() { 151 s.resolvers = nil 152 s.unlinked = nil 153 s.notempty = false 154 155 if s.container != nil { 156 s.container.Destroy() 157 s.container = nil 158 159 // Keep track of the number of used secrets... 160 secretCount.Add(-1) 161 } 162 } 163 164 // Empty return if the secret is completely empty 165 func (s *Secret) Empty() bool { 166 return !s.notempty 167 } 168 169 // EqualTo performs a constant-time comparison of the secret to the given reference 170 func (s *Secret) EqualTo(ref []byte) (bool, error) { 171 if s.container == nil { 172 return false, nil 173 } 174 175 if len(s.unlinked) > 0 { 176 return false, fmt.Errorf("unlinked parts in secret: %v", strings.Join(s.unlinked, ";")) 177 } 178 179 return s.container.Equals(ref) 180 } 181 182 // Get return the string representation of the secret 183 func (s *Secret) Get() (SecretBuffer, error) { 184 if s.container == nil { 185 return selectedImpl.EmptyBuffer(), nil 186 } 187 188 if len(s.unlinked) > 0 { 189 return nil, fmt.Errorf("unlinked parts in secret: %v", strings.Join(s.unlinked, ";")) 190 } 191 192 // Decrypt the secret so we can return it 193 buffer, err := s.container.Buffer() 194 if err != nil { 195 return nil, err 196 } 197 198 // We've got a static secret so simply return the buffer 199 if len(s.resolvers) == 0 { 200 return buffer, nil 201 } 202 defer buffer.Destroy() 203 204 replaceErrs := make([]string, 0) 205 newsecret := secretPattern.ReplaceAllFunc(buffer.Bytes(), func(match []byte) []byte { 206 resolver, found := s.resolvers[string(match)] 207 if !found { 208 replaceErrs = append(replaceErrs, fmt.Sprintf("no resolver for %q", match)) 209 return match 210 } 211 replacement, _, err := resolver() 212 if err != nil { 213 replaceErrs = append(replaceErrs, fmt.Sprintf("resolving %q failed: %v", match, err)) 214 return match 215 } 216 217 return replacement 218 }) 219 if len(replaceErrs) > 0 { 220 selectedImpl.Wipe(newsecret) 221 return nil, fmt.Errorf("replacing secrets failed: %s", strings.Join(replaceErrs, ";")) 222 } 223 224 return s.container.AsBuffer(newsecret), nil 225 } 226 227 // Set overwrites the secret's value with a new one. Please note, the secret 228 // is not linked again, so only references to secret-stores can be used, e.g. by 229 // adding more clear-text or reordering secrets. 230 func (s *Secret) Set(value []byte) error { 231 // Link the new value can be resolved 232 secret, res, replaceErrs := resolve(value, s.resolvers) 233 if len(replaceErrs) > 0 { 234 return fmt.Errorf("linking new secrets failed: %s", strings.Join(replaceErrs, ";")) 235 } 236 237 // Set the new secret 238 s.container.Replace(secret) 239 s.resolvers = res 240 s.notempty = len(value) > 0 241 242 return nil 243 } 244 245 // GetUnlinked return the parts of the secret that is not yet linked to a resolver 246 func (s *Secret) GetUnlinked() []string { 247 return s.unlinked 248 } 249 250 // Link used the given resolver map to link the secret parts to their 251 // secret-store resolvers. 252 func (s *Secret) Link(resolvers map[string]telegraf.ResolveFunc) error { 253 // Decrypt the secret so we can return it 254 if s.container == nil { 255 return nil 256 } 257 buffer, err := s.container.Buffer() 258 if err != nil { 259 return err 260 } 261 defer buffer.Destroy() 262 263 // Iterate through the parts and try to resolve them. For static parts 264 // we directly replace them, while for dynamic ones we store the resolver. 265 newsecret, res, replaceErrs := resolve(buffer.Bytes(), resolvers) 266 if len(replaceErrs) > 0 { 267 return fmt.Errorf("linking secrets failed: %s", strings.Join(replaceErrs, ";")) 268 } 269 s.resolvers = res 270 271 // Store the secret if it has changed 272 if buffer.TemporaryString() != string(newsecret) { 273 s.container.Replace(newsecret) 274 } 275 276 // All linked now 277 s.unlinked = nil 278 279 return nil 280 } 281 282 func resolve(secret []byte, resolvers map[string]telegraf.ResolveFunc) ([]byte, map[string]telegraf.ResolveFunc, []string) { 283 // Iterate through the parts and try to resolve them. For static parts 284 // we directly replace them, while for dynamic ones we store the resolver. 285 replaceErrs := make([]string, 0) 286 remaining := make(map[string]telegraf.ResolveFunc) 287 newsecret := secretPattern.ReplaceAllFunc(secret, func(match []byte) []byte { 288 resolver, found := resolvers[string(match)] 289 if !found { 290 replaceErrs = append(replaceErrs, fmt.Sprintf("unlinked part %q", match)) 291 return match 292 } 293 replacement, dynamic, err := resolver() 294 if err != nil { 295 replaceErrs = append(replaceErrs, fmt.Sprintf("resolving %q failed: %v", match, err)) 296 return match 297 } 298 299 // Replace static parts right away 300 if !dynamic { 301 return replacement 302 } 303 304 // Keep the resolver for dynamic secrets 305 remaining[string(match)] = resolver 306 return match 307 }) 308 return newsecret, remaining, replaceErrs 309 } 310 311 func splitLink(s string) (storeid string, key string) { 312 // There should _ALWAYS_ be two parts due to the regular expression match 313 parts := strings.SplitN(s[2:len(s)-1], ":", 2) 314 return parts[0], parts[1] 315 }