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

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

Powered by Google App Engine
This is Rietveld 408576698