Chromium Code Reviews| Index: server/auth/authdb/snapshot.go |
| diff --git a/server/auth/authdb/snapshot.go b/server/auth/authdb/snapshot.go |
| index 0ede9ac2fc201e786d9ca53f7b7d171b95bd2ec6..4d763d7e23db71afd7d189c1c447b5d34c0182dd 100644 |
| --- a/server/auth/authdb/snapshot.go |
| +++ b/server/auth/authdb/snapshot.go |
| @@ -8,12 +8,17 @@ import ( |
| "fmt" |
| "net" |
| "strings" |
| + "sync" |
| + "time" |
| "golang.org/x/net/context" |
| + "github.com/luci/luci-go/common/clock" |
| + "github.com/luci/luci-go/common/data/caching/lazyslot" |
| "github.com/luci/luci-go/common/logging" |
| "github.com/luci/luci-go/server/auth/identity" |
| "github.com/luci/luci-go/server/auth/service/protocol" |
| + "github.com/luci/luci-go/server/auth/signing" |
| "github.com/luci/luci-go/server/secrets" |
| ) |
| @@ -36,8 +41,14 @@ type SnapshotDB struct { |
| assignments map[identity.Identity]string // IP whitelist assignements |
| whitelists map[string][]net.IPNet // IP whitelists |
| + |
| + // Certs are loaded lazily in GetCertificates since they are used only when |
| + // checking delegation tokens, which is relatively rare. |
| + certs lazyslot.Slot |
|
Vadim Sh.
2016/10/01 04:04:43
lazyslot.Slot takes care of initial lazy fetch and
|
| } |
| +var _ DB = &SnapshotDB{} |
| + |
| // group is a node in a group graph. Nested groups are referenced directly via |
| // pointer. |
| type group struct { |
| @@ -46,7 +57,8 @@ type group struct { |
| nested []*group // pointers to nested groups |
| } |
| -var _ DB = &SnapshotDB{} |
| +// certMap is used in GetCertificate and fetchTrustedCerts. |
| +type certMap map[identity.Identity]*signing.PublicCertificates |
| // NewSnapshotDB creates new instance of SnapshotDB. |
| // |
| @@ -60,6 +72,18 @@ func NewSnapshotDB(authDB *protocol.AuthDB, authServiceURL string, rev int64) (* |
| tokenServiceURL: authDB.GetTokenServerUrl(), |
| } |
| + // Load all trusted certificates lazily and refresh each hour. |
| + db.certs.Fetcher = func(c context.Context, prev lazyslot.Value) (lazyslot.Value, error) { |
| + mapping, err := db.fetchTrustedCerts(c) |
| + if err != nil { |
| + return lazyslot.Value{}, err |
| + } |
| + return lazyslot.Value{ |
| + Value: mapping, |
| + Expiration: clock.Now(c).Add(time.Hour), |
| + }, nil |
| + } |
| + |
| // Set of all allowed clientIDs. |
| db.clientIDs = make(map[string]struct{}, 2+len(authDB.GetOauthAdditionalClientIds())) |
| db.clientIDs[googleAPIExplorerClientID] = struct{}{} |
| @@ -258,6 +282,16 @@ func (db *SnapshotDB) SharedSecrets(c context.Context) (secrets.Store, error) { |
| return db.secrets, nil |
| } |
| +// GetCertificates returns a bundle with certificates of a trusted signer. |
| +func (db *SnapshotDB) GetCertificates(c context.Context, signerID identity.Identity) (*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
|
| + val, err := db.certs.Get(c) |
| + if err != nil { |
| + return nil, err |
| + } |
| + trustedCertsMap := val.Value.(certMap) |
| + 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
|
| +} |
| + |
| // GetWhitelistForIdentity returns name of the IP whitelist to use to check |
| // IP of requests from given `ident`. |
| // |
| @@ -295,3 +329,60 @@ func (db *SnapshotDB) GetAuthServiceURL(c context.Context) (string, error) { |
| func (db *SnapshotDB) GetTokenServiceURL(c context.Context) (string, error) { |
| return db.tokenServiceURL, nil |
| } |
| + |
| +//// Implementation details. |
| + |
| +// fetchTrustedCerts is called by db.certs to fetch certificates. |
| +func (db *SnapshotDB) fetchTrustedCerts(c context.Context) (certMap, error) { |
| + // TODO(vadimsh): Stop loading certs from AuthServiceURL when all tokens |
| + // are generated by the token server. |
| + var urls []string |
| + if db.tokenServiceURL != "" { |
| + urls = []string{db.tokenServiceURL, db.AuthServiceURL} |
| + } else { |
| + urls = []string{db.AuthServiceURL} |
| + } |
| + |
| + certs := make([]*signing.PublicCertificates, len(urls)) |
| + errs := make([]error, len(urls)) |
| + |
| + // 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
|
| + wg := sync.WaitGroup{} |
| + for i, url := range urls { |
| + 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.
|
| + url := url |
| + wg.Add(1) |
| + go func() { |
| + certs[i], errs[i] = signing.FetchCertificatesFromLUCIService(c, url) |
| + 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.
|
| + }() |
| + } |
| + wg.Wait() |
| + |
| + // Each certificate bundle is available under two keys: "service:<app-id>" and |
| + // "user:<service-account-name>". This is so because there's one to one |
| + // association between GAE apps and corresponding @appspot.gserviceaccount.com |
| + // service accounts. |
| + mapping := make(certMap, 2*len(certs)) |
| + for i, cert := range certs { |
| + if errs[i] != nil { |
| + return nil, errs[i] |
| + } |
| + if cert.AppID != "" { |
| + id, err := identity.MakeIdentity("service:" + cert.AppID) |
| + if err != nil { |
| + return nil, fmt.Errorf("invalid app_id %q in fetched certificates bundle - %s", cert.AppID, err) |
| + } |
| + mapping[id] = cert |
| + } |
| + if cert.ServiceAccountName != "" { |
| + id, err := identity.MakeIdentity("user:" + cert.ServiceAccountName) |
| + if err != nil { |
| + return nil, fmt.Errorf("invalid service_account_name %q in fetched certificates bundle - %s", cert.ServiceAccountName, err) |
| + } |
| + mapping[id] = cert |
| + } |
| + } |
| + |
| + return mapping, nil |
| +} |