fix(secret): Add function to set a secret (#13012)

This commit is contained in:
Sven Rebhan 2023-04-03 15:01:47 +02:00 committed by GitHub
parent d8adb1edf2
commit 3213af612e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 145 additions and 19 deletions

View File

@ -171,6 +171,23 @@ func (s *Secret) Get() ([]byte, error) {
return newsecret, protect(newsecret)
}
// 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.enclave = memguard.NewEnclave(secret)
s.resolvers = res
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
@ -179,9 +196,6 @@ func (s *Secret) GetUnlinked() []string {
// 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
@ -193,9 +207,30 @@ func (s *Secret) Link(resolvers map[string]telegraf.ResolveFunc) error {
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.
newsecret, res, replaceErrs := resolve(secret, 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 string(secret) != string(newsecret) {
s.enclave = memguard.NewEnclave(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 {
@ -214,22 +249,10 @@ func (s *Secret) Link(resolvers map[string]telegraf.ResolveFunc) error {
}
// Keep the resolver for dynamic secrets
s.resolvers[string(match)] = resolver
remaining[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
return newsecret, remaining, replaceErrs
}
func splitLink(s string) (storeid string, key string) {

View File

@ -480,6 +480,8 @@ func TestSecretStoreInvalidReference(t *testing.T) {
}
func TestSecretStoreStaticChanging(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
cfg := []byte(
`
[[inputs.mockup]]
@ -520,6 +522,8 @@ func TestSecretStoreStaticChanging(t *testing.T) {
}
func TestSecretStoreDynamic(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
cfg := []byte(
`
[[inputs.mockup]]
@ -554,6 +558,8 @@ func TestSecretStoreDynamic(t *testing.T) {
}
func TestSecretStoreDeclarationMissingID(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
cfg := []byte(`[[secretstores.mockup]]`)
c := NewConfig()
@ -562,6 +568,8 @@ func TestSecretStoreDeclarationMissingID(t *testing.T) {
}
func TestSecretStoreDeclarationInvalidID(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
invalidIDs := []string{"foo.bar", "dummy-123", "test!", "wohoo+"}
tmpl := `
[[secretstores.mockup]]
@ -578,6 +586,8 @@ func TestSecretStoreDeclarationInvalidID(t *testing.T) {
}
func TestSecretStoreDeclarationValidID(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
validIDs := []string{"foobar", "dummy123", "test_id", "W0Hoo_lala123"}
tmpl := `
[[secretstores.mockup]]
@ -593,6 +603,97 @@ func TestSecretStoreDeclarationValidID(t *testing.T) {
}
}
func TestSecretSet(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
cfg := []byte(`
[[inputs.mockup]]
secret = "a secret"
`)
c := NewConfig()
require.NoError(t, c.LoadConfigData(cfg))
require.Len(t, c.Inputs, 1)
require.NoError(t, c.LinkSecrets())
plugin := c.Inputs[0].Input.(*MockupSecretPlugin)
secret, err := plugin.Secret.Get()
require.NoError(t, err)
defer ReleaseSecret(secret)
require.EqualValues(t, "a secret", string(secret))
require.NoError(t, plugin.Secret.Set([]byte("another secret")))
newsecret, err := plugin.Secret.Get()
require.NoError(t, err)
defer ReleaseSecret(newsecret)
require.EqualValues(t, "another secret", string(newsecret))
}
func TestSecretSetResolve(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
cfg := []byte(`
[[inputs.mockup]]
secret = "@{mock:secret}"
`)
c := NewConfig()
require.NoError(t, c.LoadConfigData(cfg))
require.Len(t, c.Inputs, 1)
// Create a mockup secretstore
store := &MockupSecretStore{
Secrets: map[string][]byte{"secret": []byte("Ood Bnar")},
Dynamic: true,
}
require.NoError(t, store.Init())
c.SecretStores["mock"] = store
require.NoError(t, c.LinkSecrets())
plugin := c.Inputs[0].Input.(*MockupSecretPlugin)
secret, err := plugin.Secret.Get()
require.NoError(t, err)
defer ReleaseSecret(secret)
require.EqualValues(t, "Ood Bnar", string(secret))
require.NoError(t, plugin.Secret.Set([]byte("@{mock:secret} is cool")))
newsecret, err := plugin.Secret.Get()
require.NoError(t, err)
defer ReleaseSecret(newsecret)
require.EqualValues(t, "Ood Bnar is cool", string(newsecret))
}
func TestSecretSetResolveInvalid(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
cfg := []byte(`
[[inputs.mockup]]
secret = "@{mock:secret}"
`)
c := NewConfig()
require.NoError(t, c.LoadConfigData(cfg))
require.Len(t, c.Inputs, 1)
// Create a mockup secretstore
store := &MockupSecretStore{
Secrets: map[string][]byte{"secret": []byte("Ood Bnar")},
Dynamic: true,
}
require.NoError(t, store.Init())
c.SecretStores["mock"] = store
require.NoError(t, c.LinkSecrets())
plugin := c.Inputs[0].Input.(*MockupSecretPlugin)
secret, err := plugin.Secret.Get()
require.NoError(t, err)
defer ReleaseSecret(secret)
require.EqualValues(t, "Ood Bnar", string(secret))
err = plugin.Secret.Set([]byte("@{mock:another_secret}"))
require.ErrorContains(t, err, `linking new secrets failed: unlinked part "@{mock:another_secret}"`)
}
/*** Mockup (input) plugin for testing to avoid cyclic dependencies ***/
type MockupSecretPlugin struct {
Secret Secret `toml:"secret"`

View File

@ -125,8 +125,10 @@ func (m *Mysql) Init() error {
conf.TLSConfig = tlsid
}
server.Destroy()
m.Servers[i] = config.NewSecret([]byte(conf.FormatDSN()))
if err := server.Set([]byte(conf.FormatDSN())); err != nil {
return fmt.Errorf("replacing server %q failed: %w", dsn, err)
}
m.Servers[i] = server
}
return nil