2022-12-09 00:53:06 +08:00
|
|
|
package config
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"regexp"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/awnumar/memguard"
|
|
|
|
|
|
|
|
|
|
"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)
|
|
|
|
|
|
2023-02-08 02:15:02 +08:00
|
|
|
// secretStorePattern is a regex to validate secret-store IDs
|
|
|
|
|
var secretStorePattern = regexp.MustCompile(`^\w+$`)
|
|
|
|
|
|
2022-12-09 00:53:06 +08:00
|
|
|
// secretPattern is a regex to extract references to secrets stored
|
|
|
|
|
// in a secret-store.
|
|
|
|
|
var secretPattern = regexp.MustCompile(`@\{(\w+:\w+)\}`)
|
|
|
|
|
|
|
|
|
|
// Secret safely stores sensitive data such as a password or token
|
|
|
|
|
type Secret struct {
|
|
|
|
|
enclave *memguard.Enclave
|
|
|
|
|
resolvers map[string]telegraf.ResolveFunc
|
|
|
|
|
// unlinked contains all references in the secret that are not yet
|
|
|
|
|
// linked to the corresponding secret store.
|
|
|
|
|
unlinked []string
|
2022-12-15 21:35:05 +08:00
|
|
|
|
|
|
|
|
// Denotes if the secret is completely empty
|
|
|
|
|
notempty bool
|
2022-12-09 00:53:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewSecret creates a new secret from the given bytes
|
|
|
|
|
func NewSecret(b []byte) Secret {
|
|
|
|
|
s := Secret{}
|
|
|
|
|
s.init(b)
|
|
|
|
|
return s
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-11 22:43:20 +08:00
|
|
|
// 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
|
2022-12-09 00:53:06 +08:00
|
|
|
s.init(b)
|
|
|
|
|
|
|
|
|
|
// Keep track of secrets that contain references to secret-stores
|
|
|
|
|
// for later resolving by the config.
|
2022-12-15 21:35:05 +08:00
|
|
|
if len(s.unlinked) > 0 && s.notempty {
|
2022-12-09 00:53:06 +08:00
|
|
|
unlinkedSecrets = append(unlinkedSecrets, s)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize the secret content
|
2023-01-11 22:43:20 +08:00
|
|
|
func (s *Secret) init(secret []byte) {
|
2022-12-15 21:35:05 +08:00
|
|
|
// Remember if the secret is completely empty
|
|
|
|
|
s.notempty = len(secret) != 0
|
|
|
|
|
|
2022-12-09 00:53:06 +08:00
|
|
|
// Find all parts that need to be resolved and return them
|
|
|
|
|
s.unlinked = secretPattern.FindAllString(string(secret), -1)
|
|
|
|
|
|
|
|
|
|
// Setup the enclave
|
|
|
|
|
s.enclave = memguard.NewEnclave(secret)
|
|
|
|
|
s.resolvers = nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Destroy the secret content
|
|
|
|
|
func (s *Secret) Destroy() {
|
|
|
|
|
s.resolvers = nil
|
|
|
|
|
s.unlinked = nil
|
2022-12-15 21:35:05 +08:00
|
|
|
s.notempty = false
|
2022-12-09 00:53:06 +08:00
|
|
|
|
|
|
|
|
if s.enclave == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Wipe the secret from memory
|
|
|
|
|
lockbuf, err := s.enclave.Open()
|
|
|
|
|
if err == nil {
|
|
|
|
|
lockbuf.Destroy()
|
|
|
|
|
}
|
|
|
|
|
s.enclave = nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-15 21:35:05 +08:00
|
|
|
// Empty return if the secret is completely empty
|
|
|
|
|
func (s *Secret) Empty() bool {
|
|
|
|
|
return !s.notempty
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-26 04:02:29 +08:00
|
|
|
// EqualTo performs a constant-time comparison of the secret to the given reference
|
|
|
|
|
func (s *Secret) EqualTo(ref []byte) (bool, error) {
|
|
|
|
|
if s.enclave == nil {
|
|
|
|
|
return false, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(s.unlinked) > 0 {
|
|
|
|
|
return false, fmt.Errorf("unlinked parts in secret: %v", strings.Join(s.unlinked, ";"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get a locked-buffer of the secret to perform the comparison
|
|
|
|
|
lockbuf, err := s.enclave.Open()
|
|
|
|
|
if err != nil {
|
2023-02-22 19:57:53 +08:00
|
|
|
return false, fmt.Errorf("opening enclave failed: %w", err)
|
2023-01-26 04:02:29 +08:00
|
|
|
}
|
|
|
|
|
defer lockbuf.Destroy()
|
|
|
|
|
|
|
|
|
|
return lockbuf.EqualTo(ref), nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-09 00:53:06 +08:00
|
|
|
// Get return the string representation of the secret
|
|
|
|
|
func (s *Secret) Get() ([]byte, error) {
|
|
|
|
|
if s.enclave == nil {
|
|
|
|
|
return nil, 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
|
|
|
|
|
lockbuf, err := s.enclave.Open()
|
|
|
|
|
if err != nil {
|
2023-02-22 19:57:53 +08:00
|
|
|
return nil, fmt.Errorf("opening enclave failed: %w", err)
|
2022-12-09 00:53:06 +08:00
|
|
|
}
|
|
|
|
|
defer lockbuf.Destroy()
|
|
|
|
|
secret := lockbuf.Bytes()
|
|
|
|
|
|
|
|
|
|
if len(s.resolvers) == 0 {
|
|
|
|
|
// Make a copy as we cannot access lockbuf after Destroy, i.e.
|
|
|
|
|
// after this function finishes.
|
|
|
|
|
newsecret := append([]byte{}, secret...)
|
|
|
|
|
return newsecret, protect(newsecret)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
replaceErrs := make([]string, 0)
|
|
|
|
|
newsecret := secretPattern.ReplaceAllFunc(secret, 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 {
|
|
|
|
|
memguard.WipeBytes(newsecret)
|
|
|
|
|
return nil, fmt.Errorf("replacing secrets failed: %s", strings.Join(replaceErrs, ";"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return newsecret, protect(newsecret)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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 {
|
|
|
|
|
// Setup the resolver map
|
|
|
|
|
s.resolvers = make(map[string]telegraf.ResolveFunc)
|
|
|
|
|
|
|
|
|
|
// Decrypt the secret so we can return it
|
|
|
|
|
if s.enclave == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
lockbuf, err := s.enclave.Open()
|
|
|
|
|
if err != nil {
|
2023-02-22 19:57:53 +08:00
|
|
|
return fmt.Errorf("opening enclave failed: %w", err)
|
2022-12-09 00:53:06 +08:00
|
|
|
}
|
|
|
|
|
defer lockbuf.Destroy()
|
|
|
|
|
secret := lockbuf.Bytes()
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
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
|
|
|
|
|
s.resolvers[string(match)] = resolver
|
|
|
|
|
return match
|
|
|
|
|
})
|
|
|
|
|
if len(replaceErrs) > 0 {
|
|
|
|
|
return fmt.Errorf("linking secrets failed: %s", strings.Join(replaceErrs, ";"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Store the secret if it has changed
|
|
|
|
|
if string(secret) != string(newsecret) {
|
|
|
|
|
s.enclave = memguard.NewEnclave(newsecret)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// All linked now
|
|
|
|
|
s.unlinked = nil
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func splitLink(s string) (storeid string, 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]
|
|
|
|
|
}
|