fix application bug

This commit is contained in:
Jeff
2019-05-13 11:19:18 +08:00
committed by zryfish
parent 996d6fe4c5
commit 5462f51e65
717 changed files with 87703 additions and 53426 deletions

View File

@@ -21,149 +21,251 @@ import (
)
// Cache is a structure that stores certificates in memory.
// Generally, there should only be one per process. However,
// complex applications that virtualize the concept of a
// "process" (such as Caddy, which virtualizes processes as
// "instances" so it can do graceful, in-memory reloads of
// its configuration) may use more of these per OS process.
// A Cache indexes certificates by name for quick access
// during TLS handshakes, and avoids duplicating certificates
// in memory. Generally, there should only be one per process.
// However, that is not a strict requirement; but using more
// than one is a code smell, and may indicate an
// over-engineered design.
//
// Using just one cache per process avoids duplication of
// certificates across multiple configurations and makes
// maintenance easier.
// An empty cache is INVALID and must not be used. Be sure
// to call NewCache to get a valid value.
//
// An empty cache is INVALID and must not be used.
// Be sure to call NewCertificateCache to get one.
//
// These should be very long-lived values, and must not be
// These should be very long-lived values and must not be
// copied. Before all references leave scope to be garbage
// collected, ensure you call Stop() to stop maintenance
// maintenance on the certificates stored in this cache.
// collected, ensure you call Stop() to stop maintenance on
// the certificates stored in this cache and release locks.
//
// Caches are not usually manipulated directly; create a
// Config value with a pointer to a Cache, and then use
// the Config to interact with the cache. Caches are
// agnostic of any particular storage or ACME config,
// since each certificate may be managed and stored
// differently.
type Cache struct {
// How often to check certificates for renewal
RenewInterval time.Duration
// How often to check if OCSP stapling needs updating
OCSPInterval time.Duration
// The storage implementation
storage Storage
// User configuration of the cache
options CacheOptions
// The cache is keyed by certificate hash
cache map[string]Certificate
// Protects the cache map
// cacheIndex is a map of SAN to cache key (cert hash)
cacheIndex map[string][]string
// Protects the cache and index maps
mu sync.RWMutex
// Close this channel to cancel asset maintenance
stopChan chan struct{}
// Used to signal when stopping is completed
doneChan chan struct{}
}
// NewCache returns a new, valid Cache backed by the
// given storage implementation. It also begins a
// maintenance goroutine for any managed certificates
// stored in this cache.
// NewCache returns a new, valid Cache for efficiently
// accessing certificates in memory. It also begins a
// maintenance goroutine to tend to the certificates
// in the cache. Call Stop() when you are done with the
// cache so it can clean up locks and stuff.
//
// See the godoc for Cache to use it properly.
// Most users of this package will not need to call this
// because a default certificate cache is created for you.
// Only advanced use cases require creating a new cache.
//
// Note that all processes running in a cluster
// configuration must use the same storage value
// in order to share certificates. (A single storage
// value may be shared by multiple clusters as well.)
func NewCache(storage Storage) *Cache {
c := &Cache{
RenewInterval: DefaultRenewInterval,
OCSPInterval: DefaultOCSPInterval,
storage: storage,
cache: make(map[string]Certificate),
stopChan: make(chan struct{}),
// This function panics if opts.GetConfigForCert is not
// set. The reason is that a cache absolutely needs to
// be able to get a Config with which to manage TLS
// assets, and it is not safe to assume that the Default
// config is always the correct one, since you have
// created the cache yourself.
//
// See the godoc for Cache to use it properly. When
// no longer needed, caches should be stopped with
// Stop() to clean up resources even if the process
// is being terminated, so that it can clean up
// any locks for other processes to unblock!
func NewCache(opts CacheOptions) *Cache {
// assume default options if necessary
if opts.OCSPCheckInterval <= 0 {
opts.OCSPCheckInterval = DefaultOCSPCheckInterval
}
if opts.RenewCheckInterval <= 0 {
opts.RenewCheckInterval = DefaultRenewCheckInterval
}
// this must be set, because we cannot not
// safely assume that the Default Config
// is always the correct one to use
if opts.GetConfigForCert == nil {
panic("cache must be initialized with a GetConfigForCert callback")
}
c := &Cache{
options: opts,
cache: make(map[string]Certificate),
cacheIndex: make(map[string][]string),
stopChan: make(chan struct{}),
doneChan: make(chan struct{}),
}
go c.maintainAssets()
return c
}
// Stop stops the maintenance goroutine for
// certificates in certCache.
// certificates in certCache. It blocks until
// stopping is complete. Once a cache is
// stopped, it cannot be reused.
func (certCache *Cache) Stop() {
close(certCache.stopChan)
close(certCache.stopChan) // signal to stop
<-certCache.doneChan // wait for stop to complete
}
// replaceCertificate replaces oldCert with newCert in the cache, and
// updates all configs that are pointing to the old certificate to
// point to the new one instead. newCert must already be loaded into
// the cache (this method does NOT load it into the cache).
// CacheOptions is used to configure certificate caches.
// Once a cache has been created with certain options,
// those settings cannot be changed.
type CacheOptions struct {
// REQUIRED. A function that returns a configuration
// used for managing a certificate, or for accessing
// that certificate's asset storage (e.g. for
// OCSP staples, etc). The returned Config MUST
// be associated with the same Cache as the caller.
//
// The reason this is a callback function, dynamically
// returning a Config (instead of attaching a static
// pointer to a Config on each certificate) is because
// the config for how to manage a domain's certificate
// might change from maintenance to maintenance. The
// cache is so long-lived, we cannot assume that the
// host's situation will always be the same; e.g. the
// certificate might switch DNS providers, so the DNS
// challenge (if used) would need to be adjusted from
// the last time it was run ~8 weeks ago.
GetConfigForCert ConfigGetter
// How often to check certificates for renewal;
// if unset, DefaultOCSPCheckInterval will be used.
OCSPCheckInterval time.Duration
// How often to check certificates for renewal;
// if unset, DefaultRenewCheckInterval will be used.
RenewCheckInterval time.Duration
}
// ConfigGetter is a function that returns a config that
// should be used when managing the given certificate
// or its assets.
type ConfigGetter func(Certificate) (Config, error)
// cacheCertificate calls unsyncedCacheCertificate with a write lock.
//
// Note that all the names on the old certificate will be deleted
// from the name lookup maps of each config, then all the names on
// the new certificate will be added to the lookup maps as long as
// they do not overwrite any entries.
// This function is safe for concurrent use.
func (certCache *Cache) cacheCertificate(cert Certificate) {
certCache.mu.Lock()
certCache.unsyncedCacheCertificate(cert)
certCache.mu.Unlock()
}
// unsyncedCacheCertificate adds cert to the in-memory cache unless
// it already exists in the cache (according to cert.Hash). It
// updates the name index.
//
// The newCert may be modified and its cache entry updated.
// This function is NOT safe for concurrent use. Callers MUST acquire
// a write lock on certCache.mu first.
func (certCache *Cache) unsyncedCacheCertificate(cert Certificate) {
// no-op if this certificate already exists in the cache
if _, ok := certCache.cache[cert.Hash]; ok {
return
}
// store the certificate
certCache.cache[cert.Hash] = cert
// update the index so we can access it by name
for _, name := range cert.Names {
certCache.cacheIndex[name] = append(certCache.cacheIndex[name], cert.Hash)
}
}
// removeCertificate removes cert from the cache.
//
// This function is NOT safe for concurrent use; callers
// MUST first acquire a write lock on certCache.mu.
func (certCache *Cache) removeCertificate(cert Certificate) {
// delete all mentions of this cert from the name index
for _, name := range cert.Names {
keyList := certCache.cacheIndex[name]
for i, cacheKey := range keyList {
if cacheKey == cert.Hash {
keyList = append(keyList[:i], keyList[i+1:]...)
}
}
if len(keyList) == 0 {
delete(certCache.cacheIndex, name)
} else {
certCache.cacheIndex[name] = keyList
}
}
// delete the actual cert from the cache
delete(certCache.cache, cert.Hash)
}
// replaceCertificate atomically replaces oldCert with newCert in
// the cache.
//
// This method is safe for concurrent use.
func (certCache *Cache) replaceCertificate(oldCert, newCert Certificate) error {
func (certCache *Cache) replaceCertificate(oldCert, newCert Certificate) {
certCache.mu.Lock()
defer certCache.mu.Unlock()
// have all the configs that are pointing to the old
// certificate point to the new certificate instead
for _, cfg := range oldCert.configs {
// first delete all the name lookup entries that
// pointed to the old certificate
for name, certKey := range cfg.certificates {
if certKey == oldCert.Hash {
delete(cfg.certificates, name)
}
}
// then add name lookup entries for the names
// on the new certificate, but don't overwrite
// entries that may already exist, not only as
// a courtesy, but importantly: because if we
// overwrote a value here, and this config no
// longer pointed to a certain certificate in
// the cache, that certificate's list of configs
// referring to it would be incorrect; so just
// insert entries, don't overwrite any
for _, name := range newCert.Names {
if _, ok := cfg.certificates[name]; !ok {
cfg.certificates[name] = newCert.Hash
}
}
}
// since caching a new certificate attaches only the config
// that loaded it, the new certificate needs to be given the
// list of all the configs that use it, so copy the list
// over from the old certificate to the new certificate
// in the cache
newCert.configs = oldCert.configs
certCache.cache[newCert.Hash] = newCert
// finally, delete the old certificate from the cache
delete(certCache.cache, oldCert.Hash)
return nil
certCache.removeCertificate(oldCert)
certCache.unsyncedCacheCertificate(newCert)
certCache.mu.Unlock()
}
// reloadManagedCertificate reloads the certificate corresponding to the name(s)
// on oldCert into the cache, from storage. This also replaces the old certificate
// with the new one, so that all configurations that used the old cert now point
// to the new cert.
func (certCache *Cache) reloadManagedCertificate(oldCert Certificate) error {
// get the certificate from storage and cache it
newCert, err := oldCert.configs[0].CacheManagedCertificate(oldCert.Names[0])
if err != nil {
return fmt.Errorf("unable to reload certificate for %v into cache: %v", oldCert.Names, err)
func (certCache *Cache) getFirstMatchingCert(name string) (Certificate, bool) {
certCache.mu.RLock()
defer certCache.mu.RUnlock()
allCertKeys := certCache.cacheIndex[name]
if len(allCertKeys) == 0 {
return Certificate{}, false
}
// and replace the old certificate with the new one
err = certCache.replaceCertificate(oldCert, newCert)
if err != nil {
return fmt.Errorf("replacing certificate %v: %v", oldCert.Names, err)
}
return nil
cert, ok := certCache.cache[allCertKeys[0]]
return cert, ok
}
var defaultCache *Cache
var defaultCacheMu sync.Mutex
// TODO: This seems unused (but could be useful if TLS
// handshakes serve up different certs for a single
// name depending on other properties such as key type)
func (certCache *Cache) getAllMatchingCerts(name string) []Certificate {
certCache.mu.RLock()
defer certCache.mu.RUnlock()
allCertKeys := certCache.cacheIndex[name]
certs := make([]Certificate, len(allCertKeys))
for i := range allCertKeys {
certs[i] = certCache.cache[allCertKeys[i]]
}
return certs
}
func (certCache *Cache) getConfig(cert Certificate) (*Config, error) {
cfg, err := certCache.options.GetConfigForCert(cert)
if err != nil {
return nil, err
}
if cfg.certCache != nil && cfg.certCache != certCache {
return nil, fmt.Errorf("config returned for certificate %v is not nil and points to different cache; got %p, expected %p (this one)",
cert.Names, cfg.certCache, certCache)
}
return New(certCache, cfg), nil
}
var (
defaultCache *Cache
defaultCacheMu sync.Mutex
)