diff --git a/.chloggen/custom-bearertokenauth-header-field-name.yaml b/.chloggen/custom-bearertokenauth-header-field-name.yaml new file mode 100644 index 000000000000..6a76ed54300b --- /dev/null +++ b/.chloggen/custom-bearertokenauth-header-field-name.yaml @@ -0,0 +1,28 @@ +# 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: Enable custom header fields (instead of the default 'Authorization' header) to read the Bearer auth token from. + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [36985] + +# (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] + diff --git a/extension/bearertokenauthextension/README.md b/extension/bearertokenauthextension/README.md index a70c4d226b61..79b1b514ca43 100644 --- a/extension/bearertokenauthextension/README.md +++ b/extension/bearertokenauthextension/README.md @@ -23,6 +23,8 @@ The authenticator type has to be set to `bearertokenauth`. - `scheme`: Specifies the auth scheme name. Defaults to "Bearer". Optional. +- `header`: Specifies which header to read the Bearer token from. Defaults to 'Authorization'. + - `token`: Static authorization token 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. @@ -40,6 +42,10 @@ extensions: bearertokenauth/withscheme: scheme: "Bearer" token: "randomtoken" + bearertokenauth/withheader: + header: "X-CustomAuthorization" + scheme: "Bearer" + token: "randomtoken" receivers: hostmetrics: @@ -61,11 +67,16 @@ exporters: auth: authenticator: bearertokenauth/withscheme + otlphttp/withheader: + endpoint: http://localhost:9000 + auth: + authenticator: bearertokenauth/withheader + service: - extensions: [bearertokenauth, bearertokenauth/withscheme] + extensions: [bearertokenauth, bearertokenauth/withscheme, bearertokenauth/withheader] pipelines: metrics: receivers: [hostmetrics] processors: [] - exporters: [otlp/withauth, otlphttp/withauth] + exporters: [otlp/withauth, otlphttp/withauth, otlphttp/withheader] ``` diff --git a/extension/bearertokenauthextension/bearertokenauth.go b/extension/bearertokenauthextension/bearertokenauth.go index e7a8ad3e4212..289bb35c4333 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" @@ -44,6 +45,7 @@ var ( // BearerTokenAuth is an implementation of auth.Client. It embeds a static authorization "bearer" token in every rpc call. type BearerTokenAuth struct { scheme string + header string authorizationValueAtomic atomic.Value shutdownCH chan struct{} @@ -60,6 +62,7 @@ func newBearerTokenAuth(cfg *Config, logger *zap.Logger) *BearerTokenAuth { } a := &BearerTokenAuth{ scheme: cfg.Scheme, + header: cfg.Header, filename: cfg.Filename, logger: logger, } @@ -171,7 +174,7 @@ func (b *BearerTokenAuth) Shutdown(_ context.Context) error { // PerRPCCredentials returns PerRPCAuth an implementation of credentials.PerRPCCredentials that func (b *BearerTokenAuth) PerRPCCredentials() (credentials.PerRPCCredentials, error) { return &PerRPCAuth{ - metadata: map[string]string{"authorization": b.authorizationValue()}, + metadata: map[string]string{strings.ToLower(b.header): b.authorizationValue()}, }, nil } @@ -185,9 +188,9 @@ func (b *BearerTokenAuth) RoundTripper(base http.RoundTripper) (http.RoundTrippe // Authenticate checks whether the given context contains valid auth data. func (b *BearerTokenAuth) Authenticate(ctx context.Context, headers map[string][]string) (context.Context, error) { - auth, ok := headers["authorization"] + auth, ok := headers[strings.ToLower(b.header)] if !ok { - auth, ok = headers["Authorization"] + auth, ok = headers[b.header] } if !ok || len(auth) == 0 { return ctx, errors.New("missing or empty authorization header") @@ -212,6 +215,6 @@ func (interceptor *BearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.R if req2.Header == nil { req2.Header = make(http.Header) } - req2.Header.Set("Authorization", interceptor.auth.authorizationValue()) + req2.Header.Set(interceptor.auth.header, interceptor.auth.authorizationValue()) return interceptor.baseTransport.RoundTrip(req2) } diff --git a/extension/bearertokenauthextension/bearertokenauth_test.go b/extension/bearertokenauthextension/bearertokenauth_test.go index a6257a8511bd..9d6acf568403 100644 --- a/extension/bearertokenauthextension/bearertokenauth_test.go +++ b/extension/bearertokenauthextension/bearertokenauth_test.go @@ -6,9 +6,11 @@ package bearertokenauthextension import ( "context" "fmt" + "log" "net/http" "os" "path/filepath" + "strings" "testing" "time" @@ -19,7 +21,7 @@ import ( func TestPerRPCAuth(t *testing.T) { metadata := map[string]string{ - "authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + strings.ToLower(defaultHeader): "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", } // test meta data is properly @@ -60,7 +62,8 @@ func TestBearerAuthenticatorHttp(t *testing.T) { request := &http.Request{Method: http.MethodGet} resp, err := c.RoundTrip(request) assert.NoError(t, err) - authHeaderValue := resp.Header.Get("Authorization") + log.Println("Authheader: ", cfg.Header) + authHeaderValue := resp.Header.Get(cfg.Header) assert.Equal(t, authHeaderValue, fmt.Sprintf("%s %s", scheme, string(cfg.BearerToken))) } @@ -79,7 +82,7 @@ func TestBearerAuthenticator(t *testing.T) { md, err := credential.GetRequestMetadata(context.Background()) expectedMd := map[string]string{ - "authorization": fmt.Sprintf("Bearer %s", string(cfg.BearerToken)), + strings.ToLower(cfg.Header): fmt.Sprintf("Bearer %s", string(cfg.BearerToken)), } assert.Equal(t, expectedMd, md) assert.NoError(t, err) @@ -90,8 +93,8 @@ func TestBearerAuthenticator(t *testing.T) { "Foo": {"bar"}, } expectedHeaders := http.Header{ - "Foo": {"bar"}, - "Authorization": {"Bearer " + string(cfg.BearerToken)}, + "Foo": {"bar"}, + cfg.Header: {"Bearer " + string(cfg.BearerToken)}, } resp, err := roundTripper.RoundTrip(&http.Request{Header: orgHeaders}) @@ -120,7 +123,7 @@ func TestBearerStartWatchStop(t *testing.T) { tokenStr := fmt.Sprintf("Bearer %s", token) md, err := credential.GetRequestMetadata(context.Background()) expectedMd := map[string]string{ - "authorization": tokenStr, + strings.ToLower(cfg.Header): tokenStr, } assert.Equal(t, expectedMd, md) assert.NoError(t, err) @@ -131,7 +134,7 @@ func TestBearerStartWatchStop(t *testing.T) { time.Sleep(5 * time.Second) credential, _ = bauth.PerRPCCredentials() md, err = credential.GetRequestMetadata(context.Background()) - expectedMd["authorization"] = tokenStr + "test" + expectedMd[strings.ToLower(cfg.Header)] = tokenStr + "test" assert.Equal(t, expectedMd, md) assert.NoError(t, err) @@ -140,7 +143,7 @@ func TestBearerStartWatchStop(t *testing.T) { time.Sleep(5 * time.Second) credential, _ = bauth.PerRPCCredentials() md, err = credential.GetRequestMetadata(context.Background()) - expectedMd["authorization"] = tokenStr + expectedMd[strings.ToLower(cfg.Header)] = tokenStr time.Sleep(5 * time.Second) assert.Equal(t, expectedMd, md) assert.NoError(t, err) @@ -173,7 +176,7 @@ func TestBearerTokenFileContentUpdate(t *testing.T) { request := &http.Request{Method: http.MethodGet} resp, err := rt.RoundTrip(request) assert.NoError(t, err) - authHeaderValue := resp.Header.Get("Authorization") + authHeaderValue := resp.Header.Get(cfg.Header) assert.Equal(t, authHeaderValue, fmt.Sprintf("%s %s", scheme, string(token))) // change file content once @@ -187,7 +190,7 @@ func TestBearerTokenFileContentUpdate(t *testing.T) { request = &http.Request{Method: http.MethodGet} resp, err = rt.RoundTrip(request) assert.NoError(t, err) - authHeaderValue = resp.Header.Get("Authorization") + authHeaderValue = resp.Header.Get(cfg.Header) assert.Equal(t, authHeaderValue, fmt.Sprintf("%s %s", scheme, string(tokenNew))) // change file content back @@ -198,10 +201,38 @@ func TestBearerTokenFileContentUpdate(t *testing.T) { request = &http.Request{Method: http.MethodGet} resp, err = rt.RoundTrip(request) assert.NoError(t, err) - authHeaderValue = resp.Header.Get("Authorization") + authHeaderValue = resp.Header.Get(cfg.Header) assert.Equal(t, authHeaderValue, fmt.Sprintf("%s %s", scheme, string(token))) } +func TestBearerServerAuthenticateWithHeader(t *testing.T) { + const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." // #nosec + cfg := createDefaultConfig().(*Config) + cfg.Scheme = "Bearer" + cfg.Header = "Custom-Auth-Header" + cfg.BearerToken = token + + bauth := newBearerTokenAuth(cfg, nil) + assert.NotNil(t, bauth) + + ctx := context.Background() + assert.NoError(t, bauth.Start(ctx, componenttest.NewNopHost())) + + _, err := bauth.Authenticate(ctx, map[string][]string{strings.ToLower(cfg.Header): {"Bearer " + token}}) + assert.NoError(t, err) + + _, err = bauth.Authenticate(ctx, map[string][]string{strings.ToLower(defaultHeader): {"Bearer " + "1234"}}) + assert.Error(t, err) + + _, err = bauth.Authenticate(ctx, map[string][]string{strings.ToLower(defaultHeader): {"" + token}}) + assert.Error(t, err) + + _, err = bauth.Authenticate(ctx, map[string][]string{strings.ToLower(defaultHeader): {"Bearer " + token}}) + assert.Error(t, err) + + assert.NoError(t, bauth.Shutdown(context.Background())) +} + func TestBearerServerAuthenticateWithScheme(t *testing.T) { const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." // #nosec cfg := createDefaultConfig().(*Config) @@ -214,13 +245,13 @@ func TestBearerServerAuthenticateWithScheme(t *testing.T) { ctx := context.Background() assert.NoError(t, bauth.Start(ctx, componenttest.NewNopHost())) - _, err := bauth.Authenticate(ctx, map[string][]string{"authorization": {"Bearer " + token}}) + _, err := bauth.Authenticate(ctx, map[string][]string{strings.ToLower(cfg.Header): {"Bearer " + token}}) assert.NoError(t, err) - _, err = bauth.Authenticate(ctx, map[string][]string{"authorization": {"Bearer " + "1234"}}) + _, err = bauth.Authenticate(ctx, map[string][]string{strings.ToLower(cfg.Header): {"Bearer " + "1234"}}) assert.Error(t, err) - _, err = bauth.Authenticate(ctx, map[string][]string{"authorization": {"" + token}}) + _, err = bauth.Authenticate(ctx, map[string][]string{strings.ToLower(cfg.Header): {"" + token}}) assert.Error(t, err) assert.NoError(t, bauth.Shutdown(context.Background())) @@ -238,16 +269,16 @@ func TestBearerServerAuthenticate(t *testing.T) { ctx := context.Background() assert.NoError(t, bauth.Start(ctx, componenttest.NewNopHost())) - _, err := bauth.Authenticate(ctx, map[string][]string{"authorization": {"Bearer " + token}}) + _, err := bauth.Authenticate(ctx, map[string][]string{strings.ToLower(cfg.Header): {"Bearer " + token}}) assert.Error(t, err) - _, err = bauth.Authenticate(ctx, map[string][]string{"authorization": {"Bearer " + "1234"}}) + _, err = bauth.Authenticate(ctx, map[string][]string{strings.ToLower(cfg.Header): {"Bearer " + "1234"}}) assert.Error(t, err) - _, err = bauth.Authenticate(ctx, map[string][]string{"authorization": {"invalidtoken"}}) + _, err = bauth.Authenticate(ctx, map[string][]string{strings.ToLower(cfg.Header): {"invalidtoken"}}) assert.Error(t, err) - _, err = bauth.Authenticate(ctx, map[string][]string{"authorization": {token}}) + _, err = bauth.Authenticate(ctx, map[string][]string{strings.ToLower(cfg.Header): {token}}) assert.NoError(t, err) assert.NoError(t, bauth.Shutdown(context.Background())) diff --git a/extension/bearertokenauthextension/config.go b/extension/bearertokenauthextension/config.go index f136d635ea32..5c1c60c88c9f 100644 --- a/extension/bearertokenauthextension/config.go +++ b/extension/bearertokenauthextension/config.go @@ -15,6 +15,9 @@ type Config struct { // Scheme specifies the auth-scheme for the token. Defaults to "Bearer" Scheme string `mapstructure:"scheme,omitempty"` + // Header specifies which http header to read the token from. Defaults to "Authorization" + Header string `mapstructure:"header,omitempty"` + // BearerToken specifies the bearer token to use for every RPC. BearerToken configopaque.String `mapstructure:"token,omitempty"` @@ -32,5 +35,8 @@ func (cfg *Config) Validate() error { if cfg.BearerToken == "" && cfg.Filename == "" { return errNoTokenProvided } + if cfg.Header == "" { + cfg.Header = defaultHeader + } return nil } diff --git a/extension/bearertokenauthextension/config_test.go b/extension/bearertokenauthextension/config_test.go index d739fcf86825..b1d2f54bdc9d 100644 --- a/extension/bearertokenauthextension/config_test.go +++ b/extension/bearertokenauthextension/config_test.go @@ -31,6 +31,7 @@ func TestLoadConfig(t *testing.T) { id: component.NewIDWithName(metadata.Type, "sometoken"), expected: &Config{ Scheme: defaultScheme, + Header: defaultHeader, BearerToken: "sometoken", }, }, @@ -38,6 +39,7 @@ func TestLoadConfig(t *testing.T) { id: component.NewIDWithName(metadata.Type, "withscheme"), expected: &Config{ Scheme: "MyScheme", + Header: defaultHeader, BearerToken: "my-token", }, }, diff --git a/extension/bearertokenauthextension/factory.go b/extension/bearertokenauthextension/factory.go index 8f3e66602885..ebc12bc6e6c1 100644 --- a/extension/bearertokenauthextension/factory.go +++ b/extension/bearertokenauthextension/factory.go @@ -14,6 +14,7 @@ import ( const ( defaultScheme = "Bearer" + defaultHeader = "Authorization" ) // NewFactory creates a factory for the static bearer token Authenticator extension. @@ -29,6 +30,7 @@ func NewFactory() extension.Factory { func createDefaultConfig() component.Config { return &Config{ Scheme: defaultScheme, + Header: defaultHeader, } } diff --git a/extension/bearertokenauthextension/factory_test.go b/extension/bearertokenauthextension/factory_test.go index 88810f9da091..0aab7bc1c1a1 100644 --- a/extension/bearertokenauthextension/factory_test.go +++ b/extension/bearertokenauthextension/factory_test.go @@ -15,7 +15,7 @@ import ( func TestFactory_CreateDefaultConfig(t *testing.T) { cfg := createDefaultConfig() - assert.Equal(t, &Config{Scheme: defaultScheme}, cfg) + assert.Equal(t, &Config{Scheme: defaultScheme, Header: defaultHeader}, cfg) assert.NoError(t, componenttest.CheckConfigStruct(cfg)) }