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