-
Notifications
You must be signed in to change notification settings - Fork 149
Move authentication to grpc interceptor #973
Conversation
Now that authentication is happening in a grpc interceptor, we don't need any authentication code in core/. We also get the added benefit of keeping the `validatedSecurityKey` private to the authentication package.
Codecov Report
@@ Coverage Diff @@
## master #973 +/- ##
==========================================
+ Coverage 49.55% 49.73% +0.17%
==========================================
Files 28 29 +1
Lines 2030 2041 +11
==========================================
+ Hits 1006 1015 +9
- Misses 843 846 +3
+ Partials 181 180 -1
Continue to review full report at Codecov.
|
| func (a *GAuth) ValidateCreds(ctx context.Context) (*authentication.SecurityContext, error) { | ||
| // Get Tokeninfo from credentials. | ||
| tokenInfo, err := a.validateToken(ctx) | ||
| // AuthFunc authenticate the information present in ctx. |
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.
authenticates
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.
done
impl/authentication/interceptor.go
Outdated
| grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth" | ||
| ) | ||
|
|
||
| // UnaryServerInterceptor returns a new unary server interceptors that performs per-request auth. |
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.
"interceptor that"
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.
done
impl/authentication/interceptor.go
Outdated
| // UnaryServerInterceptor returns a new unary server interceptors that performs per-request auth. | ||
| func UnaryServerInterceptor(authFuncs map[string]grpc_auth.AuthFunc) grpc.UnaryServerInterceptor { | ||
| return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { | ||
| glog.Infof("In authentication interceptor") |
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.
Should this be a V(...).Infof?
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.
+1
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.
This was a debug. I'll remove
impl/authentication/interceptor.go
Outdated
| glog.Infof("In authentication interceptor") | ||
| authFunc, ok := authFuncs[info.FullMethod] | ||
| if !ok { | ||
| glog.Infof("auth interceptor: no hander for %v", info.FullMethod) |
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.
Warning / V?
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.
yes
impl/authentication/interceptor.go
Outdated
| } | ||
| } | ||
|
|
||
| // StreamServerInterceptor returns a new unary server interceptors that performs per-request auth. |
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.
"interceptor that"
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.
done
impl/authorization/authorization.go
Outdated
|
|
||
| // IsAuthorized verifies that the identity issuing the call (from ctx) is | ||
| // authorized to carry the given permission. A call is authorized if: | ||
| // 1. userID matches the identity in sctx, |
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.
Does sctx need to change now that SecurityContext is not used?
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.
Updated comment
impl/authorization/authorization.go
Outdated
| func resourceLabel(domainID, appID string) string { | ||
| return fmt.Sprintf("%v|%v", domainID, appID) | ||
| return fmt.Sprintf("domains/%v/apps/%v", | ||
| strings.Replace(domainID, "/", "_", -1), |
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.
this implies that two resources with differing domains (and/or apps) "blah/blah" and "blah_blah" are treated identically, is that 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.
The / character is not allowed in domainIDs or appIDs. If they were allowed, a specially crafted domainID or appID could cross boundaries. eg domainID = "blah/apps/apptakeover"
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.
Sure, but my point is that there are now multiple "preimages" which result in identical resource labels - this function doesn't enforce the rule that "/"s aren't allowed, it just quietly papers over that situation resulting in the multiple "preimage" thing.
Maybe that's OK, I don't know :)
Is the rule about the contents of domainID and appID enforced elsewhere?
if so this function possibly doesn't need to Sprintf, and if it isn't enforced elsewhere then maybe this function should return an error in the case where a passed-in ID contains the character?
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.
Excellent point. I'll make this return an error.
cmd/keytransparency-server/main.go
Outdated
| entry.New(), authz, domains, queue, mutations) | ||
| grpcServer := grpc.NewServer( | ||
| grpc.Creds(creds), | ||
| // All streaming methods are unauthenticated for now. |
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.
That looks like a TODO to me.
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 anticipate adding authenticated streaming methods right now.
This comment is a reminder to add the interceptor if we do add an authenticated streaming rpc.
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.
OK, as you're the one who'll have to write the postmortem when that doesn't happen.
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'll add a reference to the streaming interceptor with an empty map to make things clearer.
| grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer( | ||
| grpc_prometheus.UnaryServerInterceptor, | ||
| authentication.UnaryServerInterceptor(map[string]grpc_auth.AuthFunc{ | ||
| "/google.keytransparency.v1.KeyTransparency/UpdateEntry": authFunc, |
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.
Can we avoid hardcoding this? Presumably there will be a bunch of methods under the same prefix x/y in future.
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 could replace this with a regex in the future if we need to, but simpler seems easier to test and maintain for the moment.
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 thought I saw another copy of the same string. Literals in multiple places often get overlooked.
impl/authentication/context.go
Outdated
| ErrMissingAuth = errors.New("auth: missing authentication header") | ||
| ) | ||
| // ValidatedSecurity is the auth value stored in the Contexts. | ||
| type ValidatedSecurity 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.
Maybe this should be called SecurityContext, or the key renamed so they match.
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
impl/authentication/fake_test.go
Outdated
| @@ -0,0 +1,56 @@ | |||
| // Copyright 2016 Google Inc. All Rights Reserved. | |||
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.
Time passes ... Thorin sings about gold.
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.
:-)
| wantCode codes.Code | ||
| }{ | ||
| {desc: "missing authentication", ctx: ctx, wantCode: codes.Unauthenticated}, | ||
| {desc: "working case", ctx: WithOutgoingFakeAuth(ctx, "foo"), wantEmail: "foo"}, |
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.
Aren't there more than two cases e.g. corrupt token? There are multiple errors defined below, which makes me think so.
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.
FakeAuthFunc is fairly simple. I'm getting 100% code coverage.
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.
OK we shouldn't really be testing fakes though. Are there tests for the multiple possible errors defined or is that elsewhere?
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.
TestGoogleAuthn tests those code paths.
impl/authentication/google.go
Outdated
| return diff | ||
| } | ||
|
|
||
| func parseToken(accessToken string) *oauth2.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.
Does this add much 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.
Not really, except that it nicely separates the ideas of parsing and authenticating.
impl/authentication/interceptor.go
Outdated
| // UnaryServerInterceptor returns a new unary server interceptors that performs per-request auth. | ||
| func UnaryServerInterceptor(authFuncs map[string]grpc_auth.AuthFunc) grpc.UnaryServerInterceptor { | ||
| return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { | ||
| glog.Infof("In authentication interceptor") |
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.
+1
impl/authorization/authorization.go
Outdated
| domainID, appID, userID string, permission authzpb.Permission) error { | ||
| sctx, ok := authentication.FromContext(ctx) | ||
| if !ok { | ||
| return status.Errorf(codes.Unauthenticated, "Request does not contain a ValidatedSecurity object") |
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.
The other code returns fixed errors but this doesn't. Should this do the same?
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.
The other fixed errors weren't really needed. I've removed them now.
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.
OK, this possibly addresses my point above.
| sctx, err := authentication.FakeAuthFunc(inCtx) | ||
| if err != nil { | ||
| t.Errorf("FakeAuthFunc(): %v", err) | ||
| continue |
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.
maybe t.FailNow() ?
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 want to run the rest of the tests :-)
I'll put this in a t.Run()
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.
r.Run() is better.
| gsvr := grpc.NewServer( | ||
| grpc.UnaryInterceptor( | ||
| authentication.UnaryServerInterceptor(map[string]grpc_auth.AuthFunc{ | ||
| "/google.keytransparency.v1.KeyTransparency/UpdateEntry": authFunc, |
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.
Deja vu.
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.
The integration tests act like a sort of "main", wiring together libraries much like main does.
As a result, this intentionally lives in impl/ and allows other environments to setup their own custom envs
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.
OK if it doesn't rely on those strings matching (my earlier point) then it might not be a problem.
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 a place in the generated proto files where these strings are publicly exported. Not sure what the best solution is.
|
PTAL |
|
I think the potential only blocker is the point Al made about the preimages. |
|
Fixed the resource label pre-image issue. |
|
PTAL |
* master: Move authentication to grpc interceptor (google#973) Add UnitTest for PaginateHistory (google#968) Remove unused `UserProfile` message (google#972) Update default paths (google#910)
This PR supports greater flexibility for deployment specific authentication schemes by moving authentication from the
core/code base to animpl/specific grpc interceptor which can easily be swapped out with more appropriate authentication logic in specific deployments.Changes:
core/authenticationmoved toimpl/authenticationValidateCredsmoved toAuthFuncto match thegrpc_auth.AuthFuncinterface.SecurityContextmoved toValidatedSecurityand is now stored incontext.Contextimpl/google/authenticationmerged withimpl/authenticationto use the newValidatedSecurityauthfromcore/keyserver😄