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  }