Skip to content

Commit c9bd3ef

Browse files
codebotenaxw
andauthored
[extension/bearertokenauth] use constant time comparison (#34516)
- clarify error message in case of missing header - don't use implementation code to verify expectations in tests - format header value ahead of time, rather than on every use, to avoid allocations - consistently synchronise access to header value for both client and server authenticators (now using sync/atomic.Value rather than RWMutex) --------- Signed-off-by: Alex Boten <[email protected]> Co-authored-by: Andrew Wilkins <[email protected]>
1 parent 496ca1d commit c9bd3ef

File tree

3 files changed

+62
-32
lines changed

3 files changed

+62
-32
lines changed
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: use constant time comparison
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: [34516]
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: []

extension/bearertokenauthextension/bearertokenauth.go

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ package bearertokenauthextension // import "github.com/open-telemetry/openteleme
55

66
import (
77
"context"
8+
"crypto/subtle"
89
"errors"
910
"fmt"
1011
"net/http"
1112
"os"
12-
"sync"
13+
"sync/atomic"
1314

1415
"github.com/fsnotify/fsnotify"
1516
"go.opentelemetry.io/collector/component"
@@ -42,9 +43,8 @@ var (
4243

4344
// BearerTokenAuth is an implementation of auth.Client. It embeds a static authorization "bearer" token in every rpc call.
4445
type BearerTokenAuth struct {
45-
muTokenString sync.RWMutex
46-
scheme string
47-
tokenString string
46+
scheme string
47+
authorizationValueAtomic atomic.Value
4848

4949
shutdownCH chan struct{}
5050

@@ -58,12 +58,13 @@ func newBearerTokenAuth(cfg *Config, logger *zap.Logger) *BearerTokenAuth {
5858
if cfg.Filename != "" && cfg.BearerToken != "" {
5959
logger.Warn("a filename is specified. Configured token is ignored!")
6060
}
61-
return &BearerTokenAuth{
62-
scheme: cfg.Scheme,
63-
tokenString: string(cfg.BearerToken),
64-
filename: cfg.Filename,
65-
logger: logger,
61+
a := &BearerTokenAuth{
62+
scheme: cfg.Scheme,
63+
filename: cfg.Filename,
64+
logger: logger,
6665
}
66+
a.setAuthorizationValue(string(cfg.BearerToken))
67+
return a
6768
}
6869

6970
// Start of BearerTokenAuth does nothing and returns nil if no filename
@@ -135,9 +136,21 @@ func (b *BearerTokenAuth) refreshToken() {
135136
b.logger.Error(err.Error())
136137
return
137138
}
138-
b.muTokenString.Lock()
139-
b.tokenString = string(token)
140-
b.muTokenString.Unlock()
139+
b.setAuthorizationValue(string(token))
140+
}
141+
142+
func (b *BearerTokenAuth) setAuthorizationValue(token string) {
143+
value := token
144+
if b.scheme != "" {
145+
value = b.scheme + " " + value
146+
}
147+
b.authorizationValueAtomic.Store(value)
148+
}
149+
150+
// authorizationValue returns the Authorization header/metadata value
151+
// to set for client auth, and expected value for server auth.
152+
func (b *BearerTokenAuth) authorizationValue() string {
153+
return b.authorizationValueAtomic.Load().(string)
141154
}
142155

143156
// Shutdown of BearerTokenAuth does nothing and returns nil
@@ -158,22 +171,15 @@ func (b *BearerTokenAuth) Shutdown(_ context.Context) error {
158171
// PerRPCCredentials returns PerRPCAuth an implementation of credentials.PerRPCCredentials that
159172
func (b *BearerTokenAuth) PerRPCCredentials() (credentials.PerRPCCredentials, error) {
160173
return &PerRPCAuth{
161-
metadata: map[string]string{"authorization": b.bearerToken()},
174+
metadata: map[string]string{"authorization": b.authorizationValue()},
162175
}, nil
163176
}
164177

165-
func (b *BearerTokenAuth) bearerToken() string {
166-
b.muTokenString.RLock()
167-
token := fmt.Sprintf("%s %s", b.scheme, b.tokenString)
168-
b.muTokenString.RUnlock()
169-
return token
170-
}
171-
172178
// RoundTripper is not implemented by BearerTokenAuth
173179
func (b *BearerTokenAuth) RoundTripper(base http.RoundTripper) (http.RoundTripper, error) {
174180
return &BearerAuthRoundTripper{
175-
baseTransport: base,
176-
bearerTokenFunc: b.bearerToken,
181+
baseTransport: base,
182+
auth: b,
177183
}, nil
178184
}
179185

@@ -184,23 +190,20 @@ func (b *BearerTokenAuth) Authenticate(ctx context.Context, headers map[string][
184190
auth, ok = headers["Authorization"]
185191
}
186192
if !ok || len(auth) == 0 {
187-
return ctx, errors.New("authentication didn't succeed")
193+
return ctx, errors.New("missing or empty authorization header")
188194
}
189195
token := auth[0]
190-
expect := b.tokenString
191-
if len(b.scheme) != 0 {
192-
expect = fmt.Sprintf("%s %s", b.scheme, expect)
193-
}
194-
if expect != token {
196+
expect := b.authorizationValue()
197+
if subtle.ConstantTimeCompare([]byte(expect), []byte(token)) == 0 {
195198
return ctx, fmt.Errorf("scheme or token does not match: %s", token)
196199
}
197200
return ctx, nil
198201
}
199202

200203
// BearerAuthRoundTripper intercepts and adds Bearer token Authorization headers to each http request.
201204
type BearerAuthRoundTripper struct {
202-
baseTransport http.RoundTripper
203-
bearerTokenFunc func() string
205+
baseTransport http.RoundTripper
206+
auth *BearerTokenAuth
204207
}
205208

206209
// RoundTrip modifies the original request and adds Bearer token Authorization headers.
@@ -209,6 +212,6 @@ func (interceptor *BearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.R
209212
if req2.Header == nil {
210213
req2.Header = make(http.Header)
211214
}
212-
req2.Header.Set("Authorization", interceptor.bearerTokenFunc())
215+
req2.Header.Set("Authorization", interceptor.auth.authorizationValue())
213216
return interceptor.baseTransport.RoundTrip(req2)
214217
}

extension/bearertokenauthextension/bearertokenauth_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func TestBearerAuthenticator(t *testing.T) {
9292
}
9393
expectedHeaders := http.Header{
9494
"Foo": {"bar"},
95-
"Authorization": {bauth.bearerToken()},
95+
"Authorization": {"Bearer " + string(cfg.BearerToken)},
9696
}
9797

9898
resp, err := roundTripper.RoundTrip(&http.Request{Header: orgHeaders})

0 commit comments

Comments
 (0)