From ab6e1b8465ae1a7db793a4fb2b0f9e6498954756 Mon Sep 17 00:00:00 2001 From: Sriram Panyam Date: Mon, 30 Dec 2024 15:26:54 -0800 Subject: [PATCH 1/4] Adding header config for allowing custom auth headers in http collector --- extension/bearertokenauthextension/README.md | 15 +++++++++++++-- .../bearertokenauthextension/bearertokenauth.go | 2 ++ extension/bearertokenauthextension/config_test.go | 2 ++ 3 files changed, 17 insertions(+), 2 deletions(-) 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..bfb7e7ddccff 100644 --- a/extension/bearertokenauthextension/bearertokenauth.go +++ b/extension/bearertokenauthextension/bearertokenauth.go @@ -44,6 +44,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 +61,7 @@ func newBearerTokenAuth(cfg *Config, logger *zap.Logger) *BearerTokenAuth { } a := &BearerTokenAuth{ scheme: cfg.Scheme, + header: cfg.Header, filename: cfg.Filename, logger: logger, } diff --git a/extension/bearertokenauthextension/config_test.go b/extension/bearertokenauthextension/config_test.go index d739fcf86825..1e2e83e9d63f 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: DefaultAuthHeader, BearerToken: "sometoken", }, }, @@ -38,6 +39,7 @@ func TestLoadConfig(t *testing.T) { id: component.NewIDWithName(metadata.Type, "withscheme"), expected: &Config{ Scheme: "MyScheme", + Header: DefaultAuthHeader, BearerToken: "my-token", }, }, From 2e5ab3bae8bb16c22626cd49f8f13f29de38a29c Mon Sep 17 00:00:00 2001 From: Sriram Panyam Date: Mon, 30 Dec 2024 15:32:30 -0800 Subject: [PATCH 2/4] Adding Header param for customizing token header --- extension/bearertokenauthextension/config.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/extension/bearertokenauthextension/config.go b/extension/bearertokenauthextension/config.go index f136d635ea32..44297bbcabe3 100644 --- a/extension/bearertokenauthextension/config.go +++ b/extension/bearertokenauthextension/config.go @@ -10,11 +10,16 @@ import ( "go.opentelemetry.io/collector/config/configopaque" ) +const DefaultAuthHeader = "Authorization" + // Config specifies how the Per-RPC bearer token based authentication data should be obtained. 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 +37,8 @@ func (cfg *Config) Validate() error { if cfg.BearerToken == "" && cfg.Filename == "" { return errNoTokenProvided } + if cfg.Header == "" { + cfg.Header = DefaultAuthHeader + } return nil } From 953a323fef294d632d6436424260f1e8d5a820b8 Mon Sep 17 00:00:00 2001 From: Sriram Panyam Date: Mon, 30 Dec 2024 15:47:11 -0800 Subject: [PATCH 3/4] Replacing "authorization" and "Authorization" with Header field vals and adding test --- .../bearertokenauth.go | 9 +-- .../bearertokenauth_test.go | 67 ++++++++++++++----- extension/bearertokenauthextension/config.go | 4 +- .../bearertokenauthextension/config_test.go | 4 +- extension/bearertokenauthextension/factory.go | 2 + .../bearertokenauthextension/factory_test.go | 2 +- 6 files changed, 60 insertions(+), 28 deletions(-) diff --git a/extension/bearertokenauthextension/bearertokenauth.go b/extension/bearertokenauthextension/bearertokenauth.go index bfb7e7ddccff..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" @@ -173,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 } @@ -187,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") @@ -214,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 44297bbcabe3..5c1c60c88c9f 100644 --- a/extension/bearertokenauthextension/config.go +++ b/extension/bearertokenauthextension/config.go @@ -10,8 +10,6 @@ import ( "go.opentelemetry.io/collector/config/configopaque" ) -const DefaultAuthHeader = "Authorization" - // Config specifies how the Per-RPC bearer token based authentication data should be obtained. type Config struct { // Scheme specifies the auth-scheme for the token. Defaults to "Bearer" @@ -38,7 +36,7 @@ func (cfg *Config) Validate() error { return errNoTokenProvided } if cfg.Header == "" { - cfg.Header = DefaultAuthHeader + cfg.Header = defaultHeader } return nil } diff --git a/extension/bearertokenauthextension/config_test.go b/extension/bearertokenauthextension/config_test.go index 1e2e83e9d63f..b1d2f54bdc9d 100644 --- a/extension/bearertokenauthextension/config_test.go +++ b/extension/bearertokenauthextension/config_test.go @@ -31,7 +31,7 @@ func TestLoadConfig(t *testing.T) { id: component.NewIDWithName(metadata.Type, "sometoken"), expected: &Config{ Scheme: defaultScheme, - Header: DefaultAuthHeader, + Header: defaultHeader, BearerToken: "sometoken", }, }, @@ -39,7 +39,7 @@ func TestLoadConfig(t *testing.T) { id: component.NewIDWithName(metadata.Type, "withscheme"), expected: &Config{ Scheme: "MyScheme", - Header: DefaultAuthHeader, + 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)) } From f709f684b98ccf997fd7fe430a41bdced9ae4538 Mon Sep 17 00:00:00 2001 From: Sriram Panyam Date: Tue, 31 Dec 2024 11:36:25 -0800 Subject: [PATCH 4/4] Adding change long entry for PR 36986 --- ...tom-bearertokenauth-header-field-name.yaml | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .chloggen/custom-bearertokenauth-header-field-name.yaml 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] +