Chromium Code Reviews| 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 |