-
Notifications
You must be signed in to change notification settings - Fork 1.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
Establish the auth context for authenticators to add data to #3552
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package auth | ||
|
||
import "context" | ||
|
||
var ( | ||
rawKey = rawType{} | ||
subjectKey = subjectType{} | ||
groupsKey = groupsType{} | ||
) | ||
|
||
type rawType struct{} | ||
type subjectType struct{} | ||
type groupsType struct{} | ||
Comment on lines
+25
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to have all three of them in the same time? Can we actually have one key with an enum type? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe have a "AuthContext" struct which embeds different auth types? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a library that provides this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, and quite common actually.
Those are not auth types. The raw auth data might be a JWT, which contains the principal and group memberships. The authenticator (OIDC, for instance) will then place all the three components in the context. Another authenticator might get the subject and groups from the auth server. I can split this if we need more time, but I think the third commit brings only a minimal API, generic enough to be extended with custom types in the future. The idea about AuthContext has been brought by @tigrannajaryan as well, though: 6b6e713#diff-208f8af800c6aa88673523930b718a5d1a5b3f2a2d2f6edbdcf97171ce3d15ca
I'm not aware of any. This is really only about adding auth to a context.Context, but I'm open to using a library if it brings value. |
||
|
||
// NewContextFromRaw creates a new context derived from the given context, | ||
// adding the raw authentication string to the result. | ||
func NewContextFromRaw(ctx context.Context, raw string) context.Context { | ||
return context.WithValue(ctx, rawKey, raw) | ||
} | ||
|
||
// NewContextFromSubject creates a new context derived from the given context, | ||
// adding the subject to the result. | ||
func NewContextFromSubject(ctx context.Context, subject string) context.Context { | ||
return context.WithValue(ctx, subjectKey, subject) | ||
} | ||
|
||
// NewContextFromMemberships creates a new context derived from the given context, | ||
// adding the memberships to the result. | ||
func NewContextFromMemberships(ctx context.Context, groups []string) context.Context { | ||
return context.WithValue(ctx, groupsKey, groups) | ||
} | ||
|
||
// RawFromContext returns the raw authentication string used to perform the authentication. | ||
// It's typically the string value for a single HTTP header, such as the "Authentication" header. | ||
// Example: "Basic ZGV2ZWxvcGVyOmN1cmlvdXM=". | ||
func RawFromContext(ctx context.Context) (string, bool) { | ||
value, ok := ctx.Value(rawKey).(string) | ||
return value, ok | ||
} | ||
|
||
// SubjectFromContext returns the subject that was extracted from the raw authentication string. | ||
func SubjectFromContext(ctx context.Context) (string, bool) { | ||
value, ok := ctx.Value(subjectKey).(string) | ||
return value, ok | ||
} | ||
|
||
// GroupsFromContext returns the list of groups the subject belongs. | ||
func GroupsFromContext(ctx context.Context) ([]string, bool) { | ||
value, ok := ctx.Value(groupsKey).([]string) | ||
return value, ok | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,7 +40,11 @@ type ServerAuthenticator interface { | |
// When the authentication fails, an error must be returned and the caller must not retry. This function is typically called from interceptors, | ||
// on behalf of receivers, but receivers can still call this directly if the usage of interceptors isn't suitable. | ||
// The deadline and cancellation given to this function must be respected, but note that authentication data has to be part of the map, not context. | ||
Authenticate(ctx context.Context, headers map[string][]string) error | ||
// The resulting context should contain the authentication data, such as the principal/username, group membership (if available), and the raw | ||
// authentication data (if possible). This will allow other components in the pipeline to make decisions based on that data, such as routing based | ||
// on tenancy as determined by the group membership, or passing through the authentication data to the next collector/backend. | ||
// The context keys to be used are not defined yet. | ||
Authenticate(ctx context.Context, headers map[string][]string) (context.Context, error) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add an explanation in the comments about what the returned context is expected to be? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I left that part out so that we can define it when we have a first implementation making use of it, but I added the best information we have so far. Should I define the context keys as part of this PR? It would be basically this: https://github.com/open-telemetry/opentelemetry-collector/pull/3239/files#diff-1c8371e19129e2092ab7abfce04e6558218579f1be29d90adb958d230475584f There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In any case, I updated the PR to include a few words about the resulting context. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry about the noise, but I thought it would be better to include the auth keys as part of the API for v1, so, I included it in this PR. |
||
|
||
// GRPCUnaryServerInterceptor is a helper method to provide a gRPC-compatible UnaryServerInterceptor, typically calling the authenticator's Authenticate method. | ||
// While the context is the typical source of authentication data, the interceptor is free to determine where the auth data should come from. For instance, some | ||
|
@@ -59,7 +63,7 @@ type ServerAuthenticator interface { | |
|
||
// AuthenticateFunc defines the signature for the function responsible for performing the authentication based on the given headers map. | ||
// See ServerAuthenticator.Authenticate. | ||
type AuthenticateFunc func(ctx context.Context, headers map[string][]string) error | ||
type AuthenticateFunc func(ctx context.Context, headers map[string][]string) (context.Context, error) | ||
|
||
// GRPCUnaryInterceptorFunc defines the signature for the function intercepting unary gRPC calls, useful for authenticators to use as | ||
// types for internal structs, making it easier to mock them in tests. | ||
|
@@ -79,7 +83,8 @@ func DefaultGRPCUnaryServerInterceptor(ctx context.Context, req interface{}, _ * | |
return nil, errMetadataNotFound | ||
} | ||
|
||
if err := authenticate(ctx, headers); err != nil { | ||
ctx, err := authenticate(ctx, headers) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
|
@@ -95,7 +100,9 @@ func DefaultGRPCStreamServerInterceptor(srv interface{}, stream grpc.ServerStrea | |
return errMetadataNotFound | ||
} | ||
|
||
if err := authenticate(ctx, headers); err != nil { | ||
// TODO: propagate the context down the stream | ||
_, err := authenticate(ctx, headers) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
|
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.
Why do we need this in a generic auth/context.go at all? These appear to be specific context data for example by oidc auth extension. Why not declare there on oidc implementation?
I think we should aim for every auth extension to declare its own context data.
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.
These are not specific to the OIDC auth extension. Those three pieces should be common to most authenticators, as they are common parts of any authorization system. For example, we might have a basic auth authenticator, a la Apache httpd: in such case, we would have a
.htpasswd
that would serve as the user data source (subject), and an optional.htgroups
, linking groups with users like Unix'/etc/group
. The raw field would be the base64 encodedusername:password
received by the HTTP server from the client.I think every extension (not only auth) should be allowed to enhance the context data as they wish so that other components can make use of that information, but we should have a minimal set of options that are common to all authenticators, and this auth/context represents that in my opinion.
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.
Sorry, I still do not understand why this needs to be in the
auth
package and not in the specific extension's package which needs this. We are not expecting that we can mix auth extensions in any way, right? So why not move these declarations to the extension which plans to inject these values in the context?I may be missing something but I think different authenticator extensions will need different values. Some authenticators will only need raw auth token others may need a subject or whatever it is called.
I don't understand what do we gain by making these concepts part of the public API of core and not just delegate it to individual auth extensions.
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 certainly expect auth extensions to provide data that will be consumed by other components in the pipeline. For instance, the routing processor can make decisions based on the principal or group membership (tenant "acme" should go to this Jaeger, tenant "ecorp" should go to that Zipkin, ...).
All auth extensions are expected to have at least the notion of principal (like a username, or a service account name, or ...) Raw can certainly be removed from the generic package, though. I would also keep the group information, as it's also a common aspect of auth no matter the backend used.
Other components, like the routing processor I mentioned earlier, might provide tighter integration with the auth features, without requiring specific authenticators to exist.
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.
@jpkrohling can you please show how a routing processor could use the auth data? A sample code somewhere would be great to see and would help review this PR.
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.
It cannot right now because of this missing piece, but this is how a configuration would look like for the existing routingprocessor:
In this example, the authentication for a hypothetical user "service-a", belonging to the tenant "acme" would cause the authenticator to place "service-a" as the subject/principal and "acme" in the group membership.
The routingprocessor would probably need to group the data by auth/membership, but minimal changes would be required to the current code, probably isolated to this function here: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/36a7da6a4c29ddf81d118ec159e0a7a1682c6f21/processor/routingprocessor/routing.go#L133-L134 . Instead of extracting the route destination from the context, it would extract from the pdata.
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.
Looking at my notes, I think we did discuss something else though:
Other than that, the general idea remains.