OLD | NEW |
---|---|
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 authdb | 5 package authdb |
6 | 6 |
7 import ( | 7 import ( |
8 "fmt" | 8 "fmt" |
9 "net" | 9 "net" |
10 "strings" | 10 "strings" |
11 "sync" | |
12 "time" | |
11 | 13 |
12 "golang.org/x/net/context" | 14 "golang.org/x/net/context" |
13 | 15 |
16 "github.com/luci/luci-go/common/clock" | |
17 "github.com/luci/luci-go/common/data/caching/lazyslot" | |
14 "github.com/luci/luci-go/common/logging" | 18 "github.com/luci/luci-go/common/logging" |
15 "github.com/luci/luci-go/server/auth/identity" | 19 "github.com/luci/luci-go/server/auth/identity" |
16 "github.com/luci/luci-go/server/auth/service/protocol" | 20 "github.com/luci/luci-go/server/auth/service/protocol" |
21 "github.com/luci/luci-go/server/auth/signing" | |
17 "github.com/luci/luci-go/server/secrets" | 22 "github.com/luci/luci-go/server/secrets" |
18 ) | 23 ) |
19 | 24 |
20 // OAuth client_id of https://apis-explorer.appspot.com/. | 25 // OAuth client_id of https://apis-explorer.appspot.com/. |
21 const googleAPIExplorerClientID = "292824132082.apps.googleusercontent.com" | 26 const googleAPIExplorerClientID = "292824132082.apps.googleusercontent.com" |
22 | 27 |
23 // SnapshotDB implements DB using AuthDB proto message. | 28 // SnapshotDB implements DB using AuthDB proto message. |
24 // | 29 // |
25 // Use NewSnapshotDB to create new instances. Don't touch public fields | 30 // Use NewSnapshotDB to create new instances. Don't touch public fields |
26 // of existing instances. | 31 // of existing instances. |
27 type SnapshotDB struct { | 32 type SnapshotDB struct { |
28 AuthServiceURL string // where it was fetched from | 33 AuthServiceURL string // where it was fetched from |
29 Rev int64 // its revision number | 34 Rev int64 // its revision number |
30 | 35 |
31 tokenServiceURL string // URL of the token server as provided by Auth se rvice | 36 tokenServiceURL string // URL of the token server as provided by Auth se rvice |
32 | 37 |
33 clientIDs map[string]struct{} // set of allowed client IDs | 38 clientIDs map[string]struct{} // set of allowed client IDs |
34 groups map[string]*group // map of all known groups | 39 groups map[string]*group // map of all known groups |
35 secrets secrets.StaticStore // secrets shared by all service with this DB | 40 secrets secrets.StaticStore // secrets shared by all service with this DB |
36 | 41 |
37 assignments map[identity.Identity]string // IP whitelist assignements | 42 assignments map[identity.Identity]string // IP whitelist assignements |
38 whitelists map[string][]net.IPNet // IP whitelists | 43 whitelists map[string][]net.IPNet // IP whitelists |
44 | |
45 // Certs are loaded lazily in GetCertificates since they are used only w hen | |
46 // checking delegation tokens, which is relatively rare. | |
47 certs lazyslot.Slot | |
Vadim Sh.
2016/10/01 04:04:43
lazyslot.Slot takes care of initial lazy fetch and
| |
39 } | 48 } |
40 | 49 |
50 var _ DB = &SnapshotDB{} | |
51 | |
41 // group is a node in a group graph. Nested groups are referenced directly via | 52 // group is a node in a group graph. Nested groups are referenced directly via |
42 // pointer. | 53 // pointer. |
43 type group struct { | 54 type group struct { |
44 members map[identity.Identity]struct{} // set of all members | 55 members map[identity.Identity]struct{} // set of all members |
45 globs []identity.Glob // list of all identity globs | 56 globs []identity.Glob // list of all identity globs |
46 nested []*group // pointers to nested groups | 57 nested []*group // pointers to nested groups |
47 } | 58 } |
48 | 59 |
49 var _ DB = &SnapshotDB{} | 60 // certMap is used in GetCertificate and fetchTrustedCerts. |
61 type certMap map[identity.Identity]*signing.PublicCertificates | |
50 | 62 |
51 // NewSnapshotDB creates new instance of SnapshotDB. | 63 // NewSnapshotDB creates new instance of SnapshotDB. |
52 // | 64 // |
53 // It does some preprocessing to speed up subsequent checks. Return errors if | 65 // It does some preprocessing to speed up subsequent checks. Return errors if |
54 // it encounters inconsistencies. | 66 // it encounters inconsistencies. |
55 func NewSnapshotDB(authDB *protocol.AuthDB, authServiceURL string, rev int64) (* SnapshotDB, error) { | 67 func NewSnapshotDB(authDB *protocol.AuthDB, authServiceURL string, rev int64) (* SnapshotDB, error) { |
Vadim Sh.
2016/10/03 20:47:12
this is main prod implementation
| |
56 db := &SnapshotDB{ | 68 db := &SnapshotDB{ |
57 AuthServiceURL: authServiceURL, | 69 AuthServiceURL: authServiceURL, |
58 Rev: rev, | 70 Rev: rev, |
59 | 71 |
60 tokenServiceURL: authDB.GetTokenServerUrl(), | 72 tokenServiceURL: authDB.GetTokenServerUrl(), |
61 } | 73 } |
62 | 74 |
75 // Load all trusted certificates lazily and refresh each hour. | |
76 db.certs.Fetcher = func(c context.Context, prev lazyslot.Value) (lazyslo t.Value, error) { | |
77 mapping, err := db.fetchTrustedCerts(c) | |
78 if err != nil { | |
79 return lazyslot.Value{}, err | |
80 } | |
81 return lazyslot.Value{ | |
82 Value: mapping, | |
83 Expiration: clock.Now(c).Add(time.Hour), | |
84 }, nil | |
85 } | |
86 | |
63 // Set of all allowed clientIDs. | 87 // Set of all allowed clientIDs. |
64 db.clientIDs = make(map[string]struct{}, 2+len(authDB.GetOauthAdditional ClientIds())) | 88 db.clientIDs = make(map[string]struct{}, 2+len(authDB.GetOauthAdditional ClientIds())) |
65 db.clientIDs[googleAPIExplorerClientID] = struct{}{} | 89 db.clientIDs[googleAPIExplorerClientID] = struct{}{} |
66 if authDB.GetOauthClientId() != "" { | 90 if authDB.GetOauthClientId() != "" { |
67 db.clientIDs[authDB.GetOauthClientId()] = struct{}{} | 91 db.clientIDs[authDB.GetOauthClientId()] = struct{}{} |
68 } | 92 } |
69 for _, cid := range authDB.GetOauthAdditionalClientIds() { | 93 for _, cid := range authDB.GetOauthAdditionalClientIds() { |
70 if cid != "" { | 94 if cid != "" { |
71 db.clientIDs[cid] = struct{}{} | 95 db.clientIDs[cid] = struct{}{} |
72 } | 96 } |
(...skipping 178 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
251 } | 275 } |
252 | 276 |
253 // SharedSecrets is secrets.Store with secrets in Auth DB. | 277 // SharedSecrets is secrets.Store with secrets in Auth DB. |
254 // | 278 // |
255 // Such secrets are usually generated on central Auth Service and are known | 279 // Such secrets are usually generated on central Auth Service and are known |
256 // to all trusted services (so that they can use them to exchange data). | 280 // to all trusted services (so that they can use them to exchange data). |
257 func (db *SnapshotDB) SharedSecrets(c context.Context) (secrets.Store, error) { | 281 func (db *SnapshotDB) SharedSecrets(c context.Context) (secrets.Store, error) { |
258 return db.secrets, nil | 282 return db.secrets, nil |
259 } | 283 } |
260 | 284 |
285 // GetCertificates returns a bundle with certificates of a trusted signer. | |
286 func (db *SnapshotDB) GetCertificates(c context.Context, signerID identity.Ident ity) (*signing.PublicCertificates, error) { | |
Vadim Sh.
2016/10/01 04:04:43
this is the main implementation of GetCertificates
M-A Ruel
2016/10/01 11:18:41
Which brings me: why so many mocks?
Vadim Sh.
2016/10/03 20:47:12
Replied in separate comments. They are all necessa
| |
287 val, err := db.certs.Get(c) | |
288 if err != nil { | |
289 return nil, err | |
290 } | |
291 trustedCertsMap := val.Value.(certMap) | |
292 return trustedCertsMap[signerID], nil | |
M-A Ruel
2016/10/01 11:18:41
Humm this will panic if the signerID is missing in
Vadim Sh.
2016/10/03 20:47:12
No, it will return 'nil'. Maps don't panic, they r
| |
293 } | |
294 | |
261 // GetWhitelistForIdentity returns name of the IP whitelist to use to check | 295 // GetWhitelistForIdentity returns name of the IP whitelist to use to check |
262 // IP of requests from given `ident`. | 296 // IP of requests from given `ident`. |
263 // | 297 // |
264 // It's used to restrict access for certain account to certain IP subnets. | 298 // It's used to restrict access for certain account to certain IP subnets. |
265 // | 299 // |
266 // Returns ("", nil) if `ident` is not IP restricted. | 300 // Returns ("", nil) if `ident` is not IP restricted. |
267 func (db *SnapshotDB) GetWhitelistForIdentity(c context.Context, ident identity. Identity) (string, error) { | 301 func (db *SnapshotDB) GetWhitelistForIdentity(c context.Context, ident identity. Identity) (string, error) { |
268 return db.assignments[ident], nil | 302 return db.assignments[ident], nil |
269 } | 303 } |
270 | 304 |
(...skipping 17 matching lines...) Expand all Loading... | |
288 func (db *SnapshotDB) GetAuthServiceURL(c context.Context) (string, error) { | 322 func (db *SnapshotDB) GetAuthServiceURL(c context.Context) (string, error) { |
289 return db.AuthServiceURL, nil | 323 return db.AuthServiceURL, nil |
290 } | 324 } |
291 | 325 |
292 // GetTokenServiceURL returns root URL ("https://<host>") of the token server. | 326 // GetTokenServiceURL returns root URL ("https://<host>") of the token server. |
293 // | 327 // |
294 // This is needed to implement authdb.DB interface. | 328 // This is needed to implement authdb.DB interface. |
295 func (db *SnapshotDB) GetTokenServiceURL(c context.Context) (string, error) { | 329 func (db *SnapshotDB) GetTokenServiceURL(c context.Context) (string, error) { |
296 return db.tokenServiceURL, nil | 330 return db.tokenServiceURL, nil |
297 } | 331 } |
332 | |
333 //// Implementation details. | |
334 | |
335 // fetchTrustedCerts is called by db.certs to fetch certificates. | |
336 func (db *SnapshotDB) fetchTrustedCerts(c context.Context) (certMap, error) { | |
337 // TODO(vadimsh): Stop loading certs from AuthServiceURL when all tokens | |
338 // are generated by the token server. | |
339 var urls []string | |
340 if db.tokenServiceURL != "" { | |
341 urls = []string{db.tokenServiceURL, db.AuthServiceURL} | |
342 } else { | |
343 urls = []string{db.AuthServiceURL} | |
344 } | |
345 | |
346 certs := make([]*signing.PublicCertificates, len(urls)) | |
347 errs := make([]error, len(urls)) | |
348 | |
349 // Fetch in parallel. | |
M-A Ruel
2016/10/01 11:18:41
I'm surprised there isn't a package to do this yet
Vadim Sh.
2016/10/03 20:47:12
There probably is one, but I find it simpler to us
| |
350 wg := sync.WaitGroup{} | |
351 for i, url := range urls { | |
352 i := i | |
M-A Ruel
2016/10/01 11:18:41
optional nit: I prefer passing flags on the functi
Vadim Sh.
2016/10/03 20:47:12
Done.
| |
353 url := url | |
354 wg.Add(1) | |
355 go func() { | |
356 certs[i], errs[i] = signing.FetchCertificatesFromLUCISer vice(c, url) | |
357 wg.Done() | |
M-A Ruel
2016/10/01 11:18:41
optional nit: I generally prefer
defer wg.Done()
o
Vadim Sh.
2016/10/03 20:47:12
Done.
| |
358 }() | |
359 } | |
360 wg.Wait() | |
361 | |
362 // Each certificate bundle is available under two keys: "service:<app-id >" and | |
363 // "user:<service-account-name>". This is so because there's one to one | |
364 // association between GAE apps and corresponding @appspot.gserviceaccou nt.com | |
365 // service accounts. | |
366 mapping := make(certMap, 2*len(certs)) | |
367 for i, cert := range certs { | |
368 if errs[i] != nil { | |
369 return nil, errs[i] | |
370 } | |
371 if cert.AppID != "" { | |
372 id, err := identity.MakeIdentity("service:" + cert.AppID ) | |
373 if err != nil { | |
374 return nil, fmt.Errorf("invalid app_id %q in fet ched certificates bundle - %s", cert.AppID, err) | |
375 } | |
376 mapping[id] = cert | |
377 } | |
378 if cert.ServiceAccountName != "" { | |
379 id, err := identity.MakeIdentity("user:" + cert.ServiceA ccountName) | |
380 if err != nil { | |
381 return nil, fmt.Errorf("invalid service_account_ name %q in fetched certificates bundle - %s", cert.ServiceAccountName, err) | |
382 } | |
383 mapping[id] = cert | |
384 } | |
385 } | |
386 | |
387 return mapping, nil | |
388 } | |
OLD | NEW |