diff --git a/pkg/es/config/config.go b/pkg/es/config/config.go index 0900b252a2b9..f86d87201cc1 100644 --- a/pkg/es/config/config.go +++ b/pkg/es/config/config.go @@ -20,6 +20,8 @@ import ( "crypto/x509" "io/ioutil" "net/http" + "path/filepath" + "strings" "sync" "time" @@ -38,6 +40,7 @@ type Configuration struct { Servers []string Username string Password string + TokenFilePath string Sniffer bool // https://github.com/olivere/elastic/wiki/Sniffing MaxNumSpans int // defines maximum number of spans to fetch from storage per query MaxSpanAge time.Duration `yaml:"max_span_age"` // configures the maximum lookback on span reads @@ -76,6 +79,7 @@ type ClientBuilder interface { GetAllTagsAsFields() bool GetTagDotReplacement() string GetUseReadWriteAliases() bool + GetTokenFilePath() string } // NewClient creates a new ElasticSearch client @@ -224,6 +228,11 @@ func (c *Configuration) GetUseReadWriteAliases() bool { return c.UseReadWriteAliases } +// GetTokenFilePath returns file path containing the bearer token +func (c *Configuration) GetTokenFilePath() string { + return c.TokenFilePath +} + // getConfigOptions wraps the configs to feed to the ElasticSearch client init func (c *Configuration) getConfigOptions() ([]elastic.ClientOptionFunc, error) { options := []elastic.ClientOptionFunc{elastic.SetURL(c.Servers...), elastic.SetSniff(c.Sniffer)} @@ -240,7 +249,27 @@ func (c *Configuration) getConfigOptions() ([]elastic.ClientOptionFunc, error) { TLSClientConfig: ctlsConfig, } } else { - options = append(options, elastic.SetBasicAuth(c.Username, c.Password)) + if c.TokenFilePath != "" { + token, err := loadToken(c.TokenFilePath) + if err != nil { + return nil, err + } + wrapped := &http.Transport{} + if c.TLS.CaPath != "" { + ctls := &TLSConfig{CaPath: c.TLS.CaPath} + ca, err := ctls.loadCertificate() + if err != nil { + return nil, err + } + wrapped.TLSClientConfig = &tls.Config{RootCAs: ca} + } + httpClient.Transport = &tokenAuthTransport{ + token: token, + wrapped: wrapped, + } + } else { + options = append(options, elastic.SetBasicAuth(c.Username, c.Password)) + } } return options, nil } @@ -281,3 +310,22 @@ func (tlsConfig *TLSConfig) loadPrivateKey() (*tls.Certificate, error) { } return &privateKey, nil } + +// TokenAuthTransport +type tokenAuthTransport struct { + token string + wrapped *http.Transport +} + +func (tr *tokenAuthTransport) RoundTrip(r *http.Request) (*http.Response, error) { + r.Header.Set("Authorization", "Bearer "+tr.token) + return tr.wrapped.RoundTrip(r) +} + +func loadToken(path string) (string, error) { + b, err := ioutil.ReadFile(filepath.Clean(path)) + if err != nil { + return "", err + } + return strings.TrimRight(string(b), "\r\n"), nil +} diff --git a/plugin/storage/es/factory.go b/plugin/storage/es/factory.go index fd46943fde76..597d1c458194 100644 --- a/plugin/storage/es/factory.go +++ b/plugin/storage/es/factory.go @@ -21,6 +21,7 @@ import ( "path/filepath" "strings" + "github.com/pkg/errors" "github.com/spf13/viper" "github.com/uber/jaeger-lib/metrics" "go.uber.org/zap" @@ -76,12 +77,12 @@ func (f *Factory) Initialize(metricsFactory metrics.Factory, logger *zap.Logger) primaryClient, err := f.primaryConfig.NewClient(logger, metricsFactory) if err != nil { - return err + return errors.Wrap(err, "failed to create primary Elasticsearch client") } f.primaryClient = primaryClient archiveClient, err := f.archiveConfig.NewClient(logger, metricsFactory) if err != nil { - return err + return errors.Wrap(err, "failed to create archive Elasticsearch client") } f.archiveClient = archiveClient return nil diff --git a/plugin/storage/es/factory_test.go b/plugin/storage/es/factory_test.go index db04e42fdb2c..ded464329da0 100644 --- a/plugin/storage/es/factory_test.go +++ b/plugin/storage/es/factory_test.go @@ -53,11 +53,11 @@ func TestElasticsearchFactory(t *testing.T) { // after InitFromViper, f.primaryConfig points to a real session builder that will fail in unit tests, // so we override it with a mock. f.primaryConfig = &mockClientBuilder{err: errors.New("made-up error")} - assert.EqualError(t, f.Initialize(metrics.NullFactory, zap.NewNop()), "made-up error") + assert.EqualError(t, f.Initialize(metrics.NullFactory, zap.NewNop()), "failed to create primary Elasticsearch client: made-up error") f.primaryConfig = &mockClientBuilder{} f.archiveConfig = &mockClientBuilder{err: errors.New("made-up error2")} - assert.EqualError(t, f.Initialize(metrics.NullFactory, zap.NewNop()), "made-up error2") + assert.EqualError(t, f.Initialize(metrics.NullFactory, zap.NewNop()), "failed to create archive Elasticsearch client: made-up error2") f.archiveConfig = &mockClientBuilder{} assert.NoError(t, f.Initialize(metrics.NullFactory, zap.NewNop())) diff --git a/plugin/storage/es/options.go b/plugin/storage/es/options.go index d7d5619e54a9..b23423a3270b 100644 --- a/plugin/storage/es/options.go +++ b/plugin/storage/es/options.go @@ -28,6 +28,7 @@ const ( suffixUsername = ".username" suffixPassword = ".password" suffixSniffer = ".sniffer" + suffixTokenPath = ".token-file" suffixServerURLs = ".server-urls" suffixMaxSpanAge = ".max-span-age" suffixMaxNumSpans = ".max-num-spans" @@ -119,6 +120,10 @@ func addFlags(flagSet *flag.FlagSet, nsConfig *namespaceConfig) { nsConfig.namespace+suffixPassword, nsConfig.Password, "The password required by ElasticSearch") + flagSet.String( + nsConfig.namespace+suffixTokenPath, + nsConfig.TokenFilePath, + "Path to a file containing bearer token. This flag also uses CA if it is specified") flagSet.Bool( nsConfig.namespace+suffixSniffer, nsConfig.Sniffer, @@ -217,6 +222,7 @@ func (opt *Options) InitFromViper(v *viper.Viper) { func initFromViper(cfg *namespaceConfig, v *viper.Viper) { cfg.Username = v.GetString(cfg.namespace + suffixUsername) cfg.Password = v.GetString(cfg.namespace + suffixPassword) + cfg.TokenFilePath = v.GetString(cfg.namespace + suffixTokenPath) cfg.Sniffer = v.GetBool(cfg.namespace + suffixSniffer) cfg.servers = stripWhiteSpace(v.GetString(cfg.namespace + suffixServerURLs)) cfg.MaxSpanAge = v.GetDuration(cfg.namespace + suffixMaxSpanAge) diff --git a/plugin/storage/es/options_test.go b/plugin/storage/es/options_test.go index fee824e2465a..ddade1303ef7 100644 --- a/plugin/storage/es/options_test.go +++ b/plugin/storage/es/options_test.go @@ -47,6 +47,7 @@ func TestOptionsWithFlags(t *testing.T) { "--es.server-urls=1.1.1.1, 2.2.2.2", "--es.username=hello", "--es.password=world", + "--es.token-file=/foo/bar", "--es.sniffer=true", "--es.max-span-age=48h", "--es.num-shards=20", @@ -60,6 +61,7 @@ func TestOptionsWithFlags(t *testing.T) { primary := opts.GetPrimary() assert.Equal(t, "hello", primary.Username) + assert.Equal(t, "/foo/bar", primary.TokenFilePath) assert.Equal(t, []string{"1.1.1.1", "2.2.2.2"}, primary.Servers) assert.Equal(t, 48*time.Hour, primary.MaxSpanAge) assert.True(t, primary.Sniffer)