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 |
+} |