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. |
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. |
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.
Should this be a V(...).Infof?
There was a problem hiding this comment.
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) |
impl/authentication/interceptor.go
Outdated
| } | ||
| } | ||
|
|
||
| // StreamServerInterceptor returns a new unary server interceptors that performs per-request auth. |
impl/authorization/authorization.go
Outdated
| @@ -38,30 +42,37 @@ func New() authorization.Authorization { | |||
| // 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.
Does sctx need to change now that SecurityContext is not used?
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.
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.
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.
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.
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.
That looks like a TODO to me.
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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.
Maybe this should be called SecurityContext, or the key renamed so they match.
impl/authentication/fake_test.go
Outdated
| @@ -0,0 +1,56 @@ | |||
| // Copyright 2016 Google Inc. All Rights Reserved. | |||
There was a problem hiding this comment.
Time passes ... Thorin sings about gold.
| 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.
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.
FakeAuthFunc is fairly simple. I'm getting 100% code coverage.
There was a problem hiding this comment.
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.
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.
Does this add much value?
There was a problem hiding this comment.
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") |
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.
The other code returns fixed errors but this doesn't. Should this do the same?
There was a problem hiding this comment.
The other fixed errors weren't really needed. I've removed them now.
There was a problem hiding this comment.
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.
We want to run the rest of the tests :-)
I'll put this in a t.Run()
| 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.
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.
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.
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😄