Skip to content

Commit e48b633

Browse files
committed
Passthrough OAuth bearer token supplied to Query service through to ES storage
Signed-off-by: Ruben Vargas <ruben.vp8510@gmail.com>
1 parent f62a0a3 commit e48b633

File tree

10 files changed

+132
-51
lines changed

10 files changed

+132
-51
lines changed

cmd/query/app/flags.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ import (
2323
)
2424

2525
const (
26-
queryPort = "query.port"
27-
queryBasePath = "query.base-path"
28-
queryStaticFiles = "query.static-files"
29-
queryUIConfig = "query.ui-config"
26+
queryPort = "query.port"
27+
queryBasePath = "query.base-path"
28+
queryStaticFiles = "query.static-files"
29+
queryUIConfig = "query.ui-config"
30+
queryTokenPropagation = "query.bearer-token-propagation"
3031
)
3132

3233
// QueryOptions holds configuration for query service
@@ -39,6 +40,8 @@ type QueryOptions struct {
3940
StaticAssets string
4041
// UIConfig is the path to a configuration file for the UI
4142
UIConfig string
43+
// BearerTokenPropagation activate/deactivate bearer token propagation to storage
44+
BearerTokenPropagation bool
4245
}
4346

4447
// AddFlags adds flags for QueryOptions
@@ -47,6 +50,8 @@ func AddFlags(flagSet *flag.FlagSet) {
4750
flagSet.String(queryBasePath, "/", "The base path for all HTTP routes, e.g. /jaeger; useful when running behind a reverse proxy")
4851
flagSet.String(queryStaticFiles, "", "The directory path override for the static assets for the UI")
4952
flagSet.String(queryUIConfig, "", "The path to the UI configuration file in JSON format")
53+
flagSet.Bool(queryTokenPropagation, true, "Allow propagation of bearer token to be used by storage plugins")
54+
5055
}
5156

5257
// InitFromViper initializes QueryOptions with properties from viper
@@ -55,5 +60,6 @@ func (qOpts *QueryOptions) InitFromViper(v *viper.Viper) *QueryOptions {
5560
qOpts.BasePath = v.GetString(queryBasePath)
5661
qOpts.StaticAssets = v.GetString(queryStaticFiles)
5762
qOpts.UIConfig = v.GetString(queryUIConfig)
63+
qOpts.BearerTokenPropagation = v.GetBool(queryTokenPropagation)
5864
return qOpts
5965
}

cmd/query/app/server.go

+23-3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/jaegertracing/jaeger/pkg/healthcheck"
3232
"github.com/jaegertracing/jaeger/pkg/recoveryhandler"
3333
"github.com/jaegertracing/jaeger/proto-gen/api_v2"
34+
"github.com/jaegertracing/jaeger/storage/spanstore"
3435
)
3536

3637
// Server runs HTTP, Mux and a grpc server
@@ -80,14 +81,33 @@ func createHTTPServer(querySvc *querysvc.QueryService, queryOpts *QueryOptions,
8081

8182
apiHandler.RegisterRoutes(r)
8283
RegisterStaticHandler(r, logger, queryOpts)
83-
compressHandler := handlers.CompressHandler(r)
84+
var handler http.Handler = r
85+
if queryOpts.BearerTokenPropagation {
86+
handler = bearTokenPropagationHandler(logger, r)
87+
}
88+
handler = handlers.CompressHandler(handler)
8489
recoveryHandler := recoveryhandler.NewRecoveryHandler(logger, true)
85-
8690
return &http.Server{
87-
Handler: recoveryHandler(compressHandler),
91+
Handler: recoveryHandler(handler),
8892
}
8993
}
9094

95+
func bearTokenPropagationHandler(logger *zap.Logger, h http.Handler) http.Handler {
96+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
97+
ctx := r.Context()
98+
authHeaderValue := r.Header.Get("Authorization")
99+
if authHeaderValue != "" {
100+
bearerToken := strings.Split(authHeaderValue, " ")
101+
if len(bearerToken) == 2 {
102+
h.ServeHTTP(w, r.WithContext(spanstore.ContextWithBearerToken(ctx, bearerToken[1])))
103+
}
104+
logger.Warn("Invalid authorization header, skipping bearer token propagation")
105+
} else {
106+
h.ServeHTTP(w, r.WithContext(ctx))
107+
}
108+
})
109+
}
110+
91111
// Start http, GRPC and cmux servers concurrently
92112
func (s *Server) Start() error {
93113
conn, err := net.Listen("tcp", fmt.Sprintf(":%d", s.queryOptions.Port))

cmd/query/main.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"github.com/jaegertracing/jaeger/plugin/storage"
3939
"github.com/jaegertracing/jaeger/ports"
4040
istorage "github.com/jaegertracing/jaeger/storage"
41+
"github.com/jaegertracing/jaeger/storage/spanstore"
4142
storageMetrics "github.com/jaegertracing/jaeger/storage/spanstore/metrics"
4243
)
4344

@@ -78,7 +79,9 @@ func main() {
7879
}
7980
defer closer.Close()
8081
opentracing.SetGlobalTracer(tracer)
81-
82+
queryOpts := new(app.QueryOptions).InitFromViper(v)
83+
// TODO: Need to figure out set enable/disable propagation on storage plugins.
84+
v.Set(spanstore.StoragePropagationKey, queryOpts.BearerTokenPropagation)
8285
storageFactory.InitFromViper(v)
8386
if err := storageFactory.Initialize(baseFactory, logger); err != nil {
8487
logger.Fatal("Failed to init storage factory", zap.Error(err))
@@ -98,7 +101,6 @@ func main() {
98101
dependencyReader,
99102
*queryServiceOptions)
100103

101-
queryOpts := new(app.QueryOptions).InitFromViper(v)
102104
server := app.NewServer(svc, queryService, queryOpts, tracer)
103105

104106
if err := server.Start(); err != nil {

pkg/es/config/config.go

+43-28
Original file line numberDiff line numberDiff line change
@@ -32,32 +32,34 @@ import (
3232

3333
"github.com/jaegertracing/jaeger/pkg/es"
3434
"github.com/jaegertracing/jaeger/pkg/es/wrapper"
35+
"github.com/jaegertracing/jaeger/storage/spanstore"
3536
storageMetrics "github.com/jaegertracing/jaeger/storage/spanstore/metrics"
3637
)
3738

3839
// Configuration describes the configuration properties needed to connect to an ElasticSearch cluster
3940
type Configuration struct {
40-
Servers []string
41-
Username string
42-
Password string
43-
TokenFilePath string
44-
Sniffer bool // https://github.com/olivere/elastic/wiki/Sniffing
45-
MaxNumSpans int // defines maximum number of spans to fetch from storage per query
46-
MaxSpanAge time.Duration `yaml:"max_span_age"` // configures the maximum lookback on span reads
47-
NumShards int64 `yaml:"shards"`
48-
NumReplicas int64 `yaml:"replicas"`
49-
Timeout time.Duration `validate:"min=500"`
50-
BulkSize int
51-
BulkWorkers int
52-
BulkActions int
53-
BulkFlushInterval time.Duration
54-
IndexPrefix string
55-
TagsFilePath string
56-
AllTagsAsFields bool
57-
TagDotReplacement string
58-
Enabled bool
59-
TLS TLSConfig
60-
UseReadWriteAliases bool
41+
Servers []string
42+
Username string
43+
Password string
44+
TokenFilePath string
45+
AllowTokenFromContext bool
46+
Sniffer bool // https://github.com/olivere/elastic/wiki/Sniffing
47+
MaxNumSpans int // defines maximum number of spans to fetch from storage per query
48+
MaxSpanAge time.Duration `yaml:"max_span_age"` // configures the maximum lookback on span reads
49+
NumShards int64 `yaml:"shards"`
50+
NumReplicas int64 `yaml:"replicas"`
51+
Timeout time.Duration `validate:"min=500"`
52+
BulkSize int
53+
BulkWorkers int
54+
BulkActions int
55+
BulkFlushInterval time.Duration
56+
IndexPrefix string
57+
TagsFilePath string
58+
AllTagsAsFields bool
59+
TagDotReplacement string
60+
Enabled bool
61+
TLS TLSConfig
62+
UseReadWriteAliases bool
6163
}
6264

6365
// TLSConfig describes the configuration properties to connect tls enabled ElasticSearch cluster
@@ -248,7 +250,8 @@ func (c *Configuration) IsEnabled() bool {
248250

249251
// getConfigOptions wraps the configs to feed to the ElasticSearch client init
250252
func (c *Configuration) getConfigOptions() ([]elastic.ClientOptionFunc, error) {
251-
options := []elastic.ClientOptionFunc{elastic.SetURL(c.Servers...), elastic.SetSniff(c.Sniffer)}
253+
options := []elastic.ClientOptionFunc{elastic.SetURL(c.Servers...), elastic.SetSniff(c.Sniffer),
254+
elastic.SetHealthcheck(!c.AllowTokenFromContext)}
252255
httpClient := &http.Client{
253256
Timeout: c.Timeout,
254257
}
@@ -271,14 +274,21 @@ func (c *Configuration) getConfigOptions() ([]elastic.ClientOptionFunc, error) {
271274
}
272275
httpTransport.TLSClientConfig = &tls.Config{RootCAs: ca}
273276
}
277+
278+
token := ""
274279
if c.TokenFilePath != "" {
275-
token, err := loadToken(c.TokenFilePath)
280+
tokenFromFile, err := loadToken(c.TokenFilePath)
276281
if err != nil {
277282
return nil, err
278283
}
284+
token = tokenFromFile
285+
}
286+
287+
if token != "" || c.AllowTokenFromContext {
279288
httpClient.Transport = &tokenAuthTransport{
280-
token: token,
281-
wrapped: httpTransport,
289+
token: token,
290+
allowOverrideFromCtx: c.AllowTokenFromContext,
291+
wrapped: httpTransport,
282292
}
283293
} else {
284294
httpClient.Transport = httpTransport
@@ -329,12 +339,17 @@ func (tlsConfig *TLSConfig) loadPrivateKey() (*tls.Certificate, error) {
329339

330340
// TokenAuthTransport
331341
type tokenAuthTransport struct {
332-
token string
333-
wrapped *http.Transport
342+
token string
343+
allowOverrideFromCtx bool
344+
wrapped *http.Transport
334345
}
335346

336347
func (tr *tokenAuthTransport) RoundTrip(r *http.Request) (*http.Response, error) {
337-
r.Header.Set("Authorization", "Bearer "+tr.token)
348+
token := tr.token
349+
if tr.allowOverrideFromCtx {
350+
token, _ = spanstore.GetBearerToken(r.Context())
351+
}
352+
r.Header.Set("Authorization", "Bearer "+token)
338353
return tr.wrapped.RoundTrip(r)
339354
}
340355

plugin/storage/es/options.go

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/spf13/viper"
2323

2424
"github.com/jaegertracing/jaeger/pkg/es/config"
25+
"github.com/jaegertracing/jaeger/storage/spanstore"
2526
)
2627

2728
const (
@@ -255,6 +256,8 @@ func initFromViper(cfg *namespaceConfig, v *viper.Viper) {
255256
cfg.TagDotReplacement = v.GetString(cfg.namespace + suffixTagDeDotChar)
256257
cfg.UseReadWriteAliases = v.GetBool(cfg.namespace + suffixReadAlias)
257258
cfg.Enabled = v.GetBool(cfg.namespace + suffixEnabled)
259+
// TODO: Need to figure out a better way for do this.
260+
cfg.AllowTokenFromContext = v.GetBool(spanstore.StoragePropagationKey)
258261
}
259262

260263
// GetPrimary returns primary configuration.

plugin/storage/es/spanstore/reader.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ func NewSpanReader(p SpanReaderParams) *SpanReader {
125125
logger: p.Logger,
126126
maxSpanAge: p.MaxSpanAge,
127127
maxNumSpans: p.MaxNumSpans,
128-
serviceOperationStorage: NewServiceOperationStorage(ctx, p.Client, p.Logger, 0), // the decorator takes care of metrics
128+
serviceOperationStorage: NewServiceOperationStorage(p.Client, p.Logger, 0), // the decorator takes care of metrics
129129
spanIndexPrefix: indexNames(p.IndexPrefix, spanIndex),
130130
serviceIndexPrefix: indexNames(p.IndexPrefix, serviceIndex),
131131
spanConverter: dbmodel.NewToDomain(p.TagDotReplacement),
@@ -226,22 +226,20 @@ func (s *SpanReader) unmarshalJSONSpan(esSpanRaw *elastic.SearchHit) (*dbmodel.S
226226

227227
// GetServices returns all services traced by Jaeger, ordered by frequency
228228
func (s *SpanReader) GetServices(ctx context.Context) ([]string, error) {
229-
//lint:ignore SA4006 failing to re-assign context is worse than unused variable
230229
span, ctx := opentracing.StartSpanFromContext(ctx, "GetServices")
231230
defer span.Finish()
232231
currentTime := time.Now()
233232
jaegerIndices := s.timeRangeIndices(s.serviceIndexPrefix, currentTime.Add(-s.maxSpanAge), currentTime)
234-
return s.serviceOperationStorage.getServices(jaegerIndices)
233+
return s.serviceOperationStorage.getServices(ctx, jaegerIndices)
235234
}
236235

237236
// GetOperations returns all operations for a specific service traced by Jaeger
238237
func (s *SpanReader) GetOperations(ctx context.Context, service string) ([]string, error) {
239-
//lint:ignore SA4006 failing to re-assign context is worse than unused variable
240238
span, ctx := opentracing.StartSpanFromContext(ctx, "GetOperations")
241239
defer span.Finish()
242240
currentTime := time.Now()
243241
jaegerIndices := s.timeRangeIndices(s.serviceIndexPrefix, currentTime.Add(-s.maxSpanAge), currentTime)
244-
return s.serviceOperationStorage.getOperations(jaegerIndices, service)
242+
return s.serviceOperationStorage.getOperations(ctx, jaegerIndices, service)
245243
}
246244

247245
func bucketToStringArray(buckets []*elastic.AggregationBucketKeyItem) ([]string, error) {

plugin/storage/es/spanstore/reader_test.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"errors"
2121
"fmt"
2222
"io/ioutil"
23+
"reflect"
2324
"testing"
2425
"time"
2526

@@ -749,7 +750,10 @@ func mockSearchService(r *spanReaderTest) *mock.Call {
749750
searchService.On("Aggregation", stringMatcher(operationsAggregation), mock.AnythingOfType("*elastic.TermsAggregation")).Return(searchService)
750751
searchService.On("Aggregation", stringMatcher(traceIDAggregation), mock.AnythingOfType("*elastic.TermsAggregation")).Return(searchService)
751752
r.client.On("Search", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(searchService)
752-
return searchService.On("Do", mock.AnythingOfType("*context.emptyCtx"))
753+
return searchService.On("Do", mock.MatchedBy(func(ctx context.Context) bool{
754+
t := reflect.TypeOf(ctx).String()
755+
return t == "*context.valueCtx" || t == "*context.emptyCtx"
756+
}))
753757
}
754758

755759
func TestTraceQueryParameterValidation(t *testing.T) {

plugin/storage/es/spanstore/service_operation.go

+4-7
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,18 @@ const (
3838

3939
// ServiceOperationStorage stores service to operation pairs.
4040
type ServiceOperationStorage struct {
41-
ctx context.Context
4241
client es.Client
4342
logger *zap.Logger
4443
serviceCache cache.Cache
4544
}
4645

4746
// NewServiceOperationStorage returns a new ServiceOperationStorage.
4847
func NewServiceOperationStorage(
49-
ctx context.Context,
5048
client es.Client,
5149
logger *zap.Logger,
5250
cacheTTL time.Duration,
5351
) *ServiceOperationStorage {
5452
return &ServiceOperationStorage{
55-
ctx: ctx,
5653
client: client,
5754
logger: logger,
5855
serviceCache: cache.NewLRUWithOptions(
@@ -79,7 +76,7 @@ func (s *ServiceOperationStorage) Write(indexName string, jsonSpan *dbmodel.Span
7976
}
8077
}
8178

82-
func (s *ServiceOperationStorage) getServices(indices []string) ([]string, error) {
79+
func (s *ServiceOperationStorage) getServices(context context.Context, indices []string) ([]string, error) {
8380
serviceAggregation := getServicesAggregation()
8481

8582
searchService := s.client.Search(indices...).
@@ -88,7 +85,7 @@ func (s *ServiceOperationStorage) getServices(indices []string) ([]string, error
8885
IgnoreUnavailable(true).
8986
Aggregation(servicesAggregation, serviceAggregation)
9087

91-
searchResult, err := searchService.Do(s.ctx)
88+
searchResult, err := searchService.Do(context)
9289
if err != nil {
9390
return nil, errors.Wrap(err, "Search service failed")
9491
}
@@ -109,7 +106,7 @@ func getServicesAggregation() elastic.Query {
109106
Size(defaultDocCount) // Must set to some large number. ES deprecated size omission for aggregating all. https://github.com/elastic/elasticsearch/issues/18838
110107
}
111108

112-
func (s *ServiceOperationStorage) getOperations(indices []string, service string) ([]string, error) {
109+
func (s *ServiceOperationStorage) getOperations(context context.Context, indices []string, service string) ([]string, error) {
113110
serviceQuery := elastic.NewTermQuery(serviceName, service)
114111
serviceFilter := getOperationsAggregation()
115112

@@ -120,7 +117,7 @@ func (s *ServiceOperationStorage) getOperations(indices []string, service string
120117
IgnoreUnavailable(true).
121118
Aggregation(operationsAggregation, serviceFilter)
122119

123-
searchResult, err := searchService.Do(s.ctx)
120+
searchResult, err := searchService.Do(context)
124121
if err != nil {
125122
return nil, errors.Wrap(err, "Search service failed")
126123
}

plugin/storage/es/spanstore/writer.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func NewSpanWriter(p SpanWriterParams) *SpanWriter {
7575
ctx := context.Background()
7676

7777
// TODO: Configurable TTL
78-
serviceOperationStorage := NewServiceOperationStorage(ctx, p.Client, p.Logger, time.Hour*12)
78+
serviceOperationStorage := NewServiceOperationStorage(p.Client, p.Logger, time.Hour*12)
7979
return &SpanWriter{
8080
ctx: ctx,
8181
client: p.Client,
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) 2019 The Jaeger Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package spanstore
16+
17+
import "context"
18+
19+
type contextKey string
20+
21+
const bearerToken = contextKey("bearer.token")
22+
23+
// StoragePropagationKey is a key for viper configuration to pass this option to storage plugins.
24+
const StoragePropagationKey = "storage.propagate.token"
25+
26+
// ContextWithBearerToken set bearer token in context
27+
func ContextWithBearerToken(ctx context.Context, token string) context.Context {
28+
return context.WithValue(ctx, bearerToken, token)
29+
30+
}
31+
32+
// GetBearerToken from context, or empty string if there is no token
33+
func GetBearerToken(ctx context.Context) (string, bool) {
34+
val, ok := ctx.Value(bearerToken).(string)
35+
return val, ok
36+
}

0 commit comments

Comments
 (0)