| Index: server/auth/authdb/snapshot.go
|
| diff --git a/server/auth/authdb/snapshot.go b/server/auth/authdb/snapshot.go
|
| index 0ede9ac2fc201e786d9ca53f7b7d171b95bd2ec6..2906fa703f9837706dbe933c1d7beb0f3929971e 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
|
| }
|
|
|
| +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) {
|
| + val, err := db.certs.Get(c)
|
| + if err != nil {
|
| + return nil, err
|
| + }
|
| + trustedCertsMap := val.Value.(certMap)
|
| + return trustedCertsMap[signerID], nil
|
| +}
|
| +
|
| // GetWhitelistForIdentity returns name of the IP whitelist to use to check
|
| // IP of requests from given `ident`.
|
| //
|
| @@ -295,3 +329,58 @@ 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.
|
| + wg := sync.WaitGroup{}
|
| + for i := range urls {
|
| + wg.Add(1)
|
| + go func(i int) {
|
| + defer wg.Done()
|
| + certs[i], errs[i] = signing.FetchCertificatesFromLUCIService(c, urls[i])
|
| + }(i)
|
| + }
|
| + 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
|
| +}
|
|
|