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)
|
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
|
// GetUnlinked return the parts of the secret that is not yet linked to a resolver
|
||||||
func (s *Secret) GetUnlinked() []string {
|
func (s *Secret) GetUnlinked() []string {
|
||||||
return s.unlinked
|
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
|
// Link used the given resolver map to link the secret parts to their
|
||||||
// secret-store resolvers.
|
// secret-store resolvers.
|
||||||
func (s *Secret) Link(resolvers map[string]telegraf.ResolveFunc) error {
|
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
|
// Decrypt the secret so we can return it
|
||||||
if s.enclave == nil {
|
if s.enclave == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -193,9 +207,30 @@ func (s *Secret) Link(resolvers map[string]telegraf.ResolveFunc) error {
|
||||||
defer lockbuf.Destroy()
|
defer lockbuf.Destroy()
|
||||||
secret := lockbuf.Bytes()
|
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
|
// Iterate through the parts and try to resolve them. For static parts
|
||||||
// we directly replace them, while for dynamic ones we store the resolver.
|
// we directly replace them, while for dynamic ones we store the resolver.
|
||||||
replaceErrs := make([]string, 0)
|
replaceErrs := make([]string, 0)
|
||||||
|
remaining := make(map[string]telegraf.ResolveFunc)
|
||||||
newsecret := secretPattern.ReplaceAllFunc(secret, func(match []byte) []byte {
|
newsecret := secretPattern.ReplaceAllFunc(secret, func(match []byte) []byte {
|
||||||
resolver, found := resolvers[string(match)]
|
resolver, found := resolvers[string(match)]
|
||||||
if !found {
|
if !found {
|
||||||
|
|
@ -214,22 +249,10 @@ func (s *Secret) Link(resolvers map[string]telegraf.ResolveFunc) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep the resolver for dynamic secrets
|
// Keep the resolver for dynamic secrets
|
||||||
s.resolvers[string(match)] = resolver
|
remaining[string(match)] = resolver
|
||||||
return match
|
return match
|
||||||
})
|
})
|
||||||
if len(replaceErrs) > 0 {
|
return newsecret, remaining, replaceErrs
|
||||||
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) {
|
func splitLink(s string) (storeid string, key string) {
|
||||||
|
|
|
||||||
|
|
@ -480,6 +480,8 @@ func TestSecretStoreInvalidReference(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSecretStoreStaticChanging(t *testing.T) {
|
func TestSecretStoreStaticChanging(t *testing.T) {
|
||||||
|
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
|
||||||
|
|
||||||
cfg := []byte(
|
cfg := []byte(
|
||||||
`
|
`
|
||||||
[[inputs.mockup]]
|
[[inputs.mockup]]
|
||||||
|
|
@ -520,6 +522,8 @@ func TestSecretStoreStaticChanging(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSecretStoreDynamic(t *testing.T) {
|
func TestSecretStoreDynamic(t *testing.T) {
|
||||||
|
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
|
||||||
|
|
||||||
cfg := []byte(
|
cfg := []byte(
|
||||||
`
|
`
|
||||||
[[inputs.mockup]]
|
[[inputs.mockup]]
|
||||||
|
|
@ -554,6 +558,8 @@ func TestSecretStoreDynamic(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSecretStoreDeclarationMissingID(t *testing.T) {
|
func TestSecretStoreDeclarationMissingID(t *testing.T) {
|
||||||
|
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
|
||||||
|
|
||||||
cfg := []byte(`[[secretstores.mockup]]`)
|
cfg := []byte(`[[secretstores.mockup]]`)
|
||||||
|
|
||||||
c := NewConfig()
|
c := NewConfig()
|
||||||
|
|
@ -562,6 +568,8 @@ func TestSecretStoreDeclarationMissingID(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSecretStoreDeclarationInvalidID(t *testing.T) {
|
func TestSecretStoreDeclarationInvalidID(t *testing.T) {
|
||||||
|
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
|
||||||
|
|
||||||
invalidIDs := []string{"foo.bar", "dummy-123", "test!", "wohoo+"}
|
invalidIDs := []string{"foo.bar", "dummy-123", "test!", "wohoo+"}
|
||||||
tmpl := `
|
tmpl := `
|
||||||
[[secretstores.mockup]]
|
[[secretstores.mockup]]
|
||||||
|
|
@ -578,6 +586,8 @@ func TestSecretStoreDeclarationInvalidID(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSecretStoreDeclarationValidID(t *testing.T) {
|
func TestSecretStoreDeclarationValidID(t *testing.T) {
|
||||||
|
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
|
||||||
|
|
||||||
validIDs := []string{"foobar", "dummy123", "test_id", "W0Hoo_lala123"}
|
validIDs := []string{"foobar", "dummy123", "test_id", "W0Hoo_lala123"}
|
||||||
tmpl := `
|
tmpl := `
|
||||||
[[secretstores.mockup]]
|
[[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 ***/
|
/*** Mockup (input) plugin for testing to avoid cyclic dependencies ***/
|
||||||
type MockupSecretPlugin struct {
|
type MockupSecretPlugin struct {
|
||||||
Secret Secret `toml:"secret"`
|
Secret Secret `toml:"secret"`
|
||||||
|
|
|
||||||
|
|
@ -125,8 +125,10 @@ func (m *Mysql) Init() error {
|
||||||
conf.TLSConfig = tlsid
|
conf.TLSConfig = tlsid
|
||||||
}
|
}
|
||||||
|
|
||||||
server.Destroy()
|
if err := server.Set([]byte(conf.FormatDSN())); err != nil {
|
||||||
m.Servers[i] = config.NewSecret([]byte(conf.FormatDSN()))
|
return fmt.Errorf("replacing server %q failed: %w", dsn, err)
|
||||||
|
}
|
||||||
|
m.Servers[i] = server
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue