// Polls the Revocation Provider for new revocations and adds them to the revocation cache; handles the Force Refresh // condition (e.g. refresh cache from a specific timestamp); expires revocations older than the // REVOCATION_CACHE_TTL envionment variable. func (crp *CachingRevokeProvider) RefreshRevocations() { ts := crp.cache.GetLastTS() if ts == 0 { ts = int(time.Now().Add(-1 * options.AppSettings.RevocationCacheTTL).Unix()) } ts = ts - int(options.AppSettings.RevocationRefreshTolerance.Seconds()) log.Printf("Checking for new revocations since %d...", ts) resp, err := breaker.Get("refreshRevocations", crp.url+"?from="+strconv.Itoa(ts)) if err != nil { log.Println("Failed to get revocations. " + err.Error()) return } if resp.StatusCode != http.StatusOK { log.Printf("Failed to get revocations. Server returned status %s.", resp.Status) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) jr := &jsonRevoke{} if err := json.Unmarshal(body, &jr); err != nil { log.Println("Failed to unmarshall revocation data. " + err.Error()) return } if jr.Meta.RefreshTimestamp != 0 { r := crp.cache.Get(REVOCATION_TYPE_FORCEREFRESH) if r == nil || (r.(*Revocation).Data["revoked_at"] != jr.Meta.RefreshTimestamp) { log.Printf("Force refreshing cache from %d...", jr.Meta.RefreshFrom) crp.cache.ForceRefresh(jr.Meta.RefreshFrom) rev := &Revocation{} d := make(map[string]interface{}) rev.Type = REVOCATION_TYPE_FORCEREFRESH d["refresh_from"] = jr.Meta.RefreshFrom d["revoked_at"] = jr.Meta.RefreshTimestamp rev.Data = d crp.cache.Add(rev) } } if len(jr.Revs) > 0 { log.Printf("Received %d new revocations", len(jr.Revs)) } for _, j := range jr.Revs { r, err := j.toRevocation() if err == nil { crp.cache.Add(r) } } crp.cache.Expire() }
// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse func (kl *cachingOpenIDProviderLoader) loadConfiguration() (*configuration, error) { resp, err := breaker.Get("loadConfiguration", kl.url) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, errInvalidResponseStatusCode } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } config := new(configuration) err = json.Unmarshal(body, config) return config, err }
// Example: https://www.googleapis.com/oauth2/v3/certs func (kl *cachingOpenIDProviderLoader) refreshKeys() { log.Println("Refreshing keys..") log.Println("Loading configuration..") c, err := kl.loadConfiguration() if err != nil { log.Printf("Failed to get configuration from %q. %s\n", kl.url, err) return } log.Println("Configuration loaded successfully, loading JWKS..") resp, err := breaker.Get("loadKeys", c.JwksURI) if err != nil { log.Println("Failed to get JWKS from ", c.JwksURI) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Printf("Failed to read JWKS response body from %q: %v\n", c.JwksURI, err) return } log.Println("JWKS loaded successfully, parsing JWKS..") jwks := new(jwk.JSONWebKeySet) if err = json.Unmarshal(body, jwks); err != nil { log.Println("Failed to parse JWKS: ", err) return } // safety first: only remove public keys if our newly // received list contains at least one public key! // (we don't want our tokeninfo to run out of public keys // just because somebody cleared the provider database) numKeys := len(jwks.Keys) if numKeys < 1 { log.Println("No JWKS currently in the OpenID provider") if c, ok := metrics.DefaultRegistry.GetOrRegister(metricsNoKeysError, metrics.NewCounter).(metrics.Counter); ok { c.Inc(1) } return } if g, ok := metrics.DefaultRegistry.GetOrRegister(metricsNumKeys, metrics.NewGauge).(metrics.Gauge); ok { g.Update(int64(numKeys)) } newKeys := jwks.ToMap() for kid, k := range newKeys { key := k.(jwk.JSONWebKey) existing := kl.keyCache.Get(kid) if existing == nil { log.Printf("Received new public key %q (%s)\n", kid, key.Algorithm) } else if !reflect.DeepEqual(existing, key) { // this is potentially dangerous: the key contents changed.. // (but maybe the key wasn't used for signing yet, so it might be ok) log.Printf("Received a replacement public key for existing key %q (%s)", kid, key.Algorithm) } } log.Printf("Resetting key cache with %d key(s)..", numKeys) kl.keyCache.Reset(newKeys) log.Println("Refresh done..") }