Skip to content
This repository was archived by the owner on Oct 11, 2024. It is now read-only.

Conversation

@gdbelvin
Copy link
Contributor

This PR supports greater flexibility for deployment specific authentication schemes by moving authentication from the core/ code base to an impl/ specific grpc interceptor which can easily be swapped out with more appropriate authentication logic in specific deployments.

Changes:

  • core/authentication moved to impl/authentication
  • ValidateCreds moved to AuthFunc to match the grpc_auth.AuthFunc interface.
    • SecurityContext moved to ValidatedSecurity and is now stored in context.Context
  • impl/google/authentication merged with impl/authentication to use the new ValidatedSecurity
  • Removed auth from core/keyserver 😄
  • Wrote a simple auth interceptor to support per-method authentication configurations.

gdbelvin added 5 commits May 10, 2018 10:47
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-io
Copy link

codecov-io commented May 10, 2018

Codecov Report

Merging #973 into master will increase coverage by 0.17%.
The diff coverage is 38.46%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
impl/authentication/interceptor.go 0% <0%> (ø)
core/keyserver/keyserver.go 34.89% <0%> (+1.72%) ⬆️
impl/authentication/google.go 18.75% <0%> (ø)
impl/authentication/context.go 100% <100%> (ø)
impl/integration/env.go 73.07% <100%> (+1.64%) ⬆️
impl/authentication/fake.go 88.88% <100%> (ø)
impl/authorization/authorization.go 82.35% <73.33%> (-9.96%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ae6690b...9e2bcf5. Read the comment docs.

@gdbelvin gdbelvin requested review from AlCutter and Martin2112 May 10, 2018 13:09
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

authenticates

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
)

// UnaryServerInterceptor returns a new unary server interceptors that performs per-request auth.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"interceptor that"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// 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")
Copy link
Member

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?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Contributor Author

@gdbelvin gdbelvin May 10, 2018

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

glog.Infof("In authentication interceptor")
authFunc, ok := authFuncs[info.FullMethod]
if !ok {
glog.Infof("auth interceptor: no hander for %v", info.FullMethod)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning / V?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

}
}

// StreamServerInterceptor returns a new unary server interceptors that performs per-request auth.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"interceptor that"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


// 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,
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated comment

func resourceLabel(domainID, appID string) string {
return fmt.Sprintf("%v|%v", domainID, appID)
return fmt.Sprintf("domains/%v/apps/%v",
strings.Replace(domainID, "/", "_", -1),
Copy link
Member

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?

Copy link
Contributor Author

@gdbelvin gdbelvin May 10, 2018

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"

Copy link
Member

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?

Copy link
Contributor Author

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.

entry.New(), authz, domains, queue, mutations)
grpcServer := grpc.NewServer(
grpc.Creds(creds),
// All streaming methods are unauthenticated for now.
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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,
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

ErrMissingAuth = errors.New("auth: missing authentication header")
)
// ValidatedSecurity is the auth value stored in the Contexts.
type ValidatedSecurity struct {
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed

@@ -0,0 +1,56 @@
// Copyright 2016 Google Inc. All Rights Reserved.
Copy link
Contributor

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.

Copy link
Contributor Author

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"},
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Contributor Author

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.

return diff
}

func parseToken(accessToken string) *oauth2.Token {
Copy link
Contributor

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?

Copy link
Contributor Author

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.

// 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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

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")
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe t.FailNow() ?

Copy link
Contributor Author

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()

Copy link
Contributor

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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deja vu.

Copy link
Contributor Author

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

Copy link
Contributor

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.

Copy link
Contributor Author

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.

@gdbelvin
Copy link
Contributor Author

PTAL

@Martin2112
Copy link
Contributor

I think the potential only blocker is the point Al made about the preimages.

@gdbelvin
Copy link
Contributor Author

Fixed the resource label pre-image issue.

@gdbelvin
Copy link
Contributor Author

PTAL

@gdbelvin gdbelvin merged commit d1762ed into google:master May 11, 2018
gdbelvin added a commit to gdbelvin/keytransparency that referenced this pull request May 13, 2018
* 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)
@gdbelvin gdbelvin deleted the f/auth/2 branch January 9, 2019 18:08
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants