Skip to content

Commit

Permalink
feat(secretstores): convert many output plugins (#12497)
Browse files Browse the repository at this point in the history
  • Loading branch information
srebhan authored Jan 25, 2023
1 parent 607bfdb commit f5c2c4a
Show file tree
Hide file tree
Showing 41 changed files with 546 additions and 273 deletions.
20 changes: 20 additions & 0 deletions config/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,26 @@ func (s *Secret) Empty() bool {
return !s.notempty
}

// EqualTo performs a constant-time comparison of the secret to the given reference
func (s *Secret) EqualTo(ref []byte) (bool, error) {
if s.enclave == nil {
return false, nil
}

if len(s.unlinked) > 0 {
return false, fmt.Errorf("unlinked parts in secret: %v", strings.Join(s.unlinked, ";"))
}

// Get a locked-buffer of the secret to perform the comparison
lockbuf, err := s.enclave.Open()
if err != nil {
return false, fmt.Errorf("opening enclave failed: %v", err)
}
defer lockbuf.Destroy()

return lockbuf.EqualTo(ref), nil
}

// Get return the string representation of the secret
func (s *Secret) Get() ([]byte, error) {
if s.enclave == nil {
Expand Down
14 changes: 14 additions & 0 deletions config/secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,20 @@ func TestSecretStoreInvalidKeys(t *testing.T) {
}
}

func TestSecretEqualTo(t *testing.T) {
mysecret := "a wonderful test"
s := NewSecret([]byte(mysecret))
defer s.Destroy()

equal, err := s.EqualTo([]byte(mysecret))
require.NoError(t, err)
require.True(t, equal)

equal, err = s.EqualTo([]byte("some random text"))
require.NoError(t, err)
require.False(t, equal)
}

func TestSecretStoreInvalidReference(t *testing.T) {
// Make sure we clean-up our mess
defer func() { unlinkedSecrets = make([]*Secret, 0) }()
Expand Down
21 changes: 16 additions & 5 deletions plugins/outputs/amqp/amqp.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package amqp
import (
"bytes"
_ "embed"
"fmt"
"strings"
"time"

Expand Down Expand Up @@ -47,8 +48,8 @@ type AMQP struct {
ExchangePassive bool `toml:"exchange_passive"`
ExchangeDurability string `toml:"exchange_durability"`
ExchangeArguments map[string]string `toml:"exchange_arguments"`
Username string `toml:"username"`
Password string `toml:"password"`
Username config.Secret `toml:"username"`
Password config.Secret `toml:"password"`
MaxMessages int `toml:"max_messages"`
AuthMethod string `toml:"auth_method"`
RoutingTag string `toml:"routing_tag"`
Expand Down Expand Up @@ -293,11 +294,21 @@ func (q *AMQP) makeClientConfig() (*ClientConfig, error) {
var auth []amqp.Authentication
if strings.ToUpper(q.AuthMethod) == "EXTERNAL" {
auth = []amqp.Authentication{&externalAuth{}}
} else if q.Username != "" || q.Password != "" {
} else if !q.Username.Empty() || !q.Password.Empty() {
username, err := q.Username.Get()
if err != nil {
return nil, fmt.Errorf("getting username failed: %w", err)
}
defer config.ReleaseSecret(username)
password, err := q.Password.Get()
if err != nil {
return nil, fmt.Errorf("getting password failed: %w", err)
}
defer config.ReleaseSecret(password)
auth = []amqp.Authentication{
&amqp.PlainAuth{
Username: q.Username,
Password: q.Password,
Username: string(username),
Password: string(password),
},
}
}
Expand Down
4 changes: 2 additions & 2 deletions plugins/outputs/amqp/amqp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ func TestConnect(t *testing.T) {
name: "username password",
output: &AMQP{
URL: "amqp://foo:bar@localhost",
Username: "telegraf",
Password: "pa$$word",
Username: config.NewSecret([]byte("telegraf")),
Password: config.NewSecret([]byte("pa$$word")),
connect: func(_ *ClientConfig) (Client, error) {
return NewMockClient(), nil
},
Expand Down
13 changes: 9 additions & 4 deletions plugins/outputs/dynatrace/dynatrace.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ var sampleConfig string
// Dynatrace Configuration for the Dynatrace output plugin
type Dynatrace struct {
URL string `toml:"url"`
APIToken string `toml:"api_token"`
APIToken config.Secret `toml:"api_token"`
Prefix string `toml:"prefix"`
Log telegraf.Logger `toml:"-"`
Timeout config.Duration `toml:"timeout"`
Expand Down Expand Up @@ -151,8 +151,13 @@ func (d *Dynatrace) send(msg string) error {
}
req.Header.Add("Content-Type", "text/plain; charset=UTF-8")

if len(d.APIToken) != 0 {
req.Header.Add("Authorization", "Api-Token "+d.APIToken)
if !d.APIToken.Empty() {
token, err := d.APIToken.Get()
if err != nil {
return fmt.Errorf("getting token failed: %w", err)
}
req.Header.Add("Authorization", "Api-Token "+string(token))
config.ReleaseSecret(token)
}
// add user-agent header to identify metric source
req.Header.Add("User-Agent", "telegraf")
Expand Down Expand Up @@ -184,7 +189,7 @@ func (d *Dynatrace) Init() error {
d.Log.Infof("Dynatrace URL is empty, defaulting to OneAgent metrics interface")
d.URL = apiconstants.GetDefaultOneAgentEndpoint()
}
if d.URL != apiconstants.GetDefaultOneAgentEndpoint() && len(d.APIToken) == 0 {
if d.URL != apiconstants.GetDefaultOneAgentEndpoint() && d.APIToken.Empty() {
d.Log.Errorf("Dynatrace api_token is a required field for Dynatrace output")
return fmt.Errorf("api_token is a required field for Dynatrace output")
}
Expand Down
24 changes: 12 additions & 12 deletions plugins/outputs/dynatrace/dynatrace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestNilMetrics(t *testing.T) {
}

d.URL = ts.URL
d.APIToken = "123"
d.APIToken = config.NewSecret([]byte("123"))
d.Log = testutil.Logger{}
err := d.Init()
require.NoError(t, err)
Expand All @@ -58,7 +58,7 @@ func TestEmptyMetricsSlice(t *testing.T) {
d := &Dynatrace{}

d.URL = ts.URL
d.APIToken = "123"
d.APIToken = config.NewSecret([]byte("123"))
d.Log = testutil.Logger{}

err := d.Init()
Expand All @@ -82,7 +82,7 @@ func TestMockURL(t *testing.T) {
d := &Dynatrace{}

d.URL = ts.URL
d.APIToken = "123"
d.APIToken = config.NewSecret([]byte("123"))
d.Log = testutil.Logger{}

err := d.Init()
Expand Down Expand Up @@ -153,7 +153,7 @@ func TestSendMetrics(t *testing.T) {

d := &Dynatrace{
URL: ts.URL,
APIToken: "123",
APIToken: config.NewSecret([]byte("123")),
Log: testutil.Logger{},
AddCounterMetrics: []string{},
}
Expand Down Expand Up @@ -230,7 +230,7 @@ func TestSendSingleMetricWithUnorderedTags(t *testing.T) {
d := &Dynatrace{}

d.URL = ts.URL
d.APIToken = "123"
d.APIToken = config.NewSecret([]byte("123"))
d.Log = testutil.Logger{}
err := d.Init()
require.NoError(t, err)
Expand Down Expand Up @@ -271,7 +271,7 @@ func TestSendMetricWithoutTags(t *testing.T) {
d := &Dynatrace{}

d.URL = ts.URL
d.APIToken = "123"
d.APIToken = config.NewSecret([]byte("123"))
d.Log = testutil.Logger{}
err := d.Init()
require.NoError(t, err)
Expand Down Expand Up @@ -318,7 +318,7 @@ func TestSendMetricWithUpperCaseTagKeys(t *testing.T) {
d := &Dynatrace{}

d.URL = ts.URL
d.APIToken = "123"
d.APIToken = config.NewSecret([]byte("123"))
d.Log = testutil.Logger{}
err := d.Init()
require.NoError(t, err)
Expand Down Expand Up @@ -359,7 +359,7 @@ func TestSendBooleanMetricWithoutTags(t *testing.T) {
d := &Dynatrace{}

d.URL = ts.URL
d.APIToken = "123"
d.APIToken = config.NewSecret([]byte("123"))
d.Log = testutil.Logger{}
err := d.Init()
require.NoError(t, err)
Expand Down Expand Up @@ -402,7 +402,7 @@ func TestSendMetricWithDefaultDimensions(t *testing.T) {
d := &Dynatrace{DefaultDimensions: map[string]string{"dim": "value"}}

d.URL = ts.URL
d.APIToken = "123"
d.APIToken = config.NewSecret([]byte("123"))
d.Log = testutil.Logger{}
err := d.Init()
require.NoError(t, err)
Expand Down Expand Up @@ -445,7 +445,7 @@ func TestMetricDimensionsOverrideDefault(t *testing.T) {
d := &Dynatrace{DefaultDimensions: map[string]string{"dim": "default"}}

d.URL = ts.URL
d.APIToken = "123"
d.APIToken = config.NewSecret([]byte("123"))
d.Log = testutil.Logger{}
err := d.Init()
require.NoError(t, err)
Expand Down Expand Up @@ -487,7 +487,7 @@ func TestStaticDimensionsOverrideMetric(t *testing.T) {
d := &Dynatrace{DefaultDimensions: map[string]string{"dim": "default"}}

d.URL = ts.URL
d.APIToken = "123"
d.APIToken = config.NewSecret([]byte("123"))
d.Log = testutil.Logger{}
err := d.Init()
require.NoError(t, err)
Expand Down Expand Up @@ -533,7 +533,7 @@ func TestSendUnsupportedMetric(t *testing.T) {
logStub := loggerStub{}

d.URL = ts.URL
d.APIToken = "123"
d.APIToken = config.NewSecret([]byte("123"))
d.Log = logStub
err := d.Init()
require.NoError(t, err)
Expand Down
53 changes: 38 additions & 15 deletions plugins/outputs/elasticsearch/elasticsearch.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (
var sampleConfig string

type Elasticsearch struct {
AuthBearerToken string `toml:"auth_bearer_token"`
AuthBearerToken config.Secret `toml:"auth_bearer_token"`
DefaultPipeline string `toml:"default_pipeline"`
DefaultTagValue string `toml:"default_tag_value"`
EnableGzip bool `toml:"enable_gzip"`
Expand All @@ -40,12 +40,12 @@ type Elasticsearch struct {
IndexName string `toml:"index_name"`
ManageTemplate bool `toml:"manage_template"`
OverwriteTemplate bool `toml:"overwrite_template"`
Password string `toml:"password"`
Username config.Secret `toml:"username"`
Password config.Secret `toml:"password"`
TemplateName string `toml:"template_name"`
Timeout config.Duration `toml:"timeout"`
URLs []string `toml:"urls"`
UsePipeline string `toml:"use_pipeline"`
Username string `toml:"username"`
Log telegraf.Logger `toml:"-"`
majorReleaseNumber int
pipelineName string
Expand Down Expand Up @@ -182,19 +182,11 @@ func (a *Elasticsearch) Connect() error {
elastic.SetGzip(a.EnableGzip),
)

if a.Username != "" && a.Password != "" {
clientOptions = append(clientOptions,
elastic.SetBasicAuth(a.Username, a.Password),
)
}

if a.AuthBearerToken != "" {
clientOptions = append(clientOptions,
elastic.SetHeaders(http.Header{
"Authorization": []string{fmt.Sprintf("Bearer %s", a.AuthBearerToken)},
}),
)
authOptions, err := a.getAuthOptions()
if err != nil {
return err
}
clientOptions = append(clientOptions, authOptions...)

if time.Duration(a.HealthCheckInterval) == 0 {
clientOptions = append(clientOptions,
Expand Down Expand Up @@ -470,6 +462,37 @@ func (a *Elasticsearch) Close() error {
return nil
}

func (a *Elasticsearch) getAuthOptions() ([]elastic.ClientOptionFunc, error) {
var fns []elastic.ClientOptionFunc

if !a.Username.Empty() && !a.Password.Empty() {
username, err := a.Username.Get()
if err != nil {
return nil, fmt.Errorf("getting username failed: %w", err)
}
defer config.ReleaseSecret(username)
password, err := a.Password.Get()
if err != nil {
return nil, fmt.Errorf("getting password failed: %w", err)
}
defer config.ReleaseSecret(password)

fns = append(fns, elastic.SetBasicAuth(string(username), string(password)))
}

if !a.AuthBearerToken.Empty() {
token, err := a.AuthBearerToken.Get()
if err != nil {
return nil, fmt.Errorf("getting token failed: %w", err)
}
defer config.ReleaseSecret(token)

auth := []string{"Bearer " + string(token)}
fns = append(fns, elastic.SetHeaders(http.Header{"Authorization": auth}))
}
return fns, nil
}

func init() {
outputs.Add("elasticsearch", func() telegraf.Output {
return &Elasticsearch{
Expand Down
2 changes: 1 addition & 1 deletion plugins/outputs/elasticsearch/elasticsearch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ func TestAuthorizationHeaderWhenBearerTokenIsPresent(t *testing.T) {
EnableGzip: false,
ManageTemplate: false,
Log: testutil.Logger{},
AuthBearerToken: "0123456789abcdef",
AuthBearerToken: config.NewSecret([]byte("0123456789abcdef")),
}

err := e.Connect()
Expand Down
23 changes: 17 additions & 6 deletions plugins/outputs/groundwork/groundwork.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/hashicorp/go-uuid"

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/outputs"
)

Expand All @@ -31,8 +32,8 @@ type metricMeta struct {
type Groundwork struct {
Server string `toml:"url"`
AgentID string `toml:"agent_id"`
Username string `toml:"username"`
Password string `toml:"password"`
Username config.Secret `toml:"username"`
Password config.Secret `toml:"password"`
DefaultAppType string `toml:"default_app_type"`
DefaultHost string `toml:"default_host"`
DefaultServiceState string `toml:"default_service_state"`
Expand All @@ -53,10 +54,10 @@ func (g *Groundwork) Init() error {
if g.AgentID == "" {
return errors.New(`no "agent_id" provided`)
}
if g.Username == "" {
if g.Username.Empty() {
return errors.New(`no "username" provided`)
}
if g.Password == "" {
if g.Password.Empty() {
return errors.New(`no "password" provided`)
}
if g.DefaultAppType == "" {
Expand All @@ -72,13 +73,23 @@ func (g *Groundwork) Init() error {
return errors.New(`invalid "default_service_state" provided`)
}

username, err := g.Username.Get()
if err != nil {
return fmt.Errorf("getting username failed: %w", err)
}
defer config.ReleaseSecret(username)
password, err := g.Password.Get()
if err != nil {
return fmt.Errorf("getting password failed: %w", err)
}
defer config.ReleaseSecret(password)
g.client = clients.GWClient{
AppName: "telegraf",
AppType: g.DefaultAppType,
GWConnection: &clients.GWConnection{
HostName: g.Server,
UserName: g.Username,
Password: g.Password,
UserName: string(username),
Password: string(password),
IsDynamicInventory: true,
},
}
Expand Down
Loading

0 comments on commit f5c2c4a

Please sign in to comment.