Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
315
config/secret.go
Normal file
315
config/secret.go
Normal file
|
@ -0,0 +1,315 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
)
|
||||
|
||||
// unlinkedSecrets contains the list of secrets that contain
|
||||
// references not yet linked to their corresponding secret-store.
|
||||
// Those secrets must later (after reading the config) be linked
|
||||
// by the config to their respective secret-stores.
|
||||
// Secrets containing constant strings will not be found in this
|
||||
// list.
|
||||
var unlinkedSecrets = make([]*Secret, 0)
|
||||
|
||||
// secretStorePattern is a regex to validate secret-store IDs
|
||||
var secretStorePattern = regexp.MustCompile(`^\w+$`)
|
||||
|
||||
// secretPattern is a regex to extract references to secrets store in a secret-store
|
||||
var secretPattern = regexp.MustCompile(`@\{(\w+:\w+)\}`)
|
||||
|
||||
// secretCandidatePattern is a regex to find secret candidates to warn users on invalid characters in references
|
||||
var secretCandidatePattern = regexp.MustCompile(`@\{.+?:.+?}`)
|
||||
|
||||
// secretCount is the number of secrets use in Telegraf
|
||||
var secretCount atomic.Int64
|
||||
|
||||
// selectedImpl is the configured implementation for secrets
|
||||
var selectedImpl secretImpl = &protectedSecretImpl{}
|
||||
|
||||
// secretImpl represents an abstraction for different implementations of secrets
|
||||
type secretImpl interface {
|
||||
Container(secret []byte) secretContainer
|
||||
EmptyBuffer() SecretBuffer
|
||||
Wipe(secret []byte)
|
||||
}
|
||||
|
||||
func EnableSecretProtection() {
|
||||
selectedImpl = &protectedSecretImpl{}
|
||||
}
|
||||
|
||||
func DisableSecretProtection() {
|
||||
selectedImpl = &unprotectedSecretImpl{}
|
||||
}
|
||||
|
||||
// secretContainer represents an abstraction of the container holding the
|
||||
// actual secret value
|
||||
type secretContainer interface {
|
||||
Destroy()
|
||||
Equals(ref []byte) (bool, error)
|
||||
Buffer() (SecretBuffer, error)
|
||||
AsBuffer(secret []byte) SecretBuffer
|
||||
Replace(secret []byte)
|
||||
}
|
||||
|
||||
// SecretBuffer allows to access the content of the secret
|
||||
type SecretBuffer interface {
|
||||
// Size returns the length of the buffer content
|
||||
Size() int
|
||||
// Grow will grow the capacity of the underlying buffer to the given size
|
||||
Grow(capacity int)
|
||||
// Bytes returns the content of the buffer as bytes.
|
||||
// NOTE: The returned bytes shall NOT be accessed after destroying the
|
||||
// buffer using 'Destroy()' as the underlying the memory area might be
|
||||
// wiped and invalid.
|
||||
Bytes() []byte
|
||||
// TemporaryString returns the content of the buffer as a string.
|
||||
// NOTE: The returned String shall NOT be accessed after destroying the
|
||||
// buffer using 'Destroy()' as the underlying the memory area might be
|
||||
// wiped and invalid.
|
||||
TemporaryString() string
|
||||
// String returns a copy of the underlying buffer's content as string.
|
||||
// It is safe to use the returned value after destroying the buffer.
|
||||
String() string
|
||||
// Destroy will wipe the buffer's content and destroy the underlying
|
||||
// buffer. Do not access the buffer after destroying it.
|
||||
Destroy()
|
||||
}
|
||||
|
||||
// Secret safely stores sensitive data such as a password or token
|
||||
type Secret struct {
|
||||
// container is the implementation for holding the secret. It can be
|
||||
// protected or not depending on the concrete implementation.
|
||||
container secretContainer
|
||||
|
||||
// resolvers are the functions for resolving a given secret-id (key)
|
||||
resolvers map[string]telegraf.ResolveFunc
|
||||
|
||||
// unlinked contains all references in the secret that are not yet
|
||||
// linked to the corresponding secret store.
|
||||
unlinked []string
|
||||
|
||||
// notempty denotes if the secret is completely empty
|
||||
notempty bool
|
||||
}
|
||||
|
||||
// NewSecret creates a new secret from the given bytes
|
||||
func NewSecret(b []byte) Secret {
|
||||
s := Secret{}
|
||||
s.init(b)
|
||||
return s
|
||||
}
|
||||
|
||||
// UnmarshalText creates a secret from a toml value following the "string" rule.
|
||||
func (s *Secret) UnmarshalText(b []byte) error {
|
||||
// Unmarshal secret from TOML and put it into protected memory
|
||||
s.init(b)
|
||||
|
||||
// Keep track of secrets that contain references to secret-stores
|
||||
// for later resolving by the config.
|
||||
if len(s.unlinked) > 0 && s.notempty {
|
||||
unlinkedSecrets = append(unlinkedSecrets, s)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Initialize the secret content
|
||||
func (s *Secret) init(secret []byte) {
|
||||
// Keep track of the number of secrets...
|
||||
secretCount.Add(1)
|
||||
|
||||
// Remember if the secret is completely empty
|
||||
s.notempty = len(secret) != 0
|
||||
|
||||
// Find all secret candidates and check if they are really a valid
|
||||
// reference. Otherwise issue a warning to let the user know that there is
|
||||
// a potential issue with their secret instead of silently ignoring it.
|
||||
candidates := secretCandidatePattern.FindAllString(string(secret), -1)
|
||||
s.unlinked = make([]string, 0, len(candidates))
|
||||
for _, c := range candidates {
|
||||
if secretPattern.MatchString(c) {
|
||||
s.unlinked = append(s.unlinked, c)
|
||||
} else {
|
||||
log.Printf("W! Secret %q contains invalid character(s), only letters, digits and underscores are allowed.", c)
|
||||
}
|
||||
}
|
||||
s.resolvers = nil
|
||||
|
||||
// Setup the container implementation
|
||||
s.container = selectedImpl.Container(secret)
|
||||
}
|
||||
|
||||
// Destroy the secret content
|
||||
func (s *Secret) Destroy() {
|
||||
s.resolvers = nil
|
||||
s.unlinked = nil
|
||||
s.notempty = false
|
||||
|
||||
if s.container != nil {
|
||||
s.container.Destroy()
|
||||
s.container = nil
|
||||
|
||||
// Keep track of the number of used secrets...
|
||||
secretCount.Add(-1)
|
||||
}
|
||||
}
|
||||
|
||||
// Empty return if the secret is completely empty
|
||||
func (s *Secret) Empty() bool {
|
||||
return !s.notempty
|
||||
}
|
||||
|
||||
// EqualTo performs a constant-time comparison of the secret to the given reference
|
||||
func (s *Secret) EqualTo(ref []byte) (bool, error) {
|
||||
if s.container == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if len(s.unlinked) > 0 {
|
||||
return false, fmt.Errorf("unlinked parts in secret: %v", strings.Join(s.unlinked, ";"))
|
||||
}
|
||||
|
||||
return s.container.Equals(ref)
|
||||
}
|
||||
|
||||
// Get return the string representation of the secret
|
||||
func (s *Secret) Get() (SecretBuffer, error) {
|
||||
if s.container == nil {
|
||||
return selectedImpl.EmptyBuffer(), nil
|
||||
}
|
||||
|
||||
if len(s.unlinked) > 0 {
|
||||
return nil, fmt.Errorf("unlinked parts in secret: %v", strings.Join(s.unlinked, ";"))
|
||||
}
|
||||
|
||||
// Decrypt the secret so we can return it
|
||||
buffer, err := s.container.Buffer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We've got a static secret so simply return the buffer
|
||||
if len(s.resolvers) == 0 {
|
||||
return buffer, nil
|
||||
}
|
||||
defer buffer.Destroy()
|
||||
|
||||
replaceErrs := make([]string, 0)
|
||||
newsecret := secretPattern.ReplaceAllFunc(buffer.Bytes(), func(match []byte) []byte {
|
||||
resolver, found := s.resolvers[string(match)]
|
||||
if !found {
|
||||
replaceErrs = append(replaceErrs, fmt.Sprintf("no resolver for %q", match))
|
||||
return match
|
||||
}
|
||||
replacement, _, err := resolver()
|
||||
if err != nil {
|
||||
replaceErrs = append(replaceErrs, fmt.Sprintf("resolving %q failed: %v", match, err))
|
||||
return match
|
||||
}
|
||||
|
||||
return replacement
|
||||
})
|
||||
if len(replaceErrs) > 0 {
|
||||
selectedImpl.Wipe(newsecret)
|
||||
return nil, fmt.Errorf("replacing secrets failed: %s", strings.Join(replaceErrs, ";"))
|
||||
}
|
||||
|
||||
return s.container.AsBuffer(newsecret), nil
|
||||
}
|
||||
|
||||
// Set overwrites the secret's value with a new one. Please note, the secret
|
||||
// is not linked again, so only references to secret-stores can be used, e.g. by
|
||||
// adding more clear-text or reordering secrets.
|
||||
func (s *Secret) Set(value []byte) error {
|
||||
// Link the new value can be resolved
|
||||
secret, res, replaceErrs := resolve(value, s.resolvers)
|
||||
if len(replaceErrs) > 0 {
|
||||
return fmt.Errorf("linking new secrets failed: %s", strings.Join(replaceErrs, ";"))
|
||||
}
|
||||
|
||||
// Set the new secret
|
||||
s.container.Replace(secret)
|
||||
s.resolvers = res
|
||||
s.notempty = len(value) > 0
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUnlinked return the parts of the secret that is not yet linked to a resolver
|
||||
func (s *Secret) GetUnlinked() []string {
|
||||
return s.unlinked
|
||||
}
|
||||
|
||||
// Link used the given resolver map to link the secret parts to their
|
||||
// secret-store resolvers.
|
||||
func (s *Secret) Link(resolvers map[string]telegraf.ResolveFunc) error {
|
||||
// Decrypt the secret so we can return it
|
||||
if s.container == nil {
|
||||
return nil
|
||||
}
|
||||
buffer, err := s.container.Buffer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer buffer.Destroy()
|
||||
|
||||
// Iterate through the parts and try to resolve them. For static parts
|
||||
// we directly replace them, while for dynamic ones we store the resolver.
|
||||
newsecret, res, replaceErrs := resolve(buffer.Bytes(), resolvers)
|
||||
if len(replaceErrs) > 0 {
|
||||
return fmt.Errorf("linking secrets failed: %s", strings.Join(replaceErrs, ";"))
|
||||
}
|
||||
s.resolvers = res
|
||||
|
||||
// Store the secret if it has changed
|
||||
if buffer.TemporaryString() != string(newsecret) {
|
||||
s.container.Replace(newsecret)
|
||||
}
|
||||
|
||||
// All linked now
|
||||
s.unlinked = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolve(secret []byte, resolvers map[string]telegraf.ResolveFunc) ([]byte, map[string]telegraf.ResolveFunc, []string) {
|
||||
// Iterate through the parts and try to resolve them. For static parts
|
||||
// we directly replace them, while for dynamic ones we store the resolver.
|
||||
replaceErrs := make([]string, 0)
|
||||
remaining := make(map[string]telegraf.ResolveFunc)
|
||||
newsecret := secretPattern.ReplaceAllFunc(secret, func(match []byte) []byte {
|
||||
resolver, found := resolvers[string(match)]
|
||||
if !found {
|
||||
replaceErrs = append(replaceErrs, fmt.Sprintf("unlinked part %q", match))
|
||||
return match
|
||||
}
|
||||
replacement, dynamic, err := resolver()
|
||||
if err != nil {
|
||||
replaceErrs = append(replaceErrs, fmt.Sprintf("resolving %q failed: %v", match, err))
|
||||
return match
|
||||
}
|
||||
|
||||
// Replace static parts right away
|
||||
if !dynamic {
|
||||
return replacement
|
||||
}
|
||||
|
||||
// Keep the resolver for dynamic secrets
|
||||
remaining[string(match)] = resolver
|
||||
return match
|
||||
})
|
||||
return newsecret, remaining, replaceErrs
|
||||
}
|
||||
|
||||
func splitLink(s string) (storeID, key string) {
|
||||
// There should _ALWAYS_ be two parts due to the regular expression match
|
||||
parts := strings.SplitN(s[2:len(s)-1], ":", 2)
|
||||
return parts[0], parts[1]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue