Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2017 The LUCI Authors. All rights reserved. | 1 // Copyright 2017 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package serviceaccounts | 5 package serviceaccounts |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "fmt" | |
| 9 "time" | |
| 10 | |
| 11 "github.com/golang/protobuf/jsonpb" | |
| 8 "golang.org/x/net/context" | 12 "golang.org/x/net/context" |
| 9 "google.golang.org/grpc" | 13 "google.golang.org/grpc" |
| 10 "google.golang.org/grpc/codes" | 14 "google.golang.org/grpc/codes" |
| 11 | 15 |
| 16 "github.com/luci/luci-go/common/clock" | |
| 17 "github.com/luci/luci-go/common/logging" | |
| 18 "github.com/luci/luci-go/common/proto/google" | |
| 19 "github.com/luci/luci-go/server/auth" | |
| 20 "github.com/luci/luci-go/server/auth/identity" | |
| 21 "github.com/luci/luci-go/server/auth/signing" | |
| 22 | |
| 23 "github.com/luci/luci-go/tokenserver/api" | |
| 12 "github.com/luci/luci-go/tokenserver/api/minter/v1" | 24 "github.com/luci/luci-go/tokenserver/api/minter/v1" |
| 25 "github.com/luci/luci-go/tokenserver/appengine/impl/utils" | |
| 26 "github.com/luci/luci-go/tokenserver/appengine/impl/utils/revocation" | |
| 13 ) | 27 ) |
| 14 | 28 |
| 29 // tokenIDSequenceKind defines the namespace of int64 IDs for grant tokens. | |
| 30 // | |
| 31 // Changing it will effectively reset the ID generation. | |
| 32 const tokenIDSequenceKind = "oauthTokenGrantID" | |
| 33 | |
| 15 // MintOAuthTokenGrantRPC implements TokenMinter.MintOAuthTokenGrant method. | 34 // MintOAuthTokenGrantRPC implements TokenMinter.MintOAuthTokenGrant method. |
| 16 type MintOAuthTokenGrantRPC struct { | 35 type MintOAuthTokenGrantRPC struct { |
| 36 // Signer is mocked in tests. | |
| 37 // | |
| 38 // In prod it is gaesigner.Signer. | |
| 39 Signer signing.Signer | |
| 40 | |
| 41 // Rules returns service account rules to use for the request. | |
| 42 // | |
| 43 // In prod it is GlobalRulesCache.Rules. | |
| 44 Rules func(context.Context) (*Rules, error) | |
| 45 | |
| 46 // mintMock call is used in tests. | |
| 47 // | |
| 48 // In prod it is 'mint' | |
| 49 mintMock func(context.Context, *mintParams) (*minter.MintOAuthTokenGrant Response, error) | |
| 17 } | 50 } |
| 18 | 51 |
| 19 // MintOAuthTokenGrant produces new OAuth token grant. | 52 // MintOAuthTokenGrant produces new OAuth token grant. |
| 20 func (r *MintOAuthTokenGrantRPC) MintOAuthTokenGrant(c context.Context, req *min ter.MintOAuthTokenGrantRequest) (*minter.MintOAuthTokenGrantResponse, error) { | 53 func (r *MintOAuthTokenGrantRPC) MintOAuthTokenGrant(c context.Context, req *min ter.MintOAuthTokenGrantRequest) (*minter.MintOAuthTokenGrantResponse, error) { |
|
Vadim Sh.
2017/08/04 06:37:37
similar to https://github.com/luci/luci-go/blob/ma
| |
| 21 » return nil, grpc.Errorf(codes.Unavailable, "not implemented") | 54 » state := auth.GetState(c) |
| 22 } | 55 |
| 56 » // Dump the whole request and relevant auth state to the debug log. | |
| 57 » callerID := state.User().Identity | |
| 58 » if logging.IsLogging(c, logging.Debug) { | |
| 59 » » m := jsonpb.Marshaler{Indent: " "} | |
| 60 » » dump, _ := m.MarshalToString(req) | |
| 61 » » logging.Debugf(c, "Identity: %s", callerID) | |
| 62 » » logging.Debugf(c, "MintOAuthTokenGrantRequest:\n%s", dump) | |
| 63 » } | |
| 64 | |
| 65 » // Grab a string that identifies token server version. This almost alway s | |
| 66 » // just hits local memory cache. | |
| 67 » serviceVer, err := utils.ServiceVersion(c, r.Signer) | |
| 68 » if err != nil { | |
| 69 » » return nil, grpc.Errorf(codes.Internal, "can't grab service vers ion - %s", err) | |
| 70 » } | |
| 71 | |
| 72 » // Reject obviously bad requests (and parse end_user along the way). | |
| 73 » switch { | |
| 74 » case req.ServiceAccount == "": | |
| 75 » » err = fmt.Errorf("service_account is required") | |
| 76 » case req.ValidityDuration < 0: | |
| 77 » » err = fmt.Errorf("validity_duration must be positive, not %d", r eq.ValidityDuration) | |
| 78 » case req.EndUser == "": | |
| 79 » » err = fmt.Errorf("end_user is required") | |
| 80 » } | |
| 81 » var endUserID identity.Identity | |
| 82 » if err == nil { | |
| 83 » » if endUserID, err = identity.MakeIdentity(req.EndUser); err != n il { | |
| 84 » » » err = fmt.Errorf("bad end_user - %s", err) | |
| 85 » » } | |
| 86 » } | |
| 87 » if err != nil { | |
| 88 » » logging.WithError(err).Errorf(c, "Bad request") | |
| 89 » » return nil, grpc.Errorf(codes.InvalidArgument, "bad request - %s ", err) | |
| 90 » } | |
| 91 | |
| 92 » // TODO(vadimsh): Verify that this user is present by requiring the end user's | |
| 93 » // credentials, e.g make Swarming forward user's OAuth token to the toke n | |
| 94 » // server, so it can be validated here. | |
| 95 | |
| 96 » // Fetch service account rules. They are hot in memory most of the time. | |
| 97 » rules, err := r.Rules(c) | |
| 98 » if err != nil { | |
| 99 » » // Don't put error details in the message, it may be returned to | |
| 100 » » // unauthorized callers. | |
| 101 » » logging.WithError(err).Errorf(c, "Failed to load service account s rules") | |
| 102 » » return nil, grpc.Errorf(codes.Internal, "failed to load service accounts rules") | |
| 103 » } | |
| 104 | |
| 105 » // Grab the rule for this account. Don't leak information about presence or | |
| 106 » // absence of the account to the caller, they may not be authorized to s ee the | |
| 107 » // account at all. | |
| 108 » rule := rules.Rule(req.ServiceAccount) | |
| 109 » if rule == nil { | |
| 110 » » logging.Errorf(c, "No rule for service account %q in the config rev %s", req.ServiceAccount, rules.ConfigRevision()) | |
| 111 » » return nil, grpc.Errorf(codes.PermissionDenied, "unknown service account or not enough permissions to use it") | |
| 112 » } | |
| 113 » logging.Infof(c, "Found the matching rule %q in the config rev %s", rule .Rule.Name, rules.ConfigRevision()) | |
| 114 | |
| 115 » // If the caller is in 'Proxies' list, we assume it's known to us and we trust | |
| 116 » // it enough to start returning more detailed error messages. | |
| 117 » switch known, err := rule.Proxies.IsMember(c, callerID); { | |
| 118 » case err != nil: | |
| 119 » » logging.WithError(err).Errorf(c, "Failed to check membership of caller %q", callerID) | |
| 120 » » return nil, grpc.Errorf(codes.Internal, "membership check failed ") | |
| 121 » case !known: | |
| 122 » » logging.Errorf(c, "Caller %q is not authorized to use account %q ", callerID, req.ServiceAccount) | |
| 123 » » return nil, grpc.Errorf(codes.PermissionDenied, "unknown service account or not enough permissions to use it") | |
| 124 » } | |
| 125 | |
| 126 » // Check ValidityDuration next, it is easiest check. | |
| 127 » if req.ValidityDuration == 0 { | |
| 128 » » req.ValidityDuration = 3600 | |
| 129 » } | |
| 130 » if req.ValidityDuration > rule.Rule.MaxGrantValidityDuration { | |
| 131 » » logging.Errorf(c, "Requested validity is larger than max allowed : %d > %d", req.ValidityDuration, rule.Rule.MaxGrantValidityDuration) | |
| 132 » » return nil, grpc.Errorf(codes.InvalidArgument, "per rule %q the validity duration should be <= %d", rule.Rule.Name, rule.Rule.MaxGrantValidityDu ration) | |
| 133 » } | |
| 134 | |
| 135 » // Next is EndUsers check (involves membership lookups). | |
| 136 » switch known, err := rule.EndUsers.IsMember(c, endUserID); { | |
| 137 » case err != nil: | |
| 138 » » logging.WithError(err).Errorf(c, "Failed to check membership of end user %q", endUserID) | |
| 139 » » return nil, grpc.Errorf(codes.Internal, "membership check failed ") | |
| 140 » case !known: | |
| 141 » » logging.Errorf(c, "End user %q is not authorized to use account %q", endUserID, req.ServiceAccount) | |
| 142 » » return nil, grpc.Errorf( | |
| 143 » » » codes.PermissionDenied, "per rule %q the user %q is not authorized to use the service account %q", | |
| 144 » » » rule.Rule.Name, endUserID, req.ServiceAccount) | |
| 145 » } | |
| 146 | |
| 147 » // All checks are done! Note that AllowedScopes is checked later during | |
| 148 » // MintOAuthTokenViaGrant. Here we don't even know what OAuth scopes wil l be | |
| 149 » // requested. | |
| 150 » var resp *minter.MintOAuthTokenGrantResponse | |
| 151 » p := mintParams{ | |
| 152 » » serviceAccount: req.ServiceAccount, | |
| 153 » » proxyID: callerID, | |
| 154 » » endUserID: endUserID, | |
| 155 » » validityDuration: req.ValidityDuration, | |
| 156 » » serviceVer: serviceVer, | |
| 157 » } | |
| 158 » if r.mintMock != nil { | |
| 159 » » resp, err = r.mintMock(c, &p) | |
| 160 » } else { | |
| 161 » » resp, err = r.mint(c, &p) | |
| 162 » } | |
| 163 » if err != nil { | |
| 164 » » return nil, err | |
| 165 » } | |
| 166 | |
| 167 » // TODO(vadimsh): Log the generated token to BigQuery. | |
| 168 | |
| 169 » return resp, nil | |
| 170 } | |
| 171 | |
| 172 type mintParams struct { | |
| 173 » serviceAccount string | |
| 174 » proxyID identity.Identity | |
| 175 » endUserID identity.Identity | |
| 176 » validityDuration int64 | |
| 177 » serviceVer string | |
| 178 } | |
| 179 | |
| 180 // mint is called to make the token after the request has been authorized. | |
| 181 func (r *MintOAuthTokenGrantRPC) mint(c context.Context, p *mintParams) (*minter .MintOAuthTokenGrantResponse, error) { | |
| 182 » id, err := revocation.GenerateTokenID(c, tokenIDSequenceKind) | |
| 183 » if err != nil { | |
| 184 » » logging.WithError(err).Errorf(c, "Error when generating token ID ") | |
| 185 » » return nil, grpc.Errorf(codes.Internal, "error when generating t oken ID - %s", err) | |
| 186 » } | |
| 187 | |
| 188 » now := clock.Now(c).UTC() | |
| 189 » expiry := now.Add(time.Duration(p.validityDuration) * time.Second) | |
| 190 | |
| 191 » // All the stuff here has already been validated in 'MintOAuthTokenGrant '. | |
| 192 » signed, err := SignGrant(c, r.Signer, &tokenserver.OAuthTokenGrantBody{ | |
| 193 » » TokenId: id, | |
| 194 » » ServiceAccount: p.serviceAccount, | |
| 195 » » Proxy: string(p.proxyID), | |
| 196 » » EndUser: string(p.endUserID), | |
| 197 » » IssuedAt: google.NewTimestamp(now), | |
| 198 » » ValidityDuration: p.validityDuration, | |
| 199 » }) | |
| 200 » if err != nil { | |
| 201 » » logging.WithError(err).Errorf(c, "Error when signing the token") | |
| 202 » » return nil, grpc.Errorf(codes.Internal, "error when signing the token - %s", err) | |
| 203 » } | |
| 204 | |
| 205 » return &minter.MintOAuthTokenGrantResponse{ | |
| 206 » » GrantToken: signed, | |
| 207 » » Expiry: google.NewTimestamp(expiry), | |
| 208 » » ServiceVersion: p.serviceVer, | |
| 209 » }, nil | |
| 210 } | |
| OLD | NEW |