chore(secrets): Warn if settings look like secrets but use invalid characters (#14706)

This commit is contained in:
Sven Rebhan 2024-02-08 21:36:16 +01:00 committed by GitHub
parent ae7fbc5082
commit 5f6772e869
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 40 additions and 4 deletions

View File

@ -2,6 +2,7 @@ package config
import ( import (
"fmt" "fmt"
"log"
"regexp" "regexp"
"strings" "strings"
"sync/atomic" "sync/atomic"
@ -20,10 +21,12 @@ var unlinkedSecrets = make([]*Secret, 0)
// secretStorePattern is a regex to validate secret-store IDs // secretStorePattern is a regex to validate secret-store IDs
var secretStorePattern = regexp.MustCompile(`^\w+$`) var secretStorePattern = regexp.MustCompile(`^\w+$`)
// secretPattern is a regex to extract references to secrets stored // secretPattern is a regex to extract references to secrets store in a secret-store
// in a secret-store.
var secretPattern = regexp.MustCompile(`@\{(\w+:\w+)\}`) 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 // secretCount is the number of secrets use in Telegraf
var secretCount atomic.Int64 var secretCount atomic.Int64
@ -125,8 +128,18 @@ func (s *Secret) init(secret []byte) {
// Remember if the secret is completely empty // Remember if the secret is completely empty
s.notempty = len(secret) != 0 s.notempty = len(secret) != 0
// Find all parts that need to be resolved and return them // Find all secret candidates and check if they are really a valid
s.unlinked = secretPattern.FindAllString(string(secret), -1) // 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 s.resolvers = nil
// Setup the container implementation // Setup the container implementation

View File

@ -1,8 +1,10 @@
package config package config
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"log"
"testing" "testing"
"github.com/awnumar/memguard" "github.com/awnumar/memguard"
@ -716,6 +718,27 @@ func (tsuite *SecretImplTestSuite) TestSecretSetResolveInvalid() {
require.ErrorContains(t, err, `linking new secrets failed: unlinked part "@{mock:another_secret}"`) require.ErrorContains(t, err, `linking new secrets failed: unlinked part "@{mock:another_secret}"`)
} }
func (tsuite *SecretImplTestSuite) TestSecretInvalidWarn() {
t := tsuite.T()
// Intercept the log output
var buf bytes.Buffer
backup := log.Writer()
log.SetOutput(&buf)
defer log.SetOutput(backup)
cfg := []byte(`
[[inputs.mockup]]
secret = "server=a user=@{mock:secret-with-invalid-chars} pass=@{mock:secret_pass}"
`)
c := NewConfig()
require.NoError(t, c.LoadConfigData(cfg))
require.Len(t, c.Inputs, 1)
require.Contains(t, buf.String(), `W! Secret "@{mock:secret-with-invalid-chars}" contains invalid character(s)`)
require.NotContains(t, buf.String(), "@{mock:secret_pass}")
}
func TestSecretImplUnprotected(t *testing.T) { func TestSecretImplUnprotected(t *testing.T) {
impl := &unprotectedSecretImpl{} impl := &unprotectedSecretImpl{}
container := impl.Container([]byte("foobar")) container := impl.Container([]byte("foobar"))