fix(secret): Add function to set a secret (#13012)
This commit is contained in:
parent
d8adb1edf2
commit
3213af612e
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue