Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(16)

Unified Diff: tokenserver/appengine/impl/serviceaccounts/rpc_mint_oauth_token_grant.go

Issue 2991413002: tokenserver: Implement MintOAuthTokenGrant RPC. (Closed)
Patch Set: Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: tokenserver/appengine/impl/serviceaccounts/rpc_mint_oauth_token_grant.go
diff --git a/tokenserver/appengine/impl/serviceaccounts/rpc_mint_oauth_token_grant.go b/tokenserver/appengine/impl/serviceaccounts/rpc_mint_oauth_token_grant.go
index c111a1600536ed09a746f0b619add4bb9ac3ae14..dbbaa5d478cc843de3e3cb2a9f9e2bb606e72f43 100644
--- a/tokenserver/appengine/impl/serviceaccounts/rpc_mint_oauth_token_grant.go
+++ b/tokenserver/appengine/impl/serviceaccounts/rpc_mint_oauth_token_grant.go
@@ -5,18 +5,206 @@
package serviceaccounts
import (
+ "fmt"
+ "time"
+
+ "github.com/golang/protobuf/jsonpb"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
+ "github.com/luci/luci-go/common/clock"
+ "github.com/luci/luci-go/common/logging"
+ "github.com/luci/luci-go/common/proto/google"
+ "github.com/luci/luci-go/server/auth"
+ "github.com/luci/luci-go/server/auth/identity"
+ "github.com/luci/luci-go/server/auth/signing"
+
+ "github.com/luci/luci-go/tokenserver/api"
"github.com/luci/luci-go/tokenserver/api/minter/v1"
+ "github.com/luci/luci-go/tokenserver/appengine/impl/utils"
+ "github.com/luci/luci-go/tokenserver/appengine/impl/utils/revocation"
)
+// tokenIDSequenceKind defines the namespace of int64 IDs for grant tokens.
+//
+// Changing it will effectively reset the ID generation.
+const tokenIDSequenceKind = "oauthTokenGrantID"
+
// MintOAuthTokenGrantRPC implements TokenMinter.MintOAuthTokenGrant method.
type MintOAuthTokenGrantRPC struct {
+ // Signer is mocked in tests.
+ //
+ // In prod it is gaesigner.Signer.
+ Signer signing.Signer
+
+ // Rules returns service account rules to use for the request.
+ //
+ // In prod it is GlobalRulesCache.Rules.
+ Rules func(context.Context) (*Rules, error)
+
+ // mintMock call is used in tests.
+ //
+ // In prod it is 'mint'
+ mintMock func(context.Context, *mintParams) (*minter.MintOAuthTokenGrantResponse, error)
}
// MintOAuthTokenGrant produces new OAuth token grant.
func (r *MintOAuthTokenGrantRPC) MintOAuthTokenGrant(c context.Context, req *minter.MintOAuthTokenGrantRequest) (*minter.MintOAuthTokenGrantResponse, error) {
Vadim Sh. 2017/08/04 06:37:37 similar to https://github.com/luci/luci-go/blob/ma
- return nil, grpc.Errorf(codes.Unavailable, "not implemented")
+ state := auth.GetState(c)
+
+ // Dump the whole request and relevant auth state to the debug log.
+ callerID := state.User().Identity
+ if logging.IsLogging(c, logging.Debug) {
+ m := jsonpb.Marshaler{Indent: " "}
+ dump, _ := m.MarshalToString(req)
+ logging.Debugf(c, "Identity: %s", callerID)
+ logging.Debugf(c, "MintOAuthTokenGrantRequest:\n%s", dump)
+ }
+
+ // Grab a string that identifies token server version. This almost always
+ // just hits local memory cache.
+ serviceVer, err := utils.ServiceVersion(c, r.Signer)
+ if err != nil {
+ return nil, grpc.Errorf(codes.Internal, "can't grab service version - %s", err)
+ }
+
+ // Reject obviously bad requests (and parse end_user along the way).
+ switch {
+ case req.ServiceAccount == "":
+ err = fmt.Errorf("service_account is required")
+ case req.ValidityDuration < 0:
+ err = fmt.Errorf("validity_duration must be positive, not %d", req.ValidityDuration)
+ case req.EndUser == "":
+ err = fmt.Errorf("end_user is required")
+ }
+ var endUserID identity.Identity
+ if err == nil {
+ if endUserID, err = identity.MakeIdentity(req.EndUser); err != nil {
+ err = fmt.Errorf("bad end_user - %s", err)
+ }
+ }
+ if err != nil {
+ logging.WithError(err).Errorf(c, "Bad request")
+ return nil, grpc.Errorf(codes.InvalidArgument, "bad request - %s", err)
+ }
+
+ // TODO(vadimsh): Verify that this user is present by requiring the end user's
+ // credentials, e.g make Swarming forward user's OAuth token to the token
+ // server, so it can be validated here.
+
+ // Fetch service account rules. They are hot in memory most of the time.
+ rules, err := r.Rules(c)
+ if err != nil {
+ // Don't put error details in the message, it may be returned to
+ // unauthorized callers.
+ logging.WithError(err).Errorf(c, "Failed to load service accounts rules")
+ return nil, grpc.Errorf(codes.Internal, "failed to load service accounts rules")
+ }
+
+ // Grab the rule for this account. Don't leak information about presence or
+ // absence of the account to the caller, they may not be authorized to see the
+ // account at all.
+ rule := rules.Rule(req.ServiceAccount)
+ if rule == nil {
+ logging.Errorf(c, "No rule for service account %q in the config rev %s", req.ServiceAccount, rules.ConfigRevision())
+ return nil, grpc.Errorf(codes.PermissionDenied, "unknown service account or not enough permissions to use it")
+ }
+ logging.Infof(c, "Found the matching rule %q in the config rev %s", rule.Rule.Name, rules.ConfigRevision())
+
+ // If the caller is in 'Proxies' list, we assume it's known to us and we trust
+ // it enough to start returning more detailed error messages.
+ switch known, err := rule.Proxies.IsMember(c, callerID); {
+ case err != nil:
+ logging.WithError(err).Errorf(c, "Failed to check membership of caller %q", callerID)
+ return nil, grpc.Errorf(codes.Internal, "membership check failed")
+ case !known:
+ logging.Errorf(c, "Caller %q is not authorized to use account %q", callerID, req.ServiceAccount)
+ return nil, grpc.Errorf(codes.PermissionDenied, "unknown service account or not enough permissions to use it")
+ }
+
+ // Check ValidityDuration next, it is easiest check.
+ if req.ValidityDuration == 0 {
+ req.ValidityDuration = 3600
+ }
+ if req.ValidityDuration > rule.Rule.MaxGrantValidityDuration {
+ logging.Errorf(c, "Requested validity is larger than max allowed: %d > %d", req.ValidityDuration, rule.Rule.MaxGrantValidityDuration)
+ return nil, grpc.Errorf(codes.InvalidArgument, "per rule %q the validity duration should be <= %d", rule.Rule.Name, rule.Rule.MaxGrantValidityDuration)
+ }
+
+ // Next is EndUsers check (involves membership lookups).
+ switch known, err := rule.EndUsers.IsMember(c, endUserID); {
+ case err != nil:
+ logging.WithError(err).Errorf(c, "Failed to check membership of end user %q", endUserID)
+ return nil, grpc.Errorf(codes.Internal, "membership check failed")
+ case !known:
+ logging.Errorf(c, "End user %q is not authorized to use account %q", endUserID, req.ServiceAccount)
+ return nil, grpc.Errorf(
+ codes.PermissionDenied, "per rule %q the user %q is not authorized to use the service account %q",
+ rule.Rule.Name, endUserID, req.ServiceAccount)
+ }
+
+ // All checks are done! Note that AllowedScopes is checked later during
+ // MintOAuthTokenViaGrant. Here we don't even know what OAuth scopes will be
+ // requested.
+ var resp *minter.MintOAuthTokenGrantResponse
+ p := mintParams{
+ serviceAccount: req.ServiceAccount,
+ proxyID: callerID,
+ endUserID: endUserID,
+ validityDuration: req.ValidityDuration,
+ serviceVer: serviceVer,
+ }
+ if r.mintMock != nil {
+ resp, err = r.mintMock(c, &p)
+ } else {
+ resp, err = r.mint(c, &p)
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ // TODO(vadimsh): Log the generated token to BigQuery.
+
+ return resp, nil
+}
+
+type mintParams struct {
+ serviceAccount string
+ proxyID identity.Identity
+ endUserID identity.Identity
+ validityDuration int64
+ serviceVer string
+}
+
+// mint is called to make the token after the request has been authorized.
+func (r *MintOAuthTokenGrantRPC) mint(c context.Context, p *mintParams) (*minter.MintOAuthTokenGrantResponse, error) {
+ id, err := revocation.GenerateTokenID(c, tokenIDSequenceKind)
+ if err != nil {
+ logging.WithError(err).Errorf(c, "Error when generating token ID")
+ return nil, grpc.Errorf(codes.Internal, "error when generating token ID - %s", err)
+ }
+
+ now := clock.Now(c).UTC()
+ expiry := now.Add(time.Duration(p.validityDuration) * time.Second)
+
+ // All the stuff here has already been validated in 'MintOAuthTokenGrant'.
+ signed, err := SignGrant(c, r.Signer, &tokenserver.OAuthTokenGrantBody{
+ TokenId: id,
+ ServiceAccount: p.serviceAccount,
+ Proxy: string(p.proxyID),
+ EndUser: string(p.endUserID),
+ IssuedAt: google.NewTimestamp(now),
+ ValidityDuration: p.validityDuration,
+ })
+ if err != nil {
+ logging.WithError(err).Errorf(c, "Error when signing the token")
+ return nil, grpc.Errorf(codes.Internal, "error when signing the token - %s", err)
+ }
+
+ return &minter.MintOAuthTokenGrantResponse{
+ GrantToken: signed,
+ Expiry: google.NewTimestamp(expiry),
+ ServiceVersion: p.serviceVer,
+ }, nil
}

Powered by Google App Engine
This is Rietveld 408576698