Skip to content

Commit f8c7638

Browse files
authored
Support bearerauthtoken header customization (#38793)
#### Description Adding a feature - Allow the header name to be customized in the bearerauthtoken extension #### Testing Added tests and ran all tests for the extension. #### Documentation Updated readme for the extension
1 parent 6223d55 commit f8c7638

File tree

9 files changed

+127
-7
lines changed

9 files changed

+127
-7
lines changed

.chloggen/bearerauth-header.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: bearertokenauthextension
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Allow the header name to be customized in the bearerauthtoken extension
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [38793]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: [user]

extension/bearertokenauthextension/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ The authenticator type has to be set to `bearertokenauth`.
2121

2222
## Configuration
2323

24+
- `header`: Specifies the auth header name. Defaults to "Authorization". Optional.
25+
2426
- `scheme`: Specifies the auth scheme name. Defaults to "Bearer". Optional.
2527

2628
- `token`: Static authorization token that needs to be sent on every gRPC client call as metadata.

extension/bearertokenauthextension/bearertokenauth.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package bearertokenauthextension // import "github.com/open-telemetry/openteleme
66
import (
77
"context"
88
"crypto/subtle"
9-
"errors"
109
"fmt"
1110
"net/http"
1211
"os"
@@ -30,7 +29,7 @@ type PerRPCAuth struct {
3029

3130
// GetRequestMetadata returns the request metadata to be used with the RPC.
3231
func (c *PerRPCAuth) GetRequestMetadata(context.Context, ...string) (map[string]string, error) {
33-
return map[string]string{"authorization": c.auth.authorizationValue()}, nil
32+
return map[string]string{strings.ToLower(c.auth.header): c.auth.authorizationValue()}, nil
3433
}
3534

3635
// RequireTransportSecurity always returns true for this implementation. Passing bearer tokens in plain-text connections is a bad idea.
@@ -47,6 +46,7 @@ var (
4746

4847
// BearerTokenAuth is an implementation of extensionauth interfaces. It embeds a static authorization "bearer" token in every rpc call.
4948
type BearerTokenAuth struct {
49+
header string
5050
scheme string
5151
authorizationValuesAtomic atomic.Value
5252

@@ -61,6 +61,7 @@ func newBearerTokenAuth(cfg *Config, logger *zap.Logger) *BearerTokenAuth {
6161
logger.Warn("a filename is specified. Configured token(s) is ignored!")
6262
}
6363
a := &BearerTokenAuth{
64+
header: cfg.Header,
6465
scheme: cfg.Scheme,
6566
filename: cfg.Filename,
6667
logger: logger,
@@ -211,19 +212,20 @@ func (b *BearerTokenAuth) PerRPCCredentials() (credentials.PerRPCCredentials, er
211212
// RoundTripper is not implemented by BearerTokenAuth
212213
func (b *BearerTokenAuth) RoundTripper(base http.RoundTripper) (http.RoundTripper, error) {
213214
return &BearerAuthRoundTripper{
215+
header: b.header,
214216
baseTransport: base,
215217
auth: b,
216218
}, nil
217219
}
218220

219221
// Authenticate checks whether the given context contains valid auth data. Validates tokens from clients trying to access the service (incoming requests)
220222
func (b *BearerTokenAuth) Authenticate(ctx context.Context, headers map[string][]string) (context.Context, error) {
221-
auth, ok := headers["authorization"]
223+
auth, ok := headers[strings.ToLower(b.header)]
222224
if !ok {
223-
auth, ok = headers["Authorization"]
225+
auth, ok = headers[b.header]
224226
}
225227
if !ok || len(auth) == 0 {
226-
return ctx, errors.New("missing or empty authorization header")
228+
return ctx, fmt.Errorf("missing or empty authorization header: %s", b.header)
227229
}
228230
token := auth[0] // Extract token from authorization header
229231
expectedTokens := b.authorizationValues()
@@ -237,6 +239,7 @@ func (b *BearerTokenAuth) Authenticate(ctx context.Context, headers map[string][
237239

238240
// BearerAuthRoundTripper intercepts and adds Bearer token Authorization headers to each http request.
239241
type BearerAuthRoundTripper struct {
242+
header string
240243
baseTransport http.RoundTripper
241244
auth *BearerTokenAuth
242245
}
@@ -247,6 +250,6 @@ func (interceptor *BearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.R
247250
if req2.Header == nil {
248251
req2.Header = make(http.Header)
249252
}
250-
req2.Header.Set("Authorization", interceptor.auth.authorizationValue())
253+
req2.Header.Set(interceptor.header, interceptor.auth.authorizationValue())
251254
return interceptor.baseTransport.RoundTrip(req2)
252255
}

extension/bearertokenauthextension/bearertokenauth_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net/http"
1010
"os"
1111
"path/filepath"
12+
"strings"
1213
"testing"
1314
"time"
1415

@@ -384,3 +385,65 @@ func TestBearerTokenMultipleTokensInFile(t *testing.T) {
384385

385386
assert.NoError(t, bauth.Shutdown(context.Background()))
386387
}
388+
389+
func TestCustomHeaderRoundTrip(t *testing.T) {
390+
cfg := createDefaultConfig().(*Config)
391+
cfg.Header = "X-Custom-Authorization"
392+
cfg.Scheme = ""
393+
cfg.BearerToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
394+
395+
bauth := newBearerTokenAuth(cfg, nil)
396+
assert.NotNil(t, bauth)
397+
398+
base := &mockRoundTripper{}
399+
c, err := bauth.RoundTripper(base)
400+
assert.NoError(t, err)
401+
assert.NotNil(t, c)
402+
403+
request := &http.Request{Method: http.MethodGet}
404+
resp, err := c.RoundTrip(request)
405+
assert.NoError(t, err)
406+
authHeaderValue := resp.Header.Get(cfg.Header)
407+
assert.Equal(t, authHeaderValue, string(cfg.BearerToken))
408+
}
409+
410+
func TestCustomHeaderGetRequestMetadata(t *testing.T) {
411+
cfg := createDefaultConfig().(*Config)
412+
cfg.Header = "X-Custom-Authorization"
413+
cfg.Scheme = ""
414+
cfg.BearerToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
415+
416+
bauth := newBearerTokenAuth(cfg, nil)
417+
assert.NotNil(t, bauth)
418+
419+
assert.NoError(t, bauth.Start(context.Background(), componenttest.NewNopHost()))
420+
credential, err := bauth.PerRPCCredentials()
421+
422+
assert.NoError(t, err)
423+
assert.NotNil(t, credential)
424+
425+
md, err := credential.GetRequestMetadata(context.Background())
426+
expectedMd := map[string]string{
427+
strings.ToLower(cfg.Header): string(cfg.BearerToken),
428+
}
429+
assert.Equal(t, expectedMd, md)
430+
assert.NoError(t, err)
431+
}
432+
433+
func TestCustomHeaderAuthenticate(t *testing.T) {
434+
cfg := createDefaultConfig().(*Config)
435+
cfg.Header = "X-Custom-Authorization"
436+
cfg.Scheme = ""
437+
cfg.BearerToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
438+
439+
bauth := newBearerTokenAuth(cfg, nil)
440+
assert.NotNil(t, bauth)
441+
442+
ctx := context.Background()
443+
assert.NoError(t, bauth.Start(ctx, componenttest.NewNopHost()))
444+
445+
_, err := bauth.Authenticate(ctx, map[string][]string{cfg.Header: {string(cfg.BearerToken)}})
446+
assert.NoError(t, err)
447+
448+
assert.NoError(t, bauth.Shutdown(context.Background()))
449+
}

extension/bearertokenauthextension/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import (
1212

1313
// Config specifies how the Per-RPC bearer token based authentication data should be obtained.
1414
type Config struct {
15+
// Header specifies the auth-header for the token. Defaults to "Authorization"
16+
Header string `mapstructure:"header,omitempty"`
17+
1518
// Scheme specifies the auth-scheme for the token. Defaults to "Bearer"
1619
Scheme string `mapstructure:"scheme,omitempty"`
1720

extension/bearertokenauthextension/config_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,34 +32,39 @@ func TestLoadConfig(t *testing.T) {
3232
{
3333
id: component.NewIDWithName(metadata.Type, "sometoken"),
3434
expected: &Config{
35+
Header: defaultHeader,
3536
Scheme: defaultScheme,
3637
BearerToken: "sometoken",
3738
},
3839
},
3940
{
4041
id: component.NewIDWithName(metadata.Type, "withscheme"),
4142
expected: &Config{
43+
Header: defaultHeader,
4244
Scheme: "MyScheme",
4345
BearerToken: "my-token",
4446
},
4547
},
4648
{
4749
id: component.NewIDWithName(metadata.Type, "multipletokens"),
4850
expected: &Config{
51+
Header: defaultHeader,
4952
Scheme: "Bearer",
5053
Tokens: []configopaque.String{"token1", "thistokenalsoworks"},
5154
},
5255
},
5356
{
5457
id: component.NewIDWithName(metadata.Type, "withfilename"),
5558
expected: &Config{
59+
Header: defaultHeader,
5660
Scheme: "Bearer",
5761
Filename: "file-containing.token",
5862
},
5963
},
6064
{
6165
id: component.NewIDWithName(metadata.Type, "both"),
6266
expected: &Config{
67+
Header: defaultHeader,
6368
Scheme: "Bearer",
6469
BearerToken: "ignoredtoken",
6570
Filename: "file-containing.token",
@@ -68,6 +73,7 @@ func TestLoadConfig(t *testing.T) {
6873
{
6974
id: component.NewIDWithName(metadata.Type, "tokensandtoken"),
7075
expected: &Config{
76+
Header: defaultHeader,
7177
Scheme: "Bearer",
7278
BearerToken: "sometoken",
7379
Tokens: []configopaque.String{"token1", "thistokenalsoworks"},
@@ -77,12 +83,22 @@ func TestLoadConfig(t *testing.T) {
7783
{
7884
id: component.NewIDWithName(metadata.Type, "withtokensandfilename"),
7985
expected: &Config{
86+
Header: defaultHeader,
8087
Scheme: "Bearer",
8188
Tokens: []configopaque.String{"ignoredtoken1", "ignoredtoken2"},
8289
Filename: "file-containing.token",
8390
},
8491
},
92+
{
93+
id: component.NewIDWithName(metadata.Type, "withheader"),
94+
expected: &Config{
95+
Header: "X-Custom-Authorization",
96+
Scheme: "",
97+
BearerToken: "my-token",
98+
},
99+
},
85100
}
101+
86102
for _, tt := range tests {
87103
t.Run(tt.id.String(), func(t *testing.T) {
88104
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))

extension/bearertokenauthextension/factory.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
)
1414

1515
const (
16+
defaultHeader = "Authorization"
1617
defaultScheme = "Bearer"
1718
)
1819

@@ -28,6 +29,7 @@ func NewFactory() extension.Factory {
2829

2930
func createDefaultConfig() component.Config {
3031
return &Config{
32+
Header: defaultHeader,
3133
Scheme: defaultScheme,
3234
}
3335
}

extension/bearertokenauthextension/factory_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515

1616
func TestFactory_CreateDefaultConfig(t *testing.T) {
1717
cfg := createDefaultConfig()
18-
assert.Equal(t, &Config{Scheme: defaultScheme}, cfg)
18+
assert.Equal(t, &Config{Header: defaultHeader, Scheme: defaultScheme}, cfg)
1919
assert.NoError(t, componenttest.CheckConfigStruct(cfg))
2020
}
2121

extension/bearertokenauthextension/testdata/config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,7 @@ bearertokenauth/tokensandtoken:
2828
- "token1"
2929
- "thistokenalsoworks"
3030
token: "my-token"
31+
bearertokenauth/withheader:
32+
header: "X-Custom-Authorization"
33+
scheme: ""
34+
token: "my-token"

0 commit comments

Comments
 (0)