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

Side by Side 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 unified diff | Download patch
OLDNEW
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698