From 354ad9b5f67a8559a994a2b62647b00cf51c422e Mon Sep 17 00:00:00 2001 From: bchen528 Date: Mon, 24 Feb 2025 22:54:38 +0000 Subject: [PATCH 1/6] Add support for multiple bearer tokens --- .../bearertokenauth.go | 75 +++++++++---- .../bearertokenauth_test.go | 102 +++++++++++++++++- extension/bearertokenauthextension/config.go | 7 +- .../bearertokenauthextension/config_test.go | 23 ++++ .../testdata/config.yaml | 12 +++ 5 files changed, 195 insertions(+), 24 deletions(-) diff --git a/extension/bearertokenauthextension/bearertokenauth.go b/extension/bearertokenauthextension/bearertokenauth.go index 28ecbf965e082..04f1f8df20043 100644 --- a/extension/bearertokenauthextension/bearertokenauth.go +++ b/extension/bearertokenauthextension/bearertokenauth.go @@ -10,6 +10,7 @@ import ( "fmt" "net/http" "os" + "strings" "sync/atomic" "github.com/fsnotify/fsnotify" @@ -43,8 +44,8 @@ var ( // BearerTokenAuth is an implementation of auth.Client. It embeds a static authorization "bearer" token in every rpc call. type BearerTokenAuth struct { - scheme string - authorizationValueAtomic atomic.Value + scheme string + authorizationValuesAtomic atomic.Value shutdownCH chan struct{} @@ -55,15 +56,25 @@ type BearerTokenAuth struct { var _ auth.Client = (*BearerTokenAuth)(nil) func newBearerTokenAuth(cfg *Config, logger *zap.Logger) *BearerTokenAuth { - if cfg.Filename != "" && cfg.BearerToken != "" { - logger.Warn("a filename is specified. Configured token is ignored!") + if cfg.Filename != "" && (cfg.BearerToken != "" || len(cfg.Tokens) > 0) { + logger.Warn("a filename is specified. Configured token(s) is ignored!") } a := &BearerTokenAuth{ scheme: cfg.Scheme, filename: cfg.Filename, logger: logger, } - a.setAuthorizationValue(string(cfg.BearerToken)) + if len(cfg.Tokens) > 0 { + tokens := make([]string, len(cfg.Tokens)) + for i, token := range cfg.Tokens { + tokens[i] = string(token) + } + a.setAuthorizationValues(tokens) // Store tokens + } else if cfg.BearerToken != "" { + a.setAuthorizationValues([]string{string(cfg.BearerToken)}) // Store token + } else if cfg.Filename != "" { + a.refreshToken() // Load tokens from file + } return a } @@ -129,28 +140,48 @@ func (b *BearerTokenAuth) startWatcher(ctx context.Context, watcher *fsnotify.Wa } } +// Reloads token from file func (b *BearerTokenAuth) refreshToken() { b.logger.Info("refresh token", zap.String("filename", b.filename)) - token, err := os.ReadFile(b.filename) + tokenData, err := os.ReadFile(b.filename) if err != nil { b.logger.Error(err.Error()) return } - b.setAuthorizationValue(string(token)) + + tokens := strings.Split(string(tokenData), "\n") + for i, token := range tokens { + tokens[i] = strings.TrimSpace(token) + } + b.setAuthorizationValues(tokens) // Stores new tokens } -func (b *BearerTokenAuth) setAuthorizationValue(token string) { - value := token - if b.scheme != "" { - value = b.scheme + " " + value +func (b *BearerTokenAuth) setAuthorizationValues(tokens []string) { + values := make([]string, len(tokens)) + for i, token := range tokens { + if b.scheme != "" { + values[i] = b.scheme + " " + token + } else { + values[i] = token + } } - b.authorizationValueAtomic.Store(value) + b.authorizationValuesAtomic.Store(values) } -// authorizationValue returns the Authorization header/metadata value +// authorizationValues returns the Authorization header/metadata values +// to set for client auth, and expected values for server auth. +func (b *BearerTokenAuth) authorizationValues() []string { + return b.authorizationValuesAtomic.Load().([]string) +} + +// authorizationValue returns the first Authorization header/metadata value // to set for client auth, and expected value for server auth. func (b *BearerTokenAuth) authorizationValue() string { - return b.authorizationValueAtomic.Load().(string) + values := b.authorizationValues() + if len(values) > 0 { + return values[0] // Return the first token + } + return "" } // Shutdown of BearerTokenAuth does nothing and returns nil @@ -183,7 +214,7 @@ func (b *BearerTokenAuth) RoundTripper(base http.RoundTripper) (http.RoundTrippe }, nil } -// Authenticate checks whether the given context contains valid auth data. +// Authenticate checks whether the given context contains valid auth data. Validates tokens from clients trying to access the service (incoming requests) func (b *BearerTokenAuth) Authenticate(ctx context.Context, headers map[string][]string) (context.Context, error) { auth, ok := headers["authorization"] if !ok { @@ -192,12 +223,14 @@ func (b *BearerTokenAuth) Authenticate(ctx context.Context, headers map[string][ if !ok || len(auth) == 0 { return ctx, errors.New("missing or empty authorization header") } - token := auth[0] - expect := b.authorizationValue() - if subtle.ConstantTimeCompare([]byte(expect), []byte(token)) == 0 { - return ctx, fmt.Errorf("scheme or token does not match: %s", token) + token := auth[0] // Extract token from authorization header + expectedTokens := b.authorizationValues() + for _, expectedToken := range expectedTokens { + if subtle.ConstantTimeCompare([]byte(expectedToken), []byte(token)) == 1 { + return ctx, nil // Authentication successful, token is valid + } } - return ctx, nil + return ctx, fmt.Errorf("scheme or token does not match: %s", token) // Token is invalid } // BearerAuthRoundTripper intercepts and adds Bearer token Authorization headers to each http request. @@ -206,7 +239,7 @@ type BearerAuthRoundTripper struct { auth *BearerTokenAuth } -// RoundTrip modifies the original request and adds Bearer token Authorization headers. +// RoundTrip modifies the original request and adds Bearer token Authorization headers. Incoming requests support multiple tokens, but outgoing requests only use one. func (interceptor *BearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { req2 := req.Clone(req.Context()) if req2.Header == nil { diff --git a/extension/bearertokenauthextension/bearertokenauth_test.go b/extension/bearertokenauthextension/bearertokenauth_test.go index b454d9580ad41..5dc74ba294eb7 100644 --- a/extension/bearertokenauthextension/bearertokenauth_test.go +++ b/extension/bearertokenauthextension/bearertokenauth_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configopaque" "go.uber.org/zap/zaptest" ) @@ -226,7 +227,7 @@ func TestBearerTokenUpdateForGrpc(t *testing.T) { assert.Equal(t, map[string]string{"authorization": "Bearer " + "1234"}, md) // update the token - bauth.setAuthorizationValue("5678") + bauth.setAuthorizationValues([]string{"5678"}) md, err = perRPCAuth.GetRequestMetadata(context.Background()) assert.NoError(t, err) assert.Equal(t, map[string]string{"authorization": "Bearer " + "5678"}, md) @@ -284,3 +285,102 @@ func TestBearerServerAuthenticate(t *testing.T) { assert.NoError(t, bauth.Shutdown(context.Background())) } + +func TestBearerTokenMultipleTokens(t *testing.T) { + cfg := createDefaultConfig().(*Config) + cfg.Scheme = "Bearer" + cfg.Tokens = []configopaque.String{"token1", "token2"} + + bauth := newBearerTokenAuth(cfg, zaptest.NewLogger(t)) + assert.NotNil(t, bauth) + + assert.NoError(t, bauth.Start(context.Background(), componenttest.NewNopHost())) + credential, err := bauth.PerRPCCredentials() + assert.NoError(t, err) + assert.NotNil(t, credential) + + md, err := credential.GetRequestMetadata(context.Background()) + expectedMd := map[string]string{ + "authorization": "Bearer token1", + } + assert.Equal(t, expectedMd, md) + assert.NoError(t, err) + assert.True(t, credential.RequireTransportSecurity()) + + // Test Authenticate with multiple tokens + headers := map[string][]string{ + "authorization": {"Bearer token1"}, + } + ctx := context.Background() + newCtx, err := bauth.Authenticate(ctx, headers) + assert.NoError(t, err) + assert.Equal(t, ctx, newCtx) + + headers = map[string][]string{ + "authorization": {"Bearer token2"}, + } + newCtx, err = bauth.Authenticate(ctx, headers) + assert.NoError(t, err) + assert.Equal(t, ctx, newCtx) + + headers = map[string][]string{ + "authorization": {"Bearer invalidtoken"}, + } + _, err = bauth.Authenticate(ctx, headers) + assert.Error(t, err) + + assert.NoError(t, bauth.Shutdown(context.Background())) +} + +func TestBearerTokenMultipleTokensInFile(t *testing.T) { + scheme := "Bearer" + filename := filepath.Join("testdata", t.Name()+".tokens") + fileContent := "token1\ntoken2" + err := os.WriteFile(filename, []byte(fileContent), 0644) + assert.NoError(t, err) + defer os.Remove(filename) + + cfg := createDefaultConfig().(*Config) + cfg.Scheme = scheme + cfg.Filename = filename + + bauth := newBearerTokenAuth(cfg, zaptest.NewLogger(t)) + assert.NotNil(t, bauth) + + assert.NoError(t, bauth.Start(context.Background(), componenttest.NewNopHost())) + credential, err := bauth.PerRPCCredentials() + assert.NoError(t, err) + assert.NotNil(t, credential) + + md, err := credential.GetRequestMetadata(context.Background()) + expectedMd := map[string]string{ + "authorization": "Bearer token1", + } + assert.Equal(t, expectedMd, md) + assert.NoError(t, err) + assert.True(t, credential.RequireTransportSecurity()) + + // Test Authenticate with multiple tokens + headers := map[string][]string{ + "authorization": {"Bearer token1"}, + } + ctx := context.Background() + newCtx, err := bauth.Authenticate(ctx, headers) + assert.NoError(t, err) + assert.Equal(t, ctx, newCtx) + + headers = map[string][]string{ + "authorization": {"Bearer token2"}, + } + newCtx, err = bauth.Authenticate(ctx, headers) + assert.NoError(t, err) + assert.Equal(t, ctx, newCtx) + + headers = map[string][]string{ + "authorization": {"Bearer invalidtoken"}, + } + _, err = bauth.Authenticate(ctx, headers) + assert.Error(t, err) + + assert.NoError(t, bauth.Shutdown(context.Background())) +} diff --git a/extension/bearertokenauthextension/config.go b/extension/bearertokenauthextension/config.go index f136d635ea326..6c2f062b31db0 100644 --- a/extension/bearertokenauthextension/config.go +++ b/extension/bearertokenauthextension/config.go @@ -18,7 +18,10 @@ type Config struct { // BearerToken specifies the bearer token to use for every RPC. BearerToken configopaque.String `mapstructure:"token,omitempty"` - // Filename points to a file that contains the bearer token to use for every RPC. + // Tokens specifies multiple bearer tokens to use for every RPC. + Tokens []configopaque.String `mapstructure:"tokens,omitempty"` + + // Filename points to a file that contains the bearer token(s) to use for every RPC. Filename string `mapstructure:"filename,omitempty"` } @@ -29,7 +32,7 @@ var ( // Validate checks if the extension configuration is valid func (cfg *Config) Validate() error { - if cfg.BearerToken == "" && cfg.Filename == "" { + if cfg.BearerToken == "" && len(cfg.Tokens) == 0 && cfg.Filename == "" { return errNoTokenProvided } return nil diff --git a/extension/bearertokenauthextension/config_test.go b/extension/bearertokenauthextension/config_test.go index e2c62e2cde9a0..cbc2fb54c89cf 100644 --- a/extension/bearertokenauthextension/config_test.go +++ b/extension/bearertokenauthextension/config_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configopaque" "go.opentelemetry.io/collector/confmap/confmaptest" "go.opentelemetry.io/collector/confmap/xconfmap" @@ -42,6 +43,28 @@ func TestLoadConfig(t *testing.T) { BearerToken: "my-token", }, }, + { + id: component.NewIDWithName(metadata.Type, "multipletokens"), + expected: &Config{ + Scheme: "Bearer", + Tokens: []configopaque.String{"token1", "thistokenalsoworks"}, + }, + }, + { + id: component.NewIDWithName(metadata.Type, "withfilename"), + expected: &Config{ + Scheme: "Bearer", + Filename: "file-containing.token", + }, + }, + { + id: component.NewIDWithName(metadata.Type, "both"), + expected: &Config{ + Scheme: "Bearer", + BearerToken: "ignoredtoken", + Filename: "file-containing.token", + }, + }, } for _, tt := range tests { t.Run(tt.id.String(), func(t *testing.T) { diff --git a/extension/bearertokenauthextension/testdata/config.yaml b/extension/bearertokenauthextension/testdata/config.yaml index 45feaf6337d24..9bb3327b9d044 100644 --- a/extension/bearertokenauthextension/testdata/config.yaml +++ b/extension/bearertokenauthextension/testdata/config.yaml @@ -4,3 +4,15 @@ bearertokenauth/sometoken: bearertokenauth/withscheme: scheme: MyScheme token: "my-token" +bearertokenauth/multipletokens: + scheme: "Bearer" + tokens: + - "token1" + - "thistokenalsoworks" +bearertokenauth/withfilename: + scheme: "Bearer" + filename: "file-containing.token" +bearertokenauth/both: + scheme: "Bearer" + token: "ignoredtoken" + filename: "file-containing.token" From ef127d236527b815d62d4fcf9609188bc0a5342f Mon Sep 17 00:00:00 2001 From: bchen528 Date: Mon, 24 Feb 2025 23:04:49 +0000 Subject: [PATCH 2/6] Update docs --- extension/bearertokenauthextension/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/extension/bearertokenauthextension/README.md b/extension/bearertokenauthextension/README.md index a70c4d226b619..9bbbbd89e2678 100644 --- a/extension/bearertokenauthextension/README.md +++ b/extension/bearertokenauthextension/README.md @@ -25,6 +25,8 @@ The authenticator type has to be set to `bearertokenauth`. - `token`: Static authorization token that needs to be sent on every gRPC client call as metadata. +- `tokens`: A list of static authorization tokens that needs to be sent on every gRPC client call as metadata. + - `filename`: Name of file that contains a authorization token that needs to be sent in every client call. Either one of `token` or `filename` field is required. If both are specified, then the `token` field value is **ignored**. In any case, the value of the token will be prepended by `${scheme}` before being sent as a value of "authorization" key in the request header in case of HTTP and metadata in case of gRPC. @@ -40,6 +42,11 @@ extensions: bearertokenauth/withscheme: scheme: "Bearer" token: "randomtoken" + bearertokenauth/multipletokens: + scheme: "Bearer" + tokens: + - "randomtoken" + - "thistokenalsoworks" receivers: hostmetrics: From 73ae81a5cc68195ac97bcb04ee39b649ef28fe2b Mon Sep 17 00:00:00 2001 From: bchen528 Date: Mon, 24 Feb 2025 23:18:53 +0000 Subject: [PATCH 3/6] Add changelog --- .chloggen/multiple_bearer_tokens_support.yaml | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .chloggen/multiple_bearer_tokens_support.yaml diff --git a/.chloggen/multiple_bearer_tokens_support.yaml b/.chloggen/multiple_bearer_tokens_support.yaml new file mode 100644 index 0000000000000..1c5838ffc2a16 --- /dev/null +++ b/.chloggen/multiple_bearer_tokens_support.yaml @@ -0,0 +1,27 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: bearertokenauthextension + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add the ability to configure multiple bearer tokens for the same endpoint. + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [38148] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [user, api] From 5c42e010e9e507d50622f5a1e84c322e7c1f5464 Mon Sep 17 00:00:00 2001 From: bchen528 Date: Wed, 26 Feb 2025 15:02:18 +0000 Subject: [PATCH 4/6] Add validation where tokens and token cannot coexist in config --- extension/bearertokenauthextension/README.md | 2 +- extension/bearertokenauthextension/config.go | 4 ++++ .../bearertokenauthextension/config_test.go | 17 +++++++++++++++++ .../testdata/config.yaml | 12 ++++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/extension/bearertokenauthextension/README.md b/extension/bearertokenauthextension/README.md index 9bbbbd89e2678..0c5ed73098c00 100644 --- a/extension/bearertokenauthextension/README.md +++ b/extension/bearertokenauthextension/README.md @@ -25,7 +25,7 @@ The authenticator type has to be set to `bearertokenauth`. - `token`: Static authorization token that needs to be sent on every gRPC client call as metadata. -- `tokens`: A list of static authorization tokens that needs to be sent on every gRPC client call as metadata. +- `tokens`: A list of static authorization tokens, one of which needs to be sent on every gRPC client call as metadata. - `filename`: Name of file that contains a authorization token that needs to be sent in every client call. diff --git a/extension/bearertokenauthextension/config.go b/extension/bearertokenauthextension/config.go index 6c2f062b31db0..d55098a79984c 100644 --- a/extension/bearertokenauthextension/config.go +++ b/extension/bearertokenauthextension/config.go @@ -28,6 +28,7 @@ type Config struct { var ( _ component.Config = (*Config)(nil) errNoTokenProvided = errors.New("no bearer token provided") + errTokensAndTokenProvided = errors.New("either tokens or token should be provided, not both") ) // Validate checks if the extension configuration is valid @@ -35,5 +36,8 @@ func (cfg *Config) Validate() error { if cfg.BearerToken == "" && len(cfg.Tokens) == 0 && cfg.Filename == "" { return errNoTokenProvided } + if cfg.BearerToken != "" && len(cfg.Tokens) > 0 { + return errTokensAndTokenProvided + } return nil } diff --git a/extension/bearertokenauthextension/config_test.go b/extension/bearertokenauthextension/config_test.go index cbc2fb54c89cf..0894922ff92f1 100644 --- a/extension/bearertokenauthextension/config_test.go +++ b/extension/bearertokenauthextension/config_test.go @@ -65,6 +65,23 @@ func TestLoadConfig(t *testing.T) { Filename: "file-containing.token", }, }, + { + id: component.NewIDWithName(metadata.Type, "tokensandtoken"), + expected: &Config{ + Scheme: "Bearer", + BearerToken: "sometoken", + Tokens: []configopaque.String{"token1", "thistokenalsoworks"}, + }, + expectedErr: true, + }, + { + id: component.NewIDWithName(metadata.Type, "withtokensandfilename"), + expected: &Config{ + Scheme: "Bearer", + Tokens: []configopaque.String{"ignoredtoken1", "ignoredtoken2"}, + Filename: "file-containing.token", + }, + }, } for _, tt := range tests { t.Run(tt.id.String(), func(t *testing.T) { diff --git a/extension/bearertokenauthextension/testdata/config.yaml b/extension/bearertokenauthextension/testdata/config.yaml index 9bb3327b9d044..47589d113376a 100644 --- a/extension/bearertokenauthextension/testdata/config.yaml +++ b/extension/bearertokenauthextension/testdata/config.yaml @@ -16,3 +16,15 @@ bearertokenauth/both: scheme: "Bearer" token: "ignoredtoken" filename: "file-containing.token" +bearertokenauth/withtokensandfilename: + scheme: "Bearer" + tokens: + - "ignoredtoken1" + - "ignoredtoken2" + filename: "file-containing.token" +bearertokenauth/tokensandtoken: + scheme: "Bearer" + tokens: + - "token1" + - "thistokenalsoworks" + token: "my-token" From 1da31b4c84661ef4a90a2d4b9a037cf4be698fd6 Mon Sep 17 00:00:00 2001 From: bchen528 Date: Thu, 27 Feb 2025 15:06:52 +0000 Subject: [PATCH 5/6] Fix lint --- extension/bearertokenauthextension/bearertokenauth_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/bearertokenauthextension/bearertokenauth_test.go b/extension/bearertokenauthextension/bearertokenauth_test.go index 5dc74ba294eb7..bcbde211f2601 100644 --- a/extension/bearertokenauthextension/bearertokenauth_test.go +++ b/extension/bearertokenauthextension/bearertokenauth_test.go @@ -336,7 +336,7 @@ func TestBearerTokenMultipleTokensInFile(t *testing.T) { scheme := "Bearer" filename := filepath.Join("testdata", t.Name()+".tokens") fileContent := "token1\ntoken2" - err := os.WriteFile(filename, []byte(fileContent), 0644) + err := os.WriteFile(filename, []byte(fileContent), 0600) assert.NoError(t, err) defer os.Remove(filename) From 7d79a9b5d5ab58c9c36eca4f5ad22b70658620fb Mon Sep 17 00:00:00 2001 From: bchen528 Date: Thu, 27 Feb 2025 15:29:25 +0000 Subject: [PATCH 6/6] Fix more linting errors --- .../bearertokenauth.go | 7 +- .../bearertokenauth_test.go | 88 +++++++++---------- extension/bearertokenauthextension/config.go | 6 +- .../bearertokenauthextension/config_test.go | 76 ++++++++-------- 4 files changed, 89 insertions(+), 88 deletions(-) diff --git a/extension/bearertokenauthextension/bearertokenauth.go b/extension/bearertokenauthextension/bearertokenauth.go index 04f1f8df20043..b367acc863705 100644 --- a/extension/bearertokenauthextension/bearertokenauth.go +++ b/extension/bearertokenauthextension/bearertokenauth.go @@ -64,15 +64,16 @@ func newBearerTokenAuth(cfg *Config, logger *zap.Logger) *BearerTokenAuth { filename: cfg.Filename, logger: logger, } - if len(cfg.Tokens) > 0 { + switch { + case len(cfg.Tokens) > 0: tokens := make([]string, len(cfg.Tokens)) for i, token := range cfg.Tokens { tokens[i] = string(token) } a.setAuthorizationValues(tokens) // Store tokens - } else if cfg.BearerToken != "" { + case cfg.BearerToken != "": a.setAuthorizationValues([]string{string(cfg.BearerToken)}) // Store token - } else if cfg.Filename != "" { + case cfg.Filename != "": a.refreshToken() // Load tokens from file } return a diff --git a/extension/bearertokenauthextension/bearertokenauth_test.go b/extension/bearertokenauthextension/bearertokenauth_test.go index bcbde211f2601..10ee9bbc1fafc 100644 --- a/extension/bearertokenauthextension/bearertokenauth_test.go +++ b/extension/bearertokenauthextension/bearertokenauth_test.go @@ -287,56 +287,56 @@ func TestBearerServerAuthenticate(t *testing.T) { } func TestBearerTokenMultipleTokens(t *testing.T) { - cfg := createDefaultConfig().(*Config) - cfg.Scheme = "Bearer" - cfg.Tokens = []configopaque.String{"token1", "token2"} - - bauth := newBearerTokenAuth(cfg, zaptest.NewLogger(t)) - assert.NotNil(t, bauth) - - assert.NoError(t, bauth.Start(context.Background(), componenttest.NewNopHost())) - credential, err := bauth.PerRPCCredentials() - assert.NoError(t, err) - assert.NotNil(t, credential) - - md, err := credential.GetRequestMetadata(context.Background()) - expectedMd := map[string]string{ - "authorization": "Bearer token1", - } - assert.Equal(t, expectedMd, md) - assert.NoError(t, err) - assert.True(t, credential.RequireTransportSecurity()) - - // Test Authenticate with multiple tokens - headers := map[string][]string{ - "authorization": {"Bearer token1"}, - } - ctx := context.Background() - newCtx, err := bauth.Authenticate(ctx, headers) - assert.NoError(t, err) - assert.Equal(t, ctx, newCtx) - - headers = map[string][]string{ - "authorization": {"Bearer token2"}, - } - newCtx, err = bauth.Authenticate(ctx, headers) - assert.NoError(t, err) - assert.Equal(t, ctx, newCtx) - - headers = map[string][]string{ - "authorization": {"Bearer invalidtoken"}, - } - _, err = bauth.Authenticate(ctx, headers) - assert.Error(t, err) - - assert.NoError(t, bauth.Shutdown(context.Background())) + cfg := createDefaultConfig().(*Config) + cfg.Scheme = "Bearer" + cfg.Tokens = []configopaque.String{"token1", "token2"} + + bauth := newBearerTokenAuth(cfg, zaptest.NewLogger(t)) + assert.NotNil(t, bauth) + + assert.NoError(t, bauth.Start(context.Background(), componenttest.NewNopHost())) + credential, err := bauth.PerRPCCredentials() + assert.NoError(t, err) + assert.NotNil(t, credential) + + md, err := credential.GetRequestMetadata(context.Background()) + expectedMd := map[string]string{ + "authorization": "Bearer token1", + } + assert.Equal(t, expectedMd, md) + assert.NoError(t, err) + assert.True(t, credential.RequireTransportSecurity()) + + // Test Authenticate with multiple tokens + headers := map[string][]string{ + "authorization": {"Bearer token1"}, + } + ctx := context.Background() + newCtx, err := bauth.Authenticate(ctx, headers) + assert.NoError(t, err) + assert.Equal(t, ctx, newCtx) + + headers = map[string][]string{ + "authorization": {"Bearer token2"}, + } + newCtx, err = bauth.Authenticate(ctx, headers) + assert.NoError(t, err) + assert.Equal(t, ctx, newCtx) + + headers = map[string][]string{ + "authorization": {"Bearer invalidtoken"}, + } + _, err = bauth.Authenticate(ctx, headers) + assert.Error(t, err) + + assert.NoError(t, bauth.Shutdown(context.Background())) } func TestBearerTokenMultipleTokensInFile(t *testing.T) { scheme := "Bearer" filename := filepath.Join("testdata", t.Name()+".tokens") fileContent := "token1\ntoken2" - err := os.WriteFile(filename, []byte(fileContent), 0600) + err := os.WriteFile(filename, []byte(fileContent), 0o600) assert.NoError(t, err) defer os.Remove(filename) diff --git a/extension/bearertokenauthextension/config.go b/extension/bearertokenauthextension/config.go index d55098a79984c..eec9f10531cc2 100644 --- a/extension/bearertokenauthextension/config.go +++ b/extension/bearertokenauthextension/config.go @@ -26,9 +26,9 @@ type Config struct { } var ( - _ component.Config = (*Config)(nil) - errNoTokenProvided = errors.New("no bearer token provided") - errTokensAndTokenProvided = errors.New("either tokens or token should be provided, not both") + _ component.Config = (*Config)(nil) + errNoTokenProvided = errors.New("no bearer token provided") + errTokensAndTokenProvided = errors.New("either tokens or token should be provided, not both") ) // Validate checks if the extension configuration is valid diff --git a/extension/bearertokenauthextension/config_test.go b/extension/bearertokenauthextension/config_test.go index 0894922ff92f1..81dd27ac35f8d 100644 --- a/extension/bearertokenauthextension/config_test.go +++ b/extension/bearertokenauthextension/config_test.go @@ -44,44 +44,44 @@ func TestLoadConfig(t *testing.T) { }, }, { - id: component.NewIDWithName(metadata.Type, "multipletokens"), - expected: &Config{ - Scheme: "Bearer", - Tokens: []configopaque.String{"token1", "thistokenalsoworks"}, - }, - }, - { - id: component.NewIDWithName(metadata.Type, "withfilename"), - expected: &Config{ - Scheme: "Bearer", - Filename: "file-containing.token", - }, - }, - { - id: component.NewIDWithName(metadata.Type, "both"), - expected: &Config{ - Scheme: "Bearer", - BearerToken: "ignoredtoken", - Filename: "file-containing.token", - }, - }, - { - id: component.NewIDWithName(metadata.Type, "tokensandtoken"), - expected: &Config{ - Scheme: "Bearer", - BearerToken: "sometoken", - Tokens: []configopaque.String{"token1", "thistokenalsoworks"}, - }, - expectedErr: true, - }, - { - id: component.NewIDWithName(metadata.Type, "withtokensandfilename"), - expected: &Config{ - Scheme: "Bearer", - Tokens: []configopaque.String{"ignoredtoken1", "ignoredtoken2"}, - Filename: "file-containing.token", - }, - }, + id: component.NewIDWithName(metadata.Type, "multipletokens"), + expected: &Config{ + Scheme: "Bearer", + Tokens: []configopaque.String{"token1", "thistokenalsoworks"}, + }, + }, + { + id: component.NewIDWithName(metadata.Type, "withfilename"), + expected: &Config{ + Scheme: "Bearer", + Filename: "file-containing.token", + }, + }, + { + id: component.NewIDWithName(metadata.Type, "both"), + expected: &Config{ + Scheme: "Bearer", + BearerToken: "ignoredtoken", + Filename: "file-containing.token", + }, + }, + { + id: component.NewIDWithName(metadata.Type, "tokensandtoken"), + expected: &Config{ + Scheme: "Bearer", + BearerToken: "sometoken", + Tokens: []configopaque.String{"token1", "thistokenalsoworks"}, + }, + expectedErr: true, + }, + { + id: component.NewIDWithName(metadata.Type, "withtokensandfilename"), + expected: &Config{ + Scheme: "Bearer", + Tokens: []configopaque.String{"ignoredtoken1", "ignoredtoken2"}, + Filename: "file-containing.token", + }, + }, } for _, tt := range tests { t.Run(tt.id.String(), func(t *testing.T) {