-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add auth token propagation for metrics reader #3341
Conversation
Signed-off-by: albertteoh <[email protected]>
Codecov Report
@@ Coverage Diff @@
## master #3341 +/- ##
==========================================
+ Coverage 96.46% 96.49% +0.03%
==========================================
Files 259 260 +1
Lines 15178 15196 +18
==========================================
+ Hits 14642 14664 +22
+ Misses 452 449 -3
+ Partials 84 83 -1
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔥
|
||
// tokenAuthTransport wraps an instance of http.Transport for the purpose of | ||
// propagating an Authorization token from inbound to outbound HTTP requests. | ||
type tokenAuthTransport struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: could we take this opportunity and refactor/cleanup bearer token functionality into a standalone package? We have ./cmd/query/app/token_propagation_handler.go
and ./storage/spanstore/token_propagation.go, which should really be in a single package like ./pkg/bearertoken
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in d1c96ad.
Signed-off-by: albertteoh <[email protected]>
Signed-off-by: albertteoh <[email protected]>
pkg/bearertoken/propagation.go
Outdated
// the Authorization token from the http.Request and inserts it into the http.Request | ||
// context for easier access to the request token via GetBearerToken for bearer token | ||
// propagation use cases. | ||
func PropagationHandler(logger *zap.Logger, h http.Handler) http.Handler { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed bearerTokenPropagationHandler
-> PropagationHandler
to reduce stutter.
I considered renaming ContextWithBearerToken
and GetBearerToken
for similar reasons but on further thought, I felt it reduces readability. Let me know if you disagree.
|
||
// tokenAuthTransport wraps an instance of http.Transport for the purpose of | ||
// propagating an Authorization token from inbound to outbound HTTP requests. | ||
type tokenAuthTransport struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in d1c96ad.
pkg/bearertoken/propagation.go
Outdated
func GetBearerToken(ctx context.Context) (string, bool) { | ||
val, ok := ctx.Value(bearerToken).(string) | ||
return val, ok | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would split this file:
- up to this point it's
context.go
- below it's
http.go
- you may also want to add the http transport implementation instead of using private types in different storages
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in f0774b2.
pkg/bearertoken/propagation_test.go
Outdated
) | ||
|
||
func Test_bearTokenPropagationHandler(t *testing.T) { | ||
func Test_GetBearerToken(t *testing.T) { | ||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't this string just be "blah"
? Currently it looks like there's special meaning in the value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in 8e5f1b9.
Signed-off-by: albertteoh <[email protected]>
Signed-off-by: albertteoh <[email protected]>
Signed-off-by: albertteoh <[email protected]>
Signed-off-by: albertteoh <[email protected]>
@@ -331,12 +332,27 @@ func TestGetRoundTripper(t *testing.T) { | |||
}, | |||
}, logger) | |||
require.NoError(t, err) | |||
assert.IsType(t, &http.Transport{}, rt) | |||
if tc.tlsEnabled { | |||
assert.NotNil(t, rt.(*http.Transport).TLSClientConfig) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving the common transport into the bearertoken
package meant that we can't inspect the wrapped http.Transport
.
The original test didn't feel quite right in the first place when it had to make type assertions.
Suggestions also welcome.
pkg/bearertoken/transport.go
Outdated
// request's Authorization bearer token header. | ||
// If the WithAllowOverrideFromCtx(true) option is provided, the request context's | ||
// bearer token, will be used in preference to this token. | ||
func WithToken(token string) Option { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took the functional options approach because the defaultToken
wasn't mandatory and didn't feel right to force callers to pass a sentinel token value, nor make callers guess what they should be setting it to if we exported this value in the struct, especially given it's in a separate package.
Let me know if you disagree with the approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We started to move away from the use of functional option pattern, it just adds unnecessary mental overhead and has poor discoverability in the docs. I feel like this type can simply be declared with public fields and instantiated directly, without a constructor function. The optionality of the fields is naturally available via struct zero values.
pkg/bearertoken/transport_test.go
Outdated
{ | ||
name: "No options provided and request context set should have empty Bearer token", | ||
roundTripper: roundTripFunc(func(r *http.Request) (*http.Response, error) { | ||
assert.Equal(t, "Bearer ", r.Header.Get("Authorization")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't sure if this is an acceptable Auth header value or if an error should be returned. This was the original behaviour so I kept it that way.
pkg/bearertoken/transport.go
Outdated
// NewTransport returns a new bearer token transport that wraps the given | ||
// http.RoundTripper, forwarding the authorization token from inbound to | ||
// outbound HTTP requests. | ||
func NewTransport(roundTripper http.RoundTripper, opts ...Option) http.RoundTripper { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't feel like it was necessary to expose the internals of the transport, hence why http.RoundTripper
is returned, given that's the only use case right now.
Signed-off-by: albertteoh <[email protected]>
It's not clear to me why the CIT Crossdock job is failing on:
|
pkg/bearertoken/context.go
Outdated
// Key is the string literal used internally in the implementation of this context. | ||
const Key = "bearer.token" | ||
const bearerToken = contextKey(Key) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see Key
being used outside of this package
// Key is the string literal used internally in the implementation of this context. | |
const Key = "bearer.token" | |
const bearerToken = contextKey(Key) | |
type contextKeyType string | |
const contextKey = contextKeyType("bearer.token") |
pkg/bearertoken/transport.go
Outdated
// request's Authorization bearer token header. | ||
// If the WithAllowOverrideFromCtx(true) option is provided, the request context's | ||
// bearer token, will be used in preference to this token. | ||
func WithToken(token string) Option { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We started to move away from the use of functional option pattern, it just adds unnecessary mental overhead and has poor discoverability in the docs. I feel like this type can simply be declared with public fields and instantiated directly, without a constructor function. The optionality of the fields is naturally available via struct zero values.
pkg/bearertoken/transport.go
Outdated
token = headerToken | ||
} | ||
} | ||
r.Header.Set("Authorization", "Bearer "+token) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to check for token != ""
before setting the header?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I considered it but was technically a breaking change; I don't know enough about Auth headers to determine if this is a bug or correct behaviour, though sounds like it's a bug given your suggestion.
Signed-off-by: albertteoh <[email protected]>
Signed-off-by: albertteoh <[email protected]>
pkg/bearertoken/transport.go
Outdated
token = headerToken | ||
} | ||
} | ||
r.Header.Set("Authorization", "Bearer "+token) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I considered it but was technically a breaking change; I don't know enough about Auth headers to determine if this is a bug or correct behaviour, though sounds like it's a bug given your suggestion.
pkg/bearertoken/context.go
Outdated
const bearerToken = contextKey(BearerTokenKey) | ||
const ( | ||
// Key is the key name for the bearer token context value. | ||
Key = "bearer.token" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Key
is used outside of this package here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strictly speaking, I think it would've been better if grpc plugin just defined its own public constant instead of relying on this one, since they are completely independent.
$ grx BearerTokenKey .
./plugin/storage/grpc/shared/grpc_client.go:76: md.Set(spanstore.BearerTokenKey, bearerToken)
./plugin/storage/grpc/shared/grpc_client_test.go:115: bearerTokenFromMetadata := md.Get(spanstore.BearerTokenKey)
./plugin/storage/grpc/README.md:191: values := md.Get(spanstore.BearerTokenKey)
If we remove that external dependency, then this key can be made private, and doesn't need to be a string, just this:
type contextKeyType int
const contextKey = iota
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in 696cb5f.
pkg/bearertoken/context.go
Outdated
const bearerToken = contextKey(BearerTokenKey) | ||
const ( | ||
// Key is the key name for the bearer token context value. | ||
Key = "bearer.token" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strictly speaking, I think it would've been better if grpc plugin just defined its own public constant instead of relying on this one, since they are completely independent.
$ grx BearerTokenKey .
./plugin/storage/grpc/shared/grpc_client.go:76: md.Set(spanstore.BearerTokenKey, bearerToken)
./plugin/storage/grpc/shared/grpc_client_test.go:115: bearerTokenFromMetadata := md.Get(spanstore.BearerTokenKey)
./plugin/storage/grpc/README.md:191: values := md.Get(spanstore.BearerTokenKey)
If we remove that external dependency, then this key can be made private, and doesn't need to be a string, just this:
type contextKeyType int
const contextKey = iota
Signed-off-by: albertteoh <[email protected]>
Signed-off-by: albertteoh <[email protected]>
Thanks for the review! |
Signed-off-by: albertteoh [email protected]
Which problem is this PR solving?
/api/metrics/*
endpoints #3340Short description of the changes
http.Transport
into atokenAuthTransport
struct that implements thehttp.RoundTripper
'sRoundTrip
function, which injects the Authorization bearer token header into the outbound request.