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

Side by Side Diff: tokenserver/appengine/delegation/rpc_mint_delegation_token.go

Issue 2413683004: token-server: Delegation config import, validation and evaluation. (Closed)
Patch Set: also check validity_duration Created 4 years, 1 month 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 2016 The LUCI Authors. All rights reserved. 1 // Copyright 2016 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 delegation 5 package delegation
6 6
7 import ( 7 import (
8 "fmt"
9 "strings"
10 "time"
11
12 "github.com/golang/protobuf/jsonpb"
8 "golang.org/x/net/context" 13 "golang.org/x/net/context"
9 "google.golang.org/grpc" 14 "google.golang.org/grpc"
10 "google.golang.org/grpc/codes" 15 "google.golang.org/grpc/codes"
11 16
17 "github.com/luci/luci-go/common/clock"
18 "github.com/luci/luci-go/common/errors"
19 "github.com/luci/luci-go/common/logging"
20 "github.com/luci/luci-go/server/auth"
21 "github.com/luci/luci-go/server/auth/identity"
12 "github.com/luci/luci-go/server/auth/signing" 22 "github.com/luci/luci-go/server/auth/signing"
13 » minter "github.com/luci/luci-go/tokenserver/api/minter/v1" 23
24 » admin "github.com/luci/luci-go/tokenserver/api/admin/v1"
25 » "github.com/luci/luci-go/tokenserver/api/minter/v1"
26 » "github.com/luci/luci-go/tokenserver/appengine/utils/identityset"
14 ) 27 )
15 28
16 // MintDelegationTokenRPC implements TokenMinter.MintDelegationToken RPC method. 29 // MintDelegationTokenRPC implements TokenMinter.MintDelegationToken RPC method.
17 type MintDelegationTokenRPC struct { 30 type MintDelegationTokenRPC struct {
18 // Signer is mocked in tests. 31 // Signer is mocked in tests.
19 // 32 //
20 // In prod it is gaesigner.Signer. 33 // In prod it is gaesigner.Signer.
21 Signer signing.Signer 34 Signer signing.Signer
35
36 // ConfigLoader loads delegation config on demand.
37 //
38 // In prod it is DelegationConfigLoader.
39 ConfigLoader func(context.Context) (*DelegationConfig, error)
40
41 // mintMock call is used in tests.
42 //
43 // In prod it is 'mint'
44 mintMock func(context.Context, *minter.MintDelegationTokenRequest,
45 *RulesQuery, *admin.DelegationRule) (*minter.MintDelegationToken Response, error)
22 } 46 }
23 47
24 // MintDelegationToken generates a new bearer delegation token. 48 // MintDelegationToken generates a new bearer delegation token.
25 func (r *MintDelegationTokenRPC) MintDelegationToken(c context.Context, req *min ter.MintDelegationTokenRequest) (*minter.MintDelegationTokenResponse, error) { 49 func (r *MintDelegationTokenRPC) MintDelegationToken(c context.Context, req *min ter.MintDelegationTokenRequest) (*minter.MintDelegationTokenResponse, error) {
50 state := auth.GetState(c)
51
52 // Dump the whole request and relevant auth state to the debug log.
53 if logging.IsLogging(c, logging.Debug) {
54 m := jsonpb.Marshaler{Indent: " "}
55 dump, _ := m.MarshalToString(req)
56 logging.Debugf(c, "PeerIdentity: %s", state.PeerIdentity())
57 logging.Debugf(c, "MintDelegationTokenRequest:\n%s", dump)
58 }
59
60 // Validate the request authentication context: not an anonymous call, n o
61 // delegation is used.
62 callerID := state.User().Identity
63 if callerID != state.PeerIdentity() {
64 logging.Errorf(c, "Trying to use delegation, it's forbidden")
65 return nil, grpc.Errorf(codes.PermissionDenied, "delegation is f orbidden for this API call")
66 }
67 if callerID == identity.AnonymousIdentity {
68 logging.Errorf(c, "Unauthenticated request")
69 return nil, grpc.Errorf(codes.Unauthenticated, "authentication r equired")
70 }
71
72 cfg, err := r.ConfigLoader(c)
73 if err != nil {
74 // Don't put error details in the message, it may be returned to
75 // unauthorized callers. ConfigLoader logs the error already.
76 return nil, grpc.Errorf(codes.Internal, "failed to load delegati on config")
77 }
78
79 // Make sure the caller is mentioned in the config before doing anything else.
80 // This rejects unauthorized callers early. Passing this check doesn't m ean
81 // that there's a matching rule though, so the request still can be reje cted
82 // later.
83 switch ok, err := cfg.IsAuthorizedRequestor(c, callerID); {
84 case err != nil:
85 logging.WithError(err).Errorf(c, "IsAuthorizedRequestor failed")
86 return nil, grpc.Errorf(codes.Internal, "failed to check authori zation")
87 case !ok:
88 logging.Errorf(c, "Didn't pass initial authorization")
89 return nil, grpc.Errorf(codes.PermissionDenied, "not authorized" )
90 }
91
92 // Validate requested token lifetime. It's not part of the rules query.
93 if req.ValidityDuration == 0 {
94 req.ValidityDuration = 3600
95 }
96 if req.ValidityDuration < 0 {
97 err = fmt.Errorf("invalid 'validity_duration' (%d)", req.Validit yDuration)
98 logging.WithError(err).Errorf(c, "Bad request")
99 return nil, grpc.Errorf(codes.InvalidArgument, "bad request - %s ", err)
100 }
101
102 // Validate and normalize the request. This may do relatively expensive calls
103 // to resolve "https://<service-url>" entries to "service:<id>" entries.
104 query, err := buildRulesQuery(c, req, callerID)
105 if err != nil {
106 if errors.IsTransient(err) {
107 logging.WithError(err).Errorf(c, "buildRulesQuery failed ")
108 return nil, grpc.Errorf(codes.Internal, "failure when re solving target service ID - %s", err)
109 }
110 logging.WithError(err).Errorf(c, "Bad request")
111 return nil, grpc.Errorf(codes.InvalidArgument, "bad request - %s ", err)
112 }
113
114 // Consult the config to find the rule that allows this operation (if an y).
115 rule, err := cfg.FindMatchingRule(c, query)
116 if err != nil {
117 if errors.IsTransient(err) {
118 logging.WithError(err).Errorf(c, "FindMatchingRule faile d")
119 return nil, grpc.Errorf(codes.Internal, "failure when ch ecking rules - %s", err)
120 }
121 logging.WithError(err).Errorf(c, "Didn't pass rules check")
122 return nil, grpc.Errorf(codes.PermissionDenied, "forbidden - %s" , err)
123 }
124 logging.Infof(c, "Found the matching rule %q in the config rev %s", rule .Name, cfg.Revision)
125
126 // Make sure the requested token lifetime is allowed by the rule.
127 if req.ValidityDuration > rule.MaxValidityDuration {
128 err = fmt.Errorf(
129 "the requested validity duration (%d sec) exceeds the ma ximum allowed one (%d sec)",
130 req.ValidityDuration, rule.MaxValidityDuration)
131 logging.WithError(err).Errorf(c, "Validity duration check didn't pass")
132 return nil, grpc.Errorf(codes.PermissionDenied, "forbidden - %s" , err)
133 }
134
135 if r.mintMock != nil {
136 return r.mintMock(c, req, query, rule)
137 }
138 return r.mint(c, req, query, rule)
139 }
140
141 // mint is called to make the token after the request has been authorized.
142 func (r *MintDelegationTokenRPC) mint(
143 c context.Context, req *minter.MintDelegationTokenRequest,
144 query *RulesQuery, rule *admin.DelegationRule) (*minter.MintDelegationTo kenResponse, error) {
145 // TODO(vadimsh): Make the token, record it in the audit log.
26 return nil, grpc.Errorf(codes.Unimplemented, "Not implemented yet") 146 return nil, grpc.Errorf(codes.Unimplemented, "Not implemented yet")
27 } 147 }
148
149 // buildRulesQuery validates the request, extracts and normalizes relevant
150 // fields into RulesQuery object.
151 //
152 // May return transient errors.
153 func buildRulesQuery(c context.Context, req *minter.MintDelegationTokenRequest, requestor identity.Identity) (*RulesQuery, error) {
154 // Validate 'delegated_identity'.
155 var err error
156 var delegatee identity.Identity
157 if req.DelegatedIdentity == "" {
158 return nil, fmt.Errorf("'delegated_identity' is required")
159 }
160 if req.DelegatedIdentity == Requestor {
161 delegatee = requestor // the requestor is delegating its own ide ntity
162 } else {
163 if delegatee, err = identity.MakeIdentity(req.DelegatedIdentity) ; err != nil {
164 return nil, fmt.Errorf("bad 'delegated_identity' - %s", err)
165 }
166 }
167
168 // Validate 'audience', convert it into a set.
169 if len(req.Audience) == 0 {
170 return nil, fmt.Errorf("'audience' is required")
171 }
172 audienceSet, err := identityset.FromStrings(req.Audience, skipRequestor)
173 if err != nil {
174 return nil, fmt.Errorf("bad 'audience' - %s", err)
175 }
176 if sliceHasString(req.Audience, Requestor) {
177 audienceSet.AddIdentity(requestor)
178 }
179
180 // Split 'services' into two lists: URLs and everything else (which is
181 // "service:..." and "*" presumably, validated below).
182 if len(req.Services) == 0 {
183 return nil, fmt.Errorf("'services' is required")
184 }
185 urls := make([]string, 0, len(req.Services))
186 rest := make([]string, 0, len(req.Services))
187 for _, srv := range req.Services {
188 if strings.HasPrefix(srv, "https://") {
189 urls = append(urls, srv)
190 } else {
191 rest = append(rest, srv)
192 }
193 }
194
195 // Convert the list into a set, verify it contains only services (or "*" ).
196 servicesSet, err := identityset.FromStrings(rest, nil)
197 if err != nil {
198 return nil, fmt.Errorf("bad 'services' - %s", err)
199 }
200 if len(servicesSet.Groups) != 0 {
201 return nil, fmt.Errorf("bad 'services' - can't specify groups")
202 }
203 for ident := range servicesSet.IDs {
204 if ident.Kind() != identity.Service {
205 return nil, fmt.Errorf("bad 'services' - %q is not a ser vice ID", ident)
206 }
207 }
208
209 // Resolve URLs into app IDs. This may involve URL fetch calls (if the c ache
210 // is cold), so skip this expensive call if already specifying the unive rsal
211 // set of all services.
212 if !servicesSet.All && len(urls) != 0 {
213 if err = resolveServiceIDs(c, urls, servicesSet); err != nil {
214 return nil, err
215 }
216 }
217
218 // Done!
219 return &RulesQuery{
220 Requestor: requestor,
221 Delegatee: delegatee,
222 Audience: audienceSet,
223 Services: servicesSet,
224 }, nil
225 }
226
227 // fetchLUCIServiceIdentity is replaced in tests.
228 var fetchLUCIServiceIdentity = signing.FetchLUCIServiceIdentity
229
230 // resolveServiceIDs takes a bunch of service URLs and resolves them to
231 // 'service:<app-id>' identities, putting them in the 'out' set.
232 //
233 // May return transient errors.
234 func resolveServiceIDs(c context.Context, urls []string, out *identityset.Set) e rror {
235 // URL fetch calls below should be extra fast. If they get stuck, someth ing is
236 // horribly wrong, better to abort soon.
237 c, abort := clock.WithTimeout(c, 5*time.Second)
238 defer abort()
239
240 type Result struct {
241 URL string
242 ID identity.Identity
243 Err error
244 }
245
246 ch := make(chan Result, len(urls))
247
248 for _, url := range urls {
249 go func(url string) {
250 id, err := fetchLUCIServiceIdentity(c, url)
251 ch <- Result{url, id, err}
252 }(url)
253 }
254
255 for i := 0; i < len(urls); i++ {
256 result := <-ch
257 if result.Err != nil {
258 if errors.IsTransient(result.Err) {
259 return result.Err
260 }
261 return fmt.Errorf("could not resolve %q to service ID - %s", result.URL, result.Err)
262 }
263 out.AddIdentity(result.ID)
264 }
265
266 return nil
267 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698