Skip to content

Commit

Permalink
feat(secrets): Add unprotected secret implementation (#13998)
Browse files Browse the repository at this point in the history
  • Loading branch information
srebhan authored Dec 4, 2023
1 parent 193479a commit d570f01
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 86 deletions.
5 changes: 5 additions & 0 deletions cmd/telegraf/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ func runApp(args []string, outputBuffer io.Writer, pprof Server, c TelegrafConfi
debug: cCtx.Bool("debug"),
once: cCtx.Bool("once"),
quiet: cCtx.Bool("quiet"),
unprotected: cCtx.Bool("unprotected"),
}

w := WindowFlags{
Expand Down Expand Up @@ -314,6 +315,10 @@ func runApp(args []string, outputBuffer io.Writer, pprof Server, c TelegrafConfi
Name: "quiet",
Usage: "run in quiet mode",
},
&cli.BoolFlag{
Name: "unprotected",
Usage: "do not protect secrets in memory",
},
&cli.BoolFlag{
Name: "test",
Usage: "enable test mode: gather metrics, print them out, and exit. " +
Expand Down
2 changes: 1 addition & 1 deletion cmd/telegraf/pprof.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (p *PprofServer) Start(address string) {
}

if err := server.ListenAndServe(); err != nil {
p.err <- fmt.Errorf("E! %w", err)
p.err <- err
}
close(p.err)
}()
Expand Down
30 changes: 19 additions & 11 deletions cmd/telegraf/telegraf.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type GlobalFlags struct {
debug bool
once bool
quiet bool
unprotected bool
}

type WindowFlags struct {
Expand Down Expand Up @@ -84,6 +85,12 @@ func (t *Telegraf) Init(pprofErr <-chan error, f Filters, g GlobalFlags, w Windo
t.GlobalFlags = g
t.WindowFlags = w

// Disable secret protection before performing any other operation
if g.unprotected {
log.Println("W! Running without secret protection!")
config.DisableSecretProtection()
}

// Set global password
if g.password != "" {
config.Password = config.NewSecret([]byte(g.password))
Expand Down Expand Up @@ -150,7 +157,7 @@ func (t *Telegraf) reloadLoop() error {
select {
case sig := <-signals:
if sig == syscall.SIGHUP {
log.Printf("I! Reloading Telegraf config")
log.Println("I! Reloading Telegraf config")
<-reload
reload <- true
}
Expand Down Expand Up @@ -325,17 +332,18 @@ func (t *Telegraf) runAgent(ctx context.Context, c *config.Config, reloadConfig
}

// Compute the amount of locked memory needed for the secrets
required := 2 * c.NumberSecrets * uint64(os.Getpagesize())
available := getLockedMemoryLimit()
if required > available {
required /= 1024
available /= 1024
log.Printf("I! Found %d secrets...", c.NumberSecrets)
msg := fmt.Sprintf("Insufficient lockable memory %dkb when %dkb is required.", available, required)
msg += " Please increase the limit for Telegraf in your Operating System!"
log.Printf("W! " + color.RedString(msg))
if !t.GlobalFlags.unprotected {
required := 3 * c.NumberSecrets * uint64(os.Getpagesize())
available := getLockedMemoryLimit()
if required > available {
required /= 1024
available /= 1024
log.Printf("I! Found %d secrets...", c.NumberSecrets)
msg := fmt.Sprintf("Insufficient lockable memory %dkb when %dkb is required.", available, required)
msg += " Please increase the limit for Telegraf in your Operating System!"
log.Printf("W! " + color.RedString(msg))
}
}

ag := agent.NewAgent(c)

// Notify systemd that telegraf is ready
Expand Down
8 changes: 8 additions & 0 deletions config/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ type secretImpl interface {
Wipe(secret []byte)
}

func EnableSecretProtection() {
selectedImpl = &protectedSecretImpl{}
}

func DisableSecretProtection() {
selectedImpl = &unprotectedSecretImpl{}
}

// secretContainer represents an abstraction of the container holding the
// actual secret value
type secretContainer interface {
Expand Down
166 changes: 105 additions & 61 deletions config/secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/awnumar/memguard"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
Expand Down Expand Up @@ -435,7 +436,75 @@ func TestSecretStoreInvalidKeys(t *testing.T) {
}
}

func TestSecretEqualTo(t *testing.T) {
func TestSecretStoreDeclarationMissingID(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()

cfg := []byte(`[[secretstores.mockup]]`)

c := NewConfig()
err := c.LoadConfigData(cfg)
require.ErrorContains(t, err, `error parsing mockup, "mockup" secret-store without ID`)
}

func TestSecretStoreDeclarationInvalidID(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()

invalidIDs := []string{"foo.bar", "dummy-123", "test!", "wohoo+"}
tmpl := `
[[secretstores.mockup]]
id = %q
`
for _, id := range invalidIDs {
t.Run(id, func(t *testing.T) {
cfg := []byte(fmt.Sprintf(tmpl, id))
c := NewConfig()
err := c.LoadConfigData(cfg)
require.ErrorContains(t, err, `error parsing mockup, invalid secret-store ID`)
})
}
}

func TestSecretStoreDeclarationValidID(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()

validIDs := []string{"foobar", "dummy123", "test_id", "W0Hoo_lala123"}
tmpl := `
[[secretstores.mockup]]
id = %q
`
for _, id := range validIDs {
t.Run(id, func(t *testing.T) {
cfg := []byte(fmt.Sprintf(tmpl, id))
c := NewConfig()
err := c.LoadConfigData(cfg)
require.NoError(t, err)
})
}
}

type SecretImplTestSuite struct {
suite.Suite
protected bool
}

func (tsuite *SecretImplTestSuite) SetupSuite() {
if tsuite.protected {
EnableSecretProtection()
} else {
DisableSecretProtection()
}
}

func (*SecretImplTestSuite) TearDownSuite() {
EnableSecretProtection()
}

func (*SecretImplTestSuite) TearDownTest() {
unlinkedSecrets = make([]*Secret, 0)
}

func (tsuite *SecretImplTestSuite) TestSecretEqualTo() {
t := tsuite.T()
mysecret := "a wonderful test"
s := NewSecret([]byte(mysecret))
defer s.Destroy()
Expand All @@ -449,9 +518,8 @@ func TestSecretEqualTo(t *testing.T) {
require.False(t, equal)
}

func TestSecretStoreInvalidReference(t *testing.T) {
// Make sure we clean-up our mess
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
func (tsuite *SecretImplTestSuite) TestSecretStoreInvalidReference() {
t := tsuite.T()

cfg := []byte(
`
Expand Down Expand Up @@ -480,8 +548,8 @@ func TestSecretStoreInvalidReference(t *testing.T) {
}
}

func TestSecretStoreStaticChanging(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
func (tsuite *SecretImplTestSuite) TestSecretStoreStaticChanging() {
t := tsuite.T()

cfg := []byte(
`
Expand Down Expand Up @@ -522,8 +590,8 @@ func TestSecretStoreStaticChanging(t *testing.T) {
}
}

func TestSecretStoreDynamic(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
func (tsuite *SecretImplTestSuite) TestSecretStoreDynamic() {
t := tsuite.T()

cfg := []byte(
`
Expand Down Expand Up @@ -558,54 +626,8 @@ func TestSecretStoreDynamic(t *testing.T) {
}
}

func TestSecretStoreDeclarationMissingID(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()

cfg := []byte(`[[secretstores.mockup]]`)

c := NewConfig()
err := c.LoadConfigData(cfg)
require.ErrorContains(t, err, `error parsing mockup, "mockup" secret-store without ID`)
}

func TestSecretStoreDeclarationInvalidID(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()

invalidIDs := []string{"foo.bar", "dummy-123", "test!", "wohoo+"}
tmpl := `
[[secretstores.mockup]]
id = %q
`
for _, id := range invalidIDs {
t.Run(id, func(t *testing.T) {
cfg := []byte(fmt.Sprintf(tmpl, id))
c := NewConfig()
err := c.LoadConfigData(cfg)
require.ErrorContains(t, err, `error parsing mockup, invalid secret-store ID`)
})
}
}

func TestSecretStoreDeclarationValidID(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()

validIDs := []string{"foobar", "dummy123", "test_id", "W0Hoo_lala123"}
tmpl := `
[[secretstores.mockup]]
id = %q
`
for _, id := range validIDs {
t.Run(id, func(t *testing.T) {
cfg := []byte(fmt.Sprintf(tmpl, id))
c := NewConfig()
err := c.LoadConfigData(cfg)
require.NoError(t, err)
})
}
}

func TestSecretSet(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
func (tsuite *SecretImplTestSuite) TestSecretSet() {
t := tsuite.T()

cfg := []byte(`
[[inputs.mockup]]
Expand All @@ -630,9 +652,8 @@ func TestSecretSet(t *testing.T) {
require.EqualValues(t, "another secret", newsecret.TemporaryString())
}

func TestSecretSetResolve(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()

func (tsuite *SecretImplTestSuite) TestSecretSetResolve() {
t := tsuite.T()
cfg := []byte(`
[[inputs.mockup]]
secret = "@{mock:secret}"
Expand Down Expand Up @@ -664,8 +685,8 @@ func TestSecretSetResolve(t *testing.T) {
require.EqualValues(t, "Ood Bnar is cool", newsecret.TemporaryString())
}

func TestSecretSetResolveInvalid(t *testing.T) {
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
func (tsuite *SecretImplTestSuite) TestSecretSetResolveInvalid() {
t := tsuite.T()

cfg := []byte(`
[[inputs.mockup]]
Expand Down Expand Up @@ -695,6 +716,29 @@ func TestSecretSetResolveInvalid(t *testing.T) {
require.ErrorContains(t, err, `linking new secrets failed: unlinked part "@{mock:another_secret}"`)
}

func TestSecretImplUnprotected(t *testing.T) {
impl := &unprotectedSecretImpl{}
container := impl.Container([]byte("foobar"))
require.NotNil(t, container)
c, ok := container.(*unprotectedSecretContainer)
require.True(t, ok)
require.Equal(t, "foobar", string(c.buf.content))
buf, err := container.Buffer()
require.NoError(t, err)
require.NotNil(t, buf)
require.Equal(t, []byte("foobar"), buf.Bytes())
require.Equal(t, "foobar", buf.TemporaryString())
require.Equal(t, "foobar", buf.String())
}

func TestSecretImplTestSuiteUnprotected(t *testing.T) {
suite.Run(t, &SecretImplTestSuite{protected: false})
}

func TestSecretImplTestSuiteProtected(t *testing.T) {
suite.Run(t, &SecretImplTestSuite{protected: true})
}

/*** Mockup (input) plugin for testing to avoid cyclic dependencies ***/
type MockupSecretPlugin struct {
Secret Secret `toml:"secret"`
Expand Down
Loading

0 comments on commit d570f01

Please sign in to comment.