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

Side by Side Diff: tokenserver/appengine/delegation/config.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
« no previous file with comments | « tokenserver/api/token_file.pb.go ('k') | tokenserver/appengine/delegation/config_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2016 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file.
4
5 package delegation
6
7 import (
8 "fmt"
9 "strings"
10 "time"
11
12 "github.com/golang/protobuf/proto"
13 "golang.org/x/net/context"
14
15 ds "github.com/luci/gae/service/datastore"
16
17 "github.com/luci/luci-go/common/clock"
18 "github.com/luci/luci-go/common/data/caching/lazyslot"
19 "github.com/luci/luci-go/common/errors"
20 "github.com/luci/luci-go/common/logging"
21 "github.com/luci/luci-go/server/auth/identity"
22
23 "github.com/luci/luci-go/tokenserver/api/admin/v1"
24 "github.com/luci/luci-go/tokenserver/appengine/utils/identityset"
25 )
26
27 // Requestor is magical token that may be used in the config and requests as
28 // a substitute for caller's ID.
29 //
30 // See config.proto for more info.
31 const Requestor = "REQUESTOR"
32
33 // DelegationConfig is a singleton entity that stores imported delegation.cfg.
34 type DelegationConfig struct {
35 _id int64 `gae:"$id,1"`
36
37 // Revision this config was imported from.
38 Revision string `gae:",noindex"`
39
40 // Config is serialized DelegationPermissions proto message.
41 Config []byte `gae:",noindex"`
42
43 // ParsedConfig is deserialized message stored in Config.
44 ParsedConfig *admin.DelegationPermissions `gae:"-"`
45
46 // rules is preprocessed config rules.
47 //
48 // Used by 'FindMatchingRule', built in 'Initialize'.
49 rules []*delegationRule `gae:"-"`
50
51 // requestors is a union of all 'Requestor' fields in all rules.
52 //
53 // Used by 'IsAuthorizedRequestor', built in 'Initialize'.
54 requestors *identityset.Set `gae:"-"`
55 }
56
57 // RulesQuery contains parameters to match against the delegation rules.
58 //
59 // Used by 'FindMatchingRule'.
60 type RulesQuery struct {
61 Requestor identity.Identity // who is requesting the token
62 Delegatee identity.Identity // what identity will be delegated/impersona ted
63 Audience *identityset.Set // the requested audience set
64 Services *identityset.Set // the requested target services set
65 }
66
67 // delegationRule is preprocessed admin.DelegationRule message.
68 //
69 // This object is used by 'FindMatchingRule'.
70 type delegationRule struct {
71 rule *admin.DelegationRule // the original unaltered rule proto
72
73 requestors *identityset.Set // matched to RulesQuery.Requestor
74 delegatees *identityset.Set // matched to RulesQuery.Delegatee
75 audience *identityset.Set // matched to RulesQuery.Audience
76 services *identityset.Set // matched to RulesQuery.Services
77
78 addRequestorAsDelegatee bool // if true, add RulesQuery.Requestor to 'de legatees' set
79 addRequestorToAudience bool // if true, add RulesQuery.Requestor to 'au dience' set
80 }
81
82 // procCacheExpiration is how long to keep DelegationConfig in process memory.
83 const procCacheExpiration = time.Minute
84
85 // FetchDelegationConfig loads DelegationConfig entity from the datastore.
86 //
87 // Returns empty entity if there is no config stored yet. Doesn't attempt to
88 // deserialize 'Config' protobuf field.
89 func FetchDelegationConfig(c context.Context) (*DelegationConfig, error) {
90 cfg := &DelegationConfig{}
91 switch err := ds.Get(c, cfg); {
92 case err == ds.ErrNoSuchEntity:
93 return cfg, nil
94 case err != nil:
95 return nil, errors.WrapTransient(err)
96 }
97 return cfg, nil
98 }
99
100 // DelegationConfigLoader constructs a function that lazy-loads delegation
101 // config and keeps it cached in memory, refreshing the cached copy each minute.
102 //
103 // Used as MintDelegationTokenRPC.ConfigLoader implementation in prod.
104 func DelegationConfigLoader() func(context.Context) (*DelegationConfig, error) {
105 slot := lazyslot.Slot{
106 Fetcher: func(c context.Context, prev lazyslot.Value) (lazyslot. Value, error) {
107 newCfg, err := FetchDelegationConfig(c)
108 if err != nil {
109 return lazyslot.Value{}, err
110 }
111
112 // Reuse existing unpacked validated config if the revis ion didn't change.
113 prevCfg, _ := prev.Value.(*DelegationConfig)
114 if prevCfg != nil && prevCfg.Revision == newCfg.Revision {
115 return lazyslot.Value{
116 Value: prevCfg,
117 Expiration: clock.Now(c).Add(procCacheEx piration),
118 }, nil
119 }
120
121 // An error here can happen if previously validated conf ig is no longer
122 // valid (e.g. if the service code is updated and new co de doesn't like
123 // the stored config anymore).
124 //
125 // If this check fails, the service is effectively offli ne until config is
126 // updated. Presumably, it is better than silently using no longer valid
127 // config.
128 logging.Infof(c, "Using delegation config at ref %s", ne wCfg.Revision)
129 if err := newCfg.Initialize(); err != nil {
130 logging.Errorf(c, "Existing delegation config is invalid - %s", err)
131 return lazyslot.Value{}, err
132 }
133
134 return lazyslot.Value{
135 Value: newCfg,
136 Expiration: clock.Now(c).Add(procCacheExpiration ),
137 }, nil
138 },
139 }
140
141 return func(c context.Context) (*DelegationConfig, error) {
142 val, err := slot.Get(c)
143 if err != nil {
144 return nil, err
145 }
146 return val.Value.(*DelegationConfig), nil
147 }
148 }
149
150 // Initialize parses the loaded config, initializing the guts of the object.
151 func (cfg *DelegationConfig) Initialize() error {
152 parsed := &admin.DelegationPermissions{}
153 if err := proto.Unmarshal(cfg.Config, parsed); err != nil {
154 return err
155 }
156
157 rules := make([]*delegationRule, len(parsed.Rules))
158 requestors := make([]*identityset.Set, len(parsed.Rules))
159
160 for i, msg := range parsed.Rules {
161 rule, err := makeDelegationRule(msg)
162 if err != nil {
163 return err
164 }
165 rules[i] = rule
166 requestors[i] = rule.requestors
167 }
168
169 cfg.ParsedConfig = parsed
170 cfg.rules = rules
171 cfg.requestors = identityset.Union(requestors...)
172
173 return nil
174 }
175
176 // makeDelegationRule preprocesses admin.DelegationRule proto.
177 //
178 // It also checks that the rule is passing validation.
179 func makeDelegationRule(rule *admin.DelegationRule) (*delegationRule, error) {
180 if merr := ValidateRule(rule); len(merr) != 0 {
181 return nil, merr
182 }
183
184 // The main validation step has been done above. Here we just assert tha t
185 // everything looks sane (it should). See corresponding chunks of
186 // 'ValidateRule' code.
187 requestors, err := identityset.FromStrings(rule.Requestor, nil)
188 if err != nil {
189 panic(err)
190 }
191 delegatees, err := identityset.FromStrings(rule.AllowedToImpersonate, sk ipRequestor)
192 if err != nil {
193 panic(err)
194 }
195 audience, err := identityset.FromStrings(rule.AllowedAudience, skipReque stor)
196 if err != nil {
197 panic(err)
198 }
199 services, err := identityset.FromStrings(rule.TargetService, nil)
200 if err != nil {
201 panic(err)
202 }
203
204 return &delegationRule{
205 rule: rule,
206 requestors: requestors,
207 delegatees: delegatees,
208 audience: audience,
209 services: services,
210 addRequestorAsDelegatee: sliceHasString(rule.AllowedToImpersonat e, Requestor),
211 addRequestorToAudience: sliceHasString(rule.AllowedAudience, Re questor),
212 }, nil
213 }
214
215 func skipRequestor(s string) bool {
216 return s == Requestor
217 }
218
219 func sliceHasString(slice []string, str string) bool {
220 for _, s := range slice {
221 if s == str {
222 return true
223 }
224 }
225 return false
226 }
227
228 // IsAuthorizedRequestor returns true if the caller belongs to 'requestor' set
229 // of at least one rule.
230 func (cfg *DelegationConfig) IsAuthorizedRequestor(c context.Context, id identit y.Identity) (bool, error) {
231 return cfg.requestors.IsMember(c, id)
232 }
233
234 // FindMatchingRule finds one and only one rule matching the query.
235 //
236 // If multiple rules match or none rules match, an error is returned.
237 func (cfg *DelegationConfig) FindMatchingRule(c context.Context, q *RulesQuery) (*admin.DelegationRule, error) {
238 var matches []*admin.DelegationRule
239 for _, rule := range cfg.rules {
240 switch yes, err := rule.matchesQuery(c, q); {
241 case err != nil:
242 return nil, err // usually transient
243 case yes:
244 matches = append(matches, rule.rule)
245 }
246 }
247
248 if len(matches) == 0 {
249 return nil, fmt.Errorf("no matching delegation rules in the conf ig")
250 }
251
252 if len(matches) > 1 {
253 names := make([]string, len(matches))
254 for i, m := range matches {
255 names[i] = fmt.Sprintf("%q", m.Name)
256 }
257 return nil, fmt.Errorf(
258 "ambiguous request, multiple delegation rules match (%s) ",
259 strings.Join(names, ", "))
260 }
261
262 return matches[0], nil
263 }
264
265 // matchesQuery returns true if this rule matches the query.
266 //
267 // See doc in config.proto, DelegationRule for exact description of when this
268 // happens. Basically, all sets in rule must be supersets of corresponding sets
269 // in RulesQuery.
270 //
271 // May return transient errors.
272 func (rule *delegationRule) matchesQuery(c context.Context, q *RulesQuery) (bool , error) {
273 // Rule's 'requestor' set contains the requestor?
274 switch found, err := rule.requestors.IsMember(c, q.Requestor); {
275 case err != nil:
276 return false, err
277 case !found:
278 return false, nil
279 }
280
281 // Rule's 'delegatee' set contains the identity being delegated/imperson ated?
282 allowedDelegatees := rule.delegatees
283 if rule.addRequestorAsDelegatee {
284 allowedDelegatees = identityset.Extend(allowedDelegatees, q.Requ estor)
285 }
286 switch found, err := allowedDelegatees.IsMember(c, q.Delegatee); {
287 case err != nil:
288 return false, err
289 case !found:
290 return false, nil
291 }
292
293 // Rule's 'audience' is superset of requested audience?
294 allowedAudience := rule.audience
295 if rule.addRequestorToAudience {
296 allowedAudience = identityset.Extend(allowedAudience, q.Requesto r)
297 }
298 if !allowedAudience.IsSuperset(q.Audience) {
299 return false, nil
300 }
301
302 // Rule's allowed targets is superset of requested targets?
303 if !rule.services.IsSuperset(q.Services) {
304 return false, nil
305 }
306
307 return true, nil
308 }
OLDNEW
« no previous file with comments | « tokenserver/api/token_file.pb.go ('k') | tokenserver/appengine/delegation/config_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698