Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(secrets): Add unprotected secret implementation #13998

Merged
merged 8 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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{}
srebhan marked this conversation as resolved.
Show resolved Hide resolved
}

// 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
Loading