Update dependencies

This commit is contained in:
Ken-Håvard Lieng 2019-06-09 02:01:48 +02:00
parent 7ad76273c0
commit 540efa03c4
485 changed files with 57821 additions and 12429 deletions

View file

@ -15,8 +15,8 @@ install:
- go get github.com/alecthomas/gometalinter
script:
- gometalinter --install
- gometalinter --disable-all -E vet -E gofmt -E misspell -E ineffassign -E goimports -E deadcode --tests ./...
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.15.0
- golangci-lint run --disable-all -E vet -E gofmt -E misspell -E ineffassign -E goimports -E deadcode --tests
- go test -race ./...
after_script:

View file

@ -84,7 +84,7 @@ CertMagic - Automatic HTTPS using Let's Encrypt
- Full control over almost every aspect of the system
- HTTP->HTTPS redirects (for HTTP applications)
- Solves all 3 ACME challenges: HTTP, TLS-ALPN, and DNS
- Over 50 DNS providers work out-of-the-box (powered by [lego](https://github.com/xenolf/lego)!)
- Over 50 DNS providers work out-of-the-box (powered by [lego](https://github.com/go-acme/lego)!)
- Pluggable storage implementations (default: file system)
- Wildcard certificates (requires DNS challenge)
- OCSP stapling for each qualifying certificate ([done right](https://gist.github.com/sleevi/5efe9ef98961ecfb4da8#gistcomment-2336055))
@ -136,18 +136,20 @@ This library uses Let's Encrypt by default, but you can use any certificate auth
#### The `Config` type
The `certmagic.Config` struct is how you can wield the power of this fully armed and operational battle station. However, an empty config is _not_ a valid one! In time, you will learn to use the force of `certmagic.New(certmagic.Config{...})` as I have.
The `certmagic.Config` struct is how you can wield the power of this fully armed and operational battle station. However, an empty/uninitialized `Config` is _not_ a valid one! In time, you will learn to use the force of `certmagic.NewDefault()` as I have.
#### Defaults
For every field in the `Config` struct, there is a corresponding package-level variable you can set as a default value. These defaults will be used when you call any of the high-level convenience functions like `HTTPS()` or `Listen()` or anywhere else a default `Config` is used. They are also used for any `Config` fields that are zero-valued when you call `New()`.
The default `Config` value is called `certmagic.Default`. Change its fields to suit your needs, then call `certmagic.NewDefault()` when you need a valid `Config` value. In other words, `certmagic.Default` is a template and is not valid for use directly.
You can set these values easily, for example: `certmagic.Email = ...` sets the email address to use for everything unless you explicitly override it in a Config.
You can set the default values easily, for example: `certmagic.Default.Email = ...`.
The high-level functions in this package (`HTTPS()`, `Listen()`, and `Manage()`) use the default config exclusively. This is how most of you will interact with the package. This is suitable when all your certificates are managed the same way. However, if you need to manage certificates differently depending on their name, you will need to make your own cache and configs (keep reading).
#### Providing an email address
Although not strictly required, this is highly recommended best practice. It allows you to receive expiration emails if your certificates are expiring for some reason, and also allows the CA's engineers to potentially get in touch with you if something is wrong. I recommend setting `certmagic.Email` or always setting the `Email` field of the `Config` struct.
Although not strictly required, this is highly recommended best practice. It allows you to receive expiration emails if your certificates are expiring for some reason, and also allows the CA's engineers to potentially get in touch with you if something is wrong. I recommend setting `certmagic.Default.Email` or always setting the `Email` field of a new `Config` struct.
### Development and Testing
@ -156,7 +158,7 @@ Note that Let's Encrypt imposes [strict rate limits](https://letsencrypt.org/doc
While developing your application and testing it, use [their staging endpoint](https://letsencrypt.org/docs/staging-environment/) which has much higher rate limits. Even then, don't hammer it: but it's much safer for when you're testing. When deploying, though, use their production CA because their staging CA doesn't issue trusted certificates.
To use staging, set `certmagic.CA = certmagic.LetsEncryptStagingCA` or set `CA` of every `Config` struct.
To use staging, set `certmagic.Default.CA = certmagic.LetsEncryptStagingCA` or set `CA` of every `Config` struct.
@ -164,20 +166,22 @@ To use staging, set `certmagic.CA = certmagic.LetsEncryptStagingCA` or set `CA`
There are many ways to use this library. We'll start with the highest-level (simplest) and work down (more control).
All these high-level examples use `certmagic.Default` for the config and the default cache and storage for serving up certificates.
First, we'll follow best practices and do the following:
```go
// read and agree to your CA's legal documents
certmagic.Agreed = true
certmagic.Default.Agreed = true
// provide an email address
certmagic.Email = "you@yours.com"
certmagic.Default.Email = "you@yours.com"
// use the staging endpoint while we're developing
certmagic.CA = certmagic.LetsEncryptStagingCA
certmagic.Default.CA = certmagic.LetsEncryptStagingCA
```
For fully-functional program examples, check out [this Twitter thread](https://twitter.com/mholt6/status/1073103805112147968) (or read it [unrolled into a single post](https://threadreaderapp.com/thread/1073103805112147968.html)).
For fully-functional program examples, check out [this Twitter thread](https://twitter.com/mholt6/status/1073103805112147968) (or read it [unrolled into a single post](https://threadreaderapp.com/thread/1073103805112147968.html)). (Note that the package API has changed slightly since these posts.)
#### Serving HTTP handlers with HTTPS
@ -213,10 +217,24 @@ if err != nil {
#### Advanced use
For more control, you'll make and use a `Config` like so:
For more control (particularly, if you need a different way of managing each certificate), you'll make and use a `Cache` and a `Config` like so:
```go
magic := certmagic.New(certmagic.Config{
cache := certmagic.NewCache(certmagic.CacheOptions{
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) {
// do whatever you need to do to get the right
// configuration for this certificate; keep in
// mind that this config value is used as a
// template, and will be completed with any
// defaults that are set in the Default config
return certmagic.Config{
// ...
}), nil
},
...
})
magic := certmagic.New(cache, certmagic.Config{
CA: certmagic.LetsEncryptStagingCA,
Email: "you@yours.com",
Agreed: true,
@ -233,6 +251,8 @@ if err != nil {
// you can get a TLS config to use in a TLS listener!
tlsConfig := magic.TLSConfig()
//// OR ////
// if you already have a TLS config you don't want to replace,
// we can simply set its GetCertificate field and append the
// TLS-ALPN challenge protocol to the NextProtos
@ -245,23 +265,12 @@ myTLSConfig.NextProtos = append(myTLSConfig.NextProtos, tlsalpn01.ACMETLS1Protoc
httpMux = magic.HTTPChallengeHandler(httpMux)
```
Great! This example grants you much more flexibility for advanced programs. However, _the vast majority of you will only use the high-level functions described earlier_, especially since you can still customize them by setting the package-level defaults.
If you want to use the default configuration but you still need a `certmagic.Config`, you can call `certmagic.Manage()` directly to get one:
```go
magic, err := certmagic.Manage([]string{"example.com"})
if err != nil {
return err
}
```
And then it's the same as above, as if you had made the `Config` yourself.
Great! This example grants you much more flexibility for advanced programs. However, _the vast majority of you will only use the high-level functions described earlier_, especially since you can still customize them by setting the package-level `Default` config.
### Wildcard certificates
At time of writing (December 2018), Let's Encrypt only issues wildcard certificates with the DNS challenge.
At time of writing (December 2018), Let's Encrypt only issues wildcard certificates with the DNS challenge. You can easily enable the DNS challenge with CertMagic for numerous providers (see the relevant section in the docs).
### Behind a load balancer (or in a cluster)
@ -348,22 +357,22 @@ ln, err := tls.Listen("tcp", ":443", myTLSConfig)
The DNS challenge is perhaps the most useful challenge because it allows you to obtain certificates without your server needing to be publicly accessible on the Internet, and it's the only challenge by which Let's Encrypt will issue wildcard certificates.
This challenge works by setting a special record in the domain's zone. To do this automatically, your DNS provider needs to offer an API by which changes can be made to domain names, and the changes need to take effect immediately for best results. CertMagic supports [all of lego's DNS provider implementations](https://github.com/xenolf/lego/tree/master/providers/dns)! All of them clean up the temporary record after the challenge completes.
This challenge works by setting a special record in the domain's zone. To do this automatically, your DNS provider needs to offer an API by which changes can be made to domain names, and the changes need to take effect immediately for best results. CertMagic supports [all of lego's DNS provider implementations](https://github.com/go-acme/lego/tree/master/providers/dns)! All of them clean up the temporary record after the challenge completes.
To enable it, just set the `DNSProvider` field on a `certmagic.Config` struct, or set the default `certmagic.DNSProvider` variable. For example, if my domains' DNS was served by DNSimple and I set my DNSimple API credentials in environment variables:
```go
import "github.com/xenolf/lego/providers/dns/dnsimple"
import "github.com/go-acme/lego/providers/dns/dnsimple"
provider, err := dnsimple.NewDNSProvider()
if err != nil {
return err
}
certmagic.DNSProvider = provider
certmagic.Default.DNSProvider = provider
```
Now the DNS challenge will be used by default, and I can obtain certificates for wildcard domains. See the [godoc documentation for the provider you're using](https://godoc.org/github.com/xenolf/lego/providers/dns#pkg-subdirectories) to learn how to configure it. Most can be configured by env variables or by passing in a config struct. If you pass a config struct instead of using env variables, you will probably need to set some other defaults (that's just how lego works, currently):
Now the DNS challenge will be used by default, and I can obtain certificates for wildcard domains. See the [godoc documentation for the provider you're using](https://godoc.org/github.com/go-acme/lego/providers/dns#pkg-subdirectories) to learn how to configure it. Most can be configured by env variables or by passing in a config struct. If you pass a config struct instead of using env variables, you will probably need to set some other defaults (that's just how lego works, currently):
```go
PropagationTimeout: dns01.DefaultPollingInterval,
@ -392,7 +401,7 @@ CertMagic provides several ways to enforce decision policies for On-Demand TLS,
The simplest way to enable On-Demand issuance is to set the OnDemand field of a Config (or the default package-level value):
```go
certmagic.OnDemand = &certmagic.OnDemandConfig{MaxObtain: 5}
certmagic.Default.OnDemand = &certmagic.OnDemandConfig{MaxObtain: 5}
```
This allows only 5 certificates to be requested and is the simplest way to enable On-Demand TLS, but is the least recommended. It prevents abuse, but only in the least helpful way.
@ -412,7 +421,7 @@ The notion of a "cluster" or "fleet" of instances that may be serving the same s
The easiest way to change the storage being used is to set `certmagic.DefaultStorage` to a value that satisfies the [Storage interface](https://godoc.org/github.com/mholt/certmagic#Storage). Keep in mind that a valid `Storage` must be able to implement some operations atomically in order to provide locking and synchronization.
If you write a Storage implementation, let us know and we'll add it to the project so people can find it!
If you write a Storage implementation, please add it to the [project wiki](https://github.com/mholt/certmagic/wiki/Storage-Implementations) so people can find it!
## Cache
@ -460,7 +469,7 @@ We welcome your contributions! Please see our **[contributing guidelines](https:
## Project History
CertMagic is the core of Caddy's advanced TLS automation code, extracted into a library. The underlying ACME client implementation is [lego](https://github.com/xenolf/lego), which was originally developed for use in Caddy even before Let's Encrypt entered public beta in 2015.
CertMagic is the core of Caddy's advanced TLS automation code, extracted into a library. The underlying ACME client implementation is [lego](https://github.com/go-acme/lego), which was originally developed for use in Caddy even before Let's Encrypt entered public beta in 2015.
In the years since then, Caddy's TLS automation techniques have been widely adopted, tried and tested in production, and served millions of sites and secured trillions of connections.

View file

@ -21,8 +21,6 @@ before_test:
- go env
test_script:
- gometalinter --install
- gometalinter --disable-all -E vet -E gofmt -E misspell -E ineffassign -E goimports -E deadcode --tests ./...
- go test -race ./...
after_test:

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
)

View file

@ -46,29 +46,21 @@ type Certificate struct {
// The hex-encoded hash of this cert's chain's bytes.
Hash string
// configs is the list of configs that use or refer to
// The first one is assumed to be the config that is
// "in charge" of this certificate (i.e. determines
// whether it is managed, how it is managed, etc).
// This field will be populated by cacheCertificate.
// Only meddle with it if you know what you're doing!
configs []*Config
// whether this certificate is under our management
// Whether this certificate is under our management
managed bool
}
// NeedsRenewal returns true if the certificate is
// expiring soon or has expired.
func (c Certificate) NeedsRenewal() bool {
if c.NotAfter.IsZero() {
// expiring soon (according to cfg) or has expired.
func (cert Certificate) NeedsRenewal(cfg *Config) bool {
if cert.NotAfter.IsZero() {
return false
}
renewDurationBefore := DefaultRenewDurationBefore
if len(c.configs) > 0 && c.configs[0].RenewDurationBefore > 0 {
renewDurationBefore = c.configs[0].RenewDurationBefore
if cfg.RenewDurationBefore > 0 {
renewDurationBefore = cfg.RenewDurationBefore
}
return time.Until(c.NotAfter) < renewDurationBefore
return time.Until(cert.NotAfter) < renewDurationBefore
}
// CacheManagedCertificate loads the certificate for domain into the
@ -79,19 +71,30 @@ func (c Certificate) NeedsRenewal() bool {
//
// This method is safe for concurrent use.
func (cfg *Config) CacheManagedCertificate(domain string) (Certificate, error) {
cert, err := cfg.loadManagedCertificate(domain)
if err != nil {
return cert, err
}
cfg.certCache.cacheCertificate(cert)
if cfg.OnEvent != nil {
cfg.OnEvent("cached_managed_cert", cert.Names)
}
return cert, nil
}
// loadManagedCertificate loads the managed certificate for domain,
// but it does not add it to the cache. It just loads from storage.
func (cfg *Config) loadManagedCertificate(domain string) (Certificate, error) {
certRes, err := cfg.loadCertResource(domain)
if err != nil {
return Certificate{}, err
}
cert, err := cfg.makeCertificateWithOCSP(certRes.Certificate, certRes.PrivateKey)
cert, err := makeCertificateWithOCSP(cfg.Storage, certRes.Certificate, certRes.PrivateKey)
if err != nil {
return cert, err
}
cert.managed = true
if cfg.OnEvent != nil {
cfg.OnEvent("cached_managed_cert", cert.Names)
}
return cfg.cacheCertificate(cert), nil
return cert, nil
}
// CacheUnmanagedCertificatePEMFile loads a certificate for host using certFile
@ -100,11 +103,11 @@ func (cfg *Config) CacheManagedCertificate(domain string) (Certificate, error) {
//
// This method is safe for concurrent use.
func (cfg *Config) CacheUnmanagedCertificatePEMFile(certFile, keyFile string) error {
cert, err := cfg.makeCertificateFromDiskWithOCSP(certFile, keyFile)
cert, err := makeCertificateFromDiskWithOCSP(cfg.Storage, certFile, keyFile)
if err != nil {
return err
}
cfg.cacheCertificate(cert)
cfg.certCache.cacheCertificate(cert)
if cfg.OnEvent != nil {
cfg.OnEvent("cached_unmanaged_cert", cert.Names)
}
@ -121,14 +124,14 @@ func (cfg *Config) CacheUnmanagedTLSCertificate(tlsCert tls.Certificate) error {
if err != nil {
return err
}
err = cfg.certCache.stapleOCSP(&cert, nil)
err = stapleOCSP(cfg.Storage, &cert, nil)
if err != nil {
log.Printf("[WARNING] Stapling OCSP: %v", err)
}
if cfg.OnEvent != nil {
cfg.OnEvent("cached_unmanaged_cert", cert.Names)
}
cfg.cacheCertificate(cert)
cfg.certCache.cacheCertificate(cert)
return nil
}
@ -137,11 +140,11 @@ func (cfg *Config) CacheUnmanagedTLSCertificate(tlsCert tls.Certificate) error {
//
// This method is safe for concurrent use.
func (cfg *Config) CacheUnmanagedCertificatePEMBytes(certBytes, keyBytes []byte) error {
cert, err := cfg.makeCertificateWithOCSP(certBytes, keyBytes)
cert, err := makeCertificateWithOCSP(cfg.Storage, certBytes, keyBytes)
if err != nil {
return err
}
cfg.cacheCertificate(cert)
cfg.certCache.cacheCertificate(cert)
if cfg.OnEvent != nil {
cfg.OnEvent("cached_unmanaged_cert", cert.Names)
}
@ -152,7 +155,7 @@ func (cfg *Config) CacheUnmanagedCertificatePEMBytes(certBytes, keyBytes []byte)
// certificate and key files. It fills out all the fields in
// the certificate except for the Managed and OnDemand flags.
// (It is up to the caller to set those.) It staples OCSP.
func (cfg *Config) makeCertificateFromDiskWithOCSP(certFile, keyFile string) (Certificate, error) {
func makeCertificateFromDiskWithOCSP(storage Storage, certFile, keyFile string) (Certificate, error) {
certPEMBlock, err := ioutil.ReadFile(certFile)
if err != nil {
return Certificate{}, err
@ -161,7 +164,21 @@ func (cfg *Config) makeCertificateFromDiskWithOCSP(certFile, keyFile string) (Ce
if err != nil {
return Certificate{}, err
}
return cfg.makeCertificateWithOCSP(certPEMBlock, keyPEMBlock)
return makeCertificateWithOCSP(storage, certPEMBlock, keyPEMBlock)
}
// makeCertificateWithOCSP is the same as makeCertificate except that it also
// staples OCSP to the certificate.
func makeCertificateWithOCSP(storage Storage, certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
cert, err := makeCertificate(certPEMBlock, keyPEMBlock)
if err != nil {
return cert, err
}
err = stapleOCSP(storage, &cert, certPEMBlock)
if err != nil {
log.Printf("[WARNING] Stapling OCSP: %v", err)
}
return cert, nil
}
// makeCertificate turns a certificate PEM bundle and a key PEM block into
@ -169,7 +186,7 @@ func (cfg *Config) makeCertificateFromDiskWithOCSP(certFile, keyFile string) (Ce
// its struct fields for convenience (except for the OnDemand and Managed
// flags; it is up to the caller to set those properties!). This function
// does NOT staple OCSP.
func (*Config) makeCertificate(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
func makeCertificate(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
var cert Certificate
// Convert to a tls.Certificate
@ -187,20 +204,6 @@ func (*Config) makeCertificate(certPEMBlock, keyPEMBlock []byte) (Certificate, e
return cert, nil
}
// makeCertificateWithOCSP is the same as makeCertificate except that it also
// staples OCSP to the certificate.
func (cfg *Config) makeCertificateWithOCSP(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
cert, err := cfg.makeCertificate(certPEMBlock, keyPEMBlock)
if err != nil {
return cert, err
}
err = cfg.certCache.stapleOCSP(&cert, certPEMBlock)
if err != nil {
log.Printf("[WARNING] Stapling OCSP: %v", err)
}
return cert, nil
}
// fillCertFromLeaf populates metadata fields on cert from tlsCert.
func fillCertFromLeaf(cert *Certificate, tlsCert tls.Certificate) error {
if len(tlsCert.Certificate) == 0 {
@ -253,11 +256,7 @@ func fillCertFromLeaf(cert *Certificate, tlsCert tls.Certificate) error {
// means that another instance renewed the certificate in the
// meantime, and it would be a good idea to simply load the cert
// into our cache rather than repeating the renewal process again.
func managedCertInStorageExpiresSoon(cert Certificate) (bool, error) {
if len(cert.configs) == 0 {
return false, fmt.Errorf("no configs for certificate")
}
cfg := cert.configs[0]
func (cfg *Config) managedCertInStorageExpiresSoon(cert Certificate) (bool, error) {
certRes, err := cfg.loadCertResource(cert.Names[0])
if err != nil {
return false, err
@ -274,54 +273,17 @@ func managedCertInStorageExpiresSoon(cert Certificate) (bool, error) {
return timeLeft < cfg.RenewDurationBefore, nil
}
// cacheCertificate adds cert to the in-memory cache. If a certificate
// with the same hash is already cached, it is NOT overwritten; instead,
// cfg is added to the existing certificate's list of configs if not
// already in the list. Then all the names on cert are used to add
// entries to cfg.certificates (the config's name lookup map).
// Then the certificate is stored/updated in the cache. It returns
// a copy of the certificate that ends up being stored in the cache.
//
// It is VERY important, even for some test cases, that the Hash field
// of the cert be set properly.
//
// This function is safe for concurrent use.
func (cfg *Config) cacheCertificate(cert Certificate) Certificate {
cfg.certCache.mu.Lock()
defer cfg.certCache.mu.Unlock()
// if this certificate already exists in the cache,
// use it instead of overwriting it -- very important!
if existingCert, ok := cfg.certCache.cache[cert.Hash]; ok {
cert = existingCert
// 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 (cfg *Config) reloadManagedCertificate(oldCert Certificate) error {
newCert, err := cfg.loadManagedCertificate(oldCert.Names[0])
if err != nil {
return fmt.Errorf("loading managed certificate for %v from storage: %v", oldCert.Names, err)
}
// attach this config to the certificate so we know which
// configs are referencing/using the certificate, but don't
// duplicate entries
var found bool
for _, c := range cert.configs {
if c == cfg {
found = true
break
}
}
if !found {
cert.configs = append(cert.configs, cfg)
}
// key the certificate by all its names for this config only,
// this is how we find the certificate during handshakes
// (yes, if certs overlap in the names they serve, one will
// overwrite another here, but that's just how it goes)
for _, name := range cert.Names {
cfg.certificates[NormalizedName(name)] = cert.Hash
}
// store the certificate
cfg.certCache.cache[cert.Hash] = cert
return cert
cfg.certCache.replaceCertificate(oldCert, newCert)
return nil
}
// HostQualifies returns true if the hostname alone

View file

@ -23,8 +23,9 @@
// a ready-to-use tls.Config -- whatever layer you need TLS for, CertMagic
// makes it easy. See the HTTPS, Listen, and TLS functions for that.
//
// If you need more control, create a Config using New() and then call
// Manage() on the config; but you'll have to be sure to solve the HTTP
// If you need more control, create a Cache using NewCache() and then make
// a Config using New(). You can then call Manage() on the config. But if
// you use this lower-level API, you'll have to be sure to solve the HTTP
// and TLS-ALPN challenges yourself (unless you disabled them or use the
// DNS challenge) by using the provided Config.GetCertificate function
// in your tls.Config and/or Config.HTTPChallangeHandler in your HTTP
@ -45,22 +46,22 @@ import (
"sync/atomic"
"time"
"github.com/xenolf/lego/certcrypto"
"github.com/xenolf/lego/challenge"
"github.com/go-acme/lego/certcrypto"
)
// HTTPS serves mux for all domainNames using the HTTP
// and HTTPS ports, redirecting all HTTP requests to HTTPS.
// It uses the Default config.
//
// This high-level convenience function is opinionated and
// applies sane defaults for production use, including
// timeouts for HTTP requests and responses. To allow very
// long-lived requests or connections, you should make your
// own http.Server values and use this package's Listen(),
// TLS(), or Config.TLSConfig() functions to customize to
// your needs. For example, servers which need to support
// large uploads or downloads with slow clients may need to
// use longer timeouts, thus this function is not suitable.
// long-lived connections, you should make your own
// http.Server values and use this package's Listen(), TLS(),
// or Config.TLSConfig() functions to customize to your needs.
// For example, servers which need to support large uploads or
// downloads with slow clients may need to use longer timeouts,
// thus this function is not suitable.
//
// Calling this function signifies your acceptance to
// the CA's Subscriber Agreement and/or Terms of Service.
@ -69,7 +70,10 @@ func HTTPS(domainNames []string, mux http.Handler) error {
mux = http.DefaultServeMux
}
cfg, err := manageWithDefaultConfig(domainNames, false)
Default.Agreed = true
cfg := NewDefault()
err := cfg.Manage(domainNames)
if err != nil {
return err
}
@ -154,31 +158,40 @@ func httpRedirectHandler(w http.ResponseWriter, r *http.Request) {
}
// TLS enables management of certificates for domainNames
// and returns a valid tls.Config.
// and returns a valid tls.Config. It uses the Default
// config.
//
// Because this is a convenience function that returns
// only a tls.Config, it does not assume HTTP is being
// served on the HTTP port, so the HTTP challenge is
// disabled (no HTTPChallengeHandler is necessary).
// disabled (no HTTPChallengeHandler is necessary). The
// package variable Default is modified so that the
// HTTP challenge is disabled.
//
// Calling this function signifies your acceptance to
// the CA's Subscriber Agreement and/or Terms of Service.
func TLS(domainNames []string) (*tls.Config, error) {
cfg, err := manageWithDefaultConfig(domainNames, true)
return cfg.TLSConfig(), err
Default.Agreed = true
Default.DisableHTTPChallenge = true
cfg := NewDefault()
return cfg.TLSConfig(), cfg.Manage(domainNames)
}
// Listen manages certificates for domainName and returns a
// TLS listener.
// TLS listener. It uses the Default config.
//
// Because this convenience function returns only a TLS-enabled
// listener and does not presume HTTP is also being served,
// the HTTP challenge will be disabled.
// the HTTP challenge will be disabled. The package variable
// Default is modified so that the HTTP challenge is disabled.
//
// Calling this function signifies your acceptance to
// the CA's Subscriber Agreement and/or Terms of Service.
func Listen(domainNames []string) (net.Listener, error) {
cfg, err := manageWithDefaultConfig(domainNames, true)
Default.Agreed = true
Default.DisableHTTPChallenge = true
cfg := NewDefault()
err := cfg.Manage(domainNames)
if err != nil {
return nil, err
}
@ -186,36 +199,36 @@ func Listen(domainNames []string) (net.Listener, error) {
}
// Manage obtains certificates for domainNames and keeps them
// renewed using the returned Config.
// renewed using the Default config.
//
// This is a slightly lower-level function; you will need to
// wire up support for the ACME challenges yourself. You can
// obtain a Config to help you do that by calling NewDefault().
//
// You will need to ensure that you use a TLS config that gets
// certificates from this Config and that the HTTP and TLS-ALPN
// challenges can be solved. The easiest way to do this is to
// use cfg.TLSConfig() as your TLS config and to wrap your
// HTTP handler with cfg.HTTPChallengeHandler(). If you don't
// have an HTTP server, you will need to disable the HTTP
// challenge.
// use NewDefault().TLSConfig() as your TLS config and to wrap
// your HTTP handler with NewDefault().HTTPChallengeHandler().
// If you don't have an HTTP server, you will need to disable
// the HTTP challenge.
//
// If you already have a TLS config you want to use, you can
// simply set its GetCertificate field to cfg.GetCertificate.
// simply set its GetCertificate field to
// NewDefault().GetCertificate.
//
// Calling this function signifies your acceptance to
// the CA's Subscriber Agreement and/or Terms of Service.
func Manage(domainNames []string) (cfg *Config, err error) {
return manageWithDefaultConfig(domainNames, false)
}
// manageWithDefaultConfig returns a TLS configuration that
// is fully managed for the given names, optionally
// with the HTTP challenge disabled.
func manageWithDefaultConfig(domainNames []string, disableHTTPChallenge bool) (*Config, error) {
cfg := NewDefault()
cfg.DisableHTTPChallenge = disableHTTPChallenge
return cfg, cfg.Manage(domainNames)
func Manage(domainNames []string) error {
Default.Agreed = true
return NewDefault().Manage(domainNames)
}
// OnDemandConfig contains some state relevant for providing
// on-demand TLS.
// on-demand TLS. Important note: If you are using the
// MaxObtain property to limit the maximum number of certs
// to be issued, the count of how many certs were issued
// will be reset if this struct gets garbage-collected.
type OnDemandConfig struct {
// If set, this function will be the absolute
// authority on whether the hostname (according
@ -246,6 +259,8 @@ type OnDemandConfig struct {
// The number of certificates that have been issued on-demand
// by this config. It is only safe to modify this count atomically.
// If it reaches MaxObtain, on-demand issuances must fail.
// Note that this will necessarily be reset to 0 if the
// struct leaves scope and/or gets garbage-collected.
obtainedCount int32
}
@ -405,98 +420,28 @@ func isInternal(addr string) bool {
return false
}
// Package defaults
var (
// The endpoint of the directory for the ACME
// CA we are to use
CA = LetsEncryptProductionCA
// The email address to use when creating or
// selecting an existing ACME server account
Email string
// The synchronization implementation - all
// instances of certmagic in a cluster must
// use the same value here, otherwise some
// cert operations will not be properly
// coordinated
Sync Locker
// Set to true if agreed to the CA's
// subscriber agreement
Agreed bool
// Disable all HTTP challenges
DisableHTTPChallenge bool
// Disable all TLS-ALPN challenges
DisableTLSALPNChallenge bool
// How long before expiration to renew certificates
RenewDurationBefore = DefaultRenewDurationBefore
// How long before expiration to require a renewed
// certificate when in interactive mode, like when
// the program is first starting up (see
// mholt/caddy#1680). A wider window between
// RenewDurationBefore and this value will suppress
// errors under duress (bad) but hopefully this duration
// will give it enough time for the blockage to be
// relieved.
RenewDurationBeforeAtStartup = DefaultRenewDurationBeforeAtStartup
// An optional event callback clients can set
// to subscribe to certain things happening
// internally by this config; invocations are
// synchronous, so make them return quickly!
OnEvent func(event string, data interface{})
// The host (ONLY the host, not port) to listen
// on if necessary to start a listener to solve
// an ACME challenge
ListenHost string
// The alternate port to use for the ACME HTTP
// challenge; if non-empty, this port will be
// used instead of HTTPChallengePort to spin up
// a listener for the HTTP challenge
AltHTTPPort int
// The alternate port to use for the ACME
// TLS-ALPN challenge; the system must forward
// TLSALPNChallengePort to this port for
// challenge to succeed
AltTLSALPNPort int
// The DNS provider to use when solving the
// ACME DNS challenge
DNSProvider challenge.Provider
// The type of key to use when generating
// certificates
KeyType = certcrypto.RSA2048
// The maximum amount of time to allow for
// obtaining a certificate. If empty, the
// default from the underlying lego lib is
// used. If set, it must not be too low so
// as to cancel orders too early, running
// the risk of rate limiting.
CertObtainTimeout time.Duration
// Set the default server name for clients
// not indicating a server name using SNI.
// In most cases this will be the primary
// domain that is being served.
DefaultServerName string
// The state needed to operate on-demand TLS
OnDemand *OnDemandConfig
// Add the must staple TLS extension to the
// CSR generated by lego/acme
MustStaple bool
)
// Default contains the package defaults for the
// various Config fields. This is used as a template
// when creating your own Configs with New(), and it
// is also used as the Config by all the high-level
// functions in this package.
//
// The fields of this value will be used for Config
// fields which are unset. Feel free to modify these
// defaults, but do not use this Config by itself: it
// is only a template. Valid configurations can be
// obtained by calling New() (if you have your own
// certificate cache) or NewDefault() (if you only
// need a single config and want to use the default
// cache). This is the only Config which can access
// the default certificate cache.
var Default = Config{
CA: LetsEncryptProductionCA,
RenewDurationBefore: DefaultRenewDurationBefore,
RenewDurationBeforeAtStartup: DefaultRenewDurationBeforeAtStartup,
KeyType: certcrypto.EC256,
Storage: defaultFileStorage,
}
const (
// HTTPChallengePort is the officially-designated port for
@ -520,16 +465,16 @@ const (
var (
// HTTPPort is the port on which to serve HTTP
// and, by extension, the HTTP challenge (unless
// AltHTTPPort is set).
// Default.AltHTTPPort is set).
HTTPPort = 80
// HTTPSPort is the port on which to serve HTTPS
// and, by extension, the TLS-ALPN challenge
// (unless AltTLSALPNPort is set).
// (unless Default.AltTLSALPNPort is set).
HTTPSPort = 443
)
// Variables for conveniently serving HTTPS
// Variables for conveniently serving HTTPS.
var (
httpLn, httpsLn net.Listener
lnMu sync.Mutex

View file

@ -23,12 +23,12 @@ import (
"sync"
"time"
"github.com/xenolf/lego/certificate"
"github.com/xenolf/lego/challenge"
"github.com/xenolf/lego/challenge/http01"
"github.com/xenolf/lego/challenge/tlsalpn01"
"github.com/xenolf/lego/lego"
"github.com/xenolf/lego/registration"
"github.com/go-acme/lego/certificate"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/challenge/http01"
"github.com/go-acme/lego/challenge/tlsalpn01"
"github.com/go-acme/lego/lego"
"github.com/go-acme/lego/registration"
)
// acmeMu ensures that only one ACME challenge occurs at a time.
@ -52,6 +52,13 @@ func listenerAddressInUse(addr string) bool {
return err == nil
}
func (cfg *Config) newManager(interactive bool) (Manager, error) {
if cfg.NewManager != nil {
return cfg.NewManager(interactive)
}
return cfg.newACMEClient(interactive)
}
func (cfg *Config) newACMEClient(interactive bool) (*acmeClient, error) {
// look up or create the user account
leUser, err := cfg.getUser(cfg.Email)
@ -62,15 +69,15 @@ func (cfg *Config) newACMEClient(interactive bool) (*acmeClient, error) {
// ensure key type and timeout are set
keyType := cfg.KeyType
if keyType == "" {
keyType = KeyType
keyType = Default.KeyType
}
certObtainTimeout := cfg.CertObtainTimeout
if certObtainTimeout == 0 {
certObtainTimeout = CertObtainTimeout
certObtainTimeout = Default.CertObtainTimeout
}
// ensure CA URL (directory endpoint) is set
caURL := CA
caURL := Default.CA
if cfg.CA != "" {
caURL = cfg.CA
}
@ -91,6 +98,7 @@ func (cfg *Config) newACMEClient(interactive bool) (*acmeClient, error) {
clientKey := caURL + leUser.Email + string(keyType)
// if an underlying client with this configuration already exists, reuse it
// TODO: Could this be a global cache instead, perhaps?
cfg.acmeClientsMu.Lock()
client, ok := cfg.acmeClients[clientKey]
if !ok {
@ -232,12 +240,12 @@ func (cfg *Config) lockKey(op, domainName string) string {
func (c *acmeClient) Obtain(name string) error {
// ensure idempotency of the obtain operation for this name
lockKey := c.config.lockKey("cert_acme", name)
err := c.config.certCache.storage.Lock(lockKey)
err := c.config.Storage.Lock(lockKey)
if err != nil {
return err
}
defer func() {
if err := c.config.certCache.storage.Unlock(lockKey); err != nil {
if err := c.config.Storage.Unlock(lockKey); err != nil {
log.Printf("[ERROR][%s] Obtain: Unable to unlock '%s': %v", name, lockKey, err)
}
}()
@ -292,12 +300,12 @@ func (c *acmeClient) Obtain(name string) error {
func (c *acmeClient) Renew(name string) error {
// ensure idempotency of the renew operation for this name
lockKey := c.config.lockKey("cert_acme", name)
err := c.config.certCache.storage.Lock(lockKey)
err := c.config.Storage.Lock(lockKey)
if err != nil {
return err
}
defer func() {
if err := c.config.certCache.storage.Unlock(lockKey); err != nil {
if err := c.config.Storage.Unlock(lockKey); err != nil {
log.Printf("[ERROR][%s] Renew: Unable to unlock '%s': %v", name, lockKey, err)
}
}()
@ -351,7 +359,7 @@ func (c *acmeClient) Renew(name string) error {
// Revoke revokes the certificate for name and deletes
// it from storage.
func (c *acmeClient) Revoke(name string) error {
if !c.config.certCache.storage.Exists(StorageKeys.SitePrivateKey(c.config.CA, name)) {
if !c.config.Storage.Exists(StorageKeys.SitePrivateKey(c.config.CA, name)) {
return fmt.Errorf("private key not found for %s", name)
}
@ -369,15 +377,15 @@ func (c *acmeClient) Revoke(name string) error {
c.config.OnEvent("acme_cert_revoked", name)
}
err = c.config.certCache.storage.Delete(StorageKeys.SiteCert(c.config.CA, name))
err = c.config.Storage.Delete(StorageKeys.SiteCert(c.config.CA, name))
if err != nil {
return fmt.Errorf("certificate revoked, but unable to delete certificate file: %v", err)
}
err = c.config.certCache.storage.Delete(StorageKeys.SitePrivateKey(c.config.CA, name))
err = c.config.Storage.Delete(StorageKeys.SitePrivateKey(c.config.CA, name))
if err != nil {
return fmt.Errorf("certificate revoked, but unable to delete private key: %v", err)
}
err = c.config.certCache.storage.Delete(StorageKeys.SiteMeta(c.config.CA, name))
err = c.config.Storage.Delete(StorageKeys.SiteMeta(c.config.CA, name))
if err != nil {
return fmt.Errorf("certificate revoked, but unable to delete certificate metadata: %v", err)
}
@ -398,3 +406,6 @@ var (
UserAgent string
HTTPTimeout = 30 * time.Second
)
// Interface guard
var _ Manager = (*acmeClient)(nil)

View file

@ -20,11 +20,11 @@ import (
"sync"
"time"
"github.com/xenolf/lego/certcrypto"
"github.com/xenolf/lego/certificate"
"github.com/xenolf/lego/challenge"
"github.com/xenolf/lego/challenge/tlsalpn01"
"github.com/xenolf/lego/lego"
"github.com/go-acme/lego/certcrypto"
"github.com/go-acme/lego/certificate"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/challenge/tlsalpn01"
"github.com/go-acme/lego/lego"
)
// Config configures a certificate manager instance.
@ -74,7 +74,7 @@ type Config struct {
ListenHost string
// The alternate port to use for the ACME HTTP
// challenge; if non-empty, this port will be
// challenge; if non-empty, this port will be
// used instead of HTTPChallengePort to spin up
// a listener for the HTTP challenge
AltHTTPPort int
@ -113,117 +113,167 @@ type Config struct {
// CSR generated by lego/acme
MustStaple bool
// Map of hostname to certificate hash; used
// to complete handshakes and serve the right
// certificate given SNI
certificates map[string]string
// The storage to access when storing or
// loading TLS assets
Storage Storage
// Pointer to the certificate store to use
// NewManager returns a new Manager. If nil,
// an ACME client will be created and used.
NewManager func(interactive bool) (Manager, error)
// Pointer to the in-memory certificate cache
certCache *Cache
// Map of client config key to ACME clients
// so they can be reused
// TODO: It might be better if these were globally cached, rather than per-config, which are ephemeral... but maybe evict them after a certain time, like 1 day or something
acmeClients map[string]*lego.Client
acmeClientsMu *sync.Mutex
}
// NewDefault returns a new, valid, default config.
// NewDefault makes a valid config based on the package
// Default config. Most users will call this function
// instead of New() since most use cases require only a
// single config for any and all certificates.
//
// Calling this function signifies your acceptance to
// the CA's Subscriber Agreement and/or Terms of Service.
// If your requirements are more advanced (for example,
// multiple configs depending on the certificate), then use
// New() instead. (You will need to make your own Cache
// first.) If you only need a single Config to manage your
// certs (even if that config changes, as long as it is the
// only one), customize the Default package variable before
// calling NewDefault().
//
// All calls to NewDefault() will return configs that use the
// same, default certificate cache. All configs returned
// by NewDefault() are based on the values of the fields of
// Default at the time it is called.
func NewDefault() *Config {
return New(Config{Agreed: true})
}
// New makes a valid config based on cfg and uses
// a default certificate cache. All calls to
// New() will use the same certificate cache.
func New(cfg Config) *Config {
return NewWithCache(nil, cfg)
}
// NewWithCache makes a valid new config based on cfg
// and uses the provided certificate cache. If certCache
// is nil, a new, default one will be created using
// DefaultStorage; or, if a default cache has already
// been created, it will be reused.
func NewWithCache(certCache *Cache, cfg Config) *Config {
// avoid nil pointers with sensible defaults,
// careful to initialize a default cache (which
// begins its maintenance goroutine) only if
// needed - and only once (we don't initialize
// it at package init to give importers a chance
// to set DefaultStorage if they so desire)
if certCache == nil {
defaultCacheMu.Lock()
if defaultCache == nil {
defaultCache = NewCache(DefaultStorage)
}
certCache = defaultCache
defaultCacheMu.Unlock()
defaultCacheMu.Lock()
if defaultCache == nil {
defaultCache = NewCache(CacheOptions{
// the cache will likely need to renew certificates,
// so it will need to know how to do that, which
// depends on the certificate being managed and which
// can change during the lifetime of the cache; this
// callback makes it possible to get the latest and
// correct config with which to manage the cert,
// but if the user does not provide one, we can only
// assume that we are to use the default config
GetConfigForCert: func(Certificate) (Config, error) {
return Default, nil
},
})
}
if certCache.storage == nil {
certCache.storage = DefaultStorage
certCache := defaultCache
defaultCacheMu.Unlock()
return newWithCache(certCache, Default)
}
// New makes a new, valid config based on cfg and
// uses the provided certificate cache. certCache
// MUST NOT be nil or this function will panic.
//
// Use this method when you have an advanced use case
// that requires a custom certificate cache and config
// that may differ from the Default. For example, if
// not all certificates are managed/renewed the same
// way, you need to make your own Cache value with a
// GetConfigForCert callback that returns the correct
// configuration for each certificate. However, for
// the vast majority of cases, there will be only a
// single Config, thus the default cache (which always
// uses the default Config) and default config will
// suffice, and you should use New() instead.
func New(certCache *Cache, cfg Config) *Config {
if certCache == nil {
panic("a certificate cache is required")
}
if certCache.options.GetConfigForCert == nil {
panic("cache must have GetConfigForCert set in its options")
}
return newWithCache(certCache, cfg)
}
// newWithCache ensures that cfg is a valid config by populating
// zero-value fields from the Default Config. If certCache is
// nil, this function panics.
func newWithCache(certCache *Cache, cfg Config) *Config {
if certCache == nil {
panic("cannot make a valid config without a pointer to a certificate cache")
}
// fill in default values
if cfg.CA == "" {
cfg.CA = CA
cfg.CA = Default.CA
}
if cfg.Email == "" {
cfg.Email = Email
cfg.Email = Default.Email
}
if cfg.OnDemand == nil {
cfg.OnDemand = OnDemand
cfg.OnDemand = Default.OnDemand
}
if !cfg.Agreed {
cfg.Agreed = Agreed
cfg.Agreed = Default.Agreed
}
if !cfg.DisableHTTPChallenge {
cfg.DisableHTTPChallenge = DisableHTTPChallenge
cfg.DisableHTTPChallenge = Default.DisableHTTPChallenge
}
if !cfg.DisableTLSALPNChallenge {
cfg.DisableTLSALPNChallenge = DisableTLSALPNChallenge
cfg.DisableTLSALPNChallenge = Default.DisableTLSALPNChallenge
}
if cfg.RenewDurationBefore == 0 {
cfg.RenewDurationBefore = RenewDurationBefore
cfg.RenewDurationBefore = Default.RenewDurationBefore
}
if cfg.RenewDurationBeforeAtStartup == 0 {
cfg.RenewDurationBeforeAtStartup = RenewDurationBeforeAtStartup
cfg.RenewDurationBeforeAtStartup = Default.RenewDurationBeforeAtStartup
}
if cfg.OnEvent == nil {
cfg.OnEvent = OnEvent
cfg.OnEvent = Default.OnEvent
}
if cfg.ListenHost == "" {
cfg.ListenHost = ListenHost
cfg.ListenHost = Default.ListenHost
}
if cfg.AltHTTPPort == 0 {
cfg.AltHTTPPort = AltHTTPPort
cfg.AltHTTPPort = Default.AltHTTPPort
}
if cfg.AltTLSALPNPort == 0 {
cfg.AltTLSALPNPort = AltTLSALPNPort
cfg.AltTLSALPNPort = Default.AltTLSALPNPort
}
if cfg.DNSProvider == nil {
cfg.DNSProvider = DNSProvider
cfg.DNSProvider = Default.DNSProvider
}
if cfg.KeyType == "" {
cfg.KeyType = KeyType
cfg.KeyType = Default.KeyType
}
if cfg.CertObtainTimeout == 0 {
cfg.CertObtainTimeout = CertObtainTimeout
cfg.CertObtainTimeout = Default.CertObtainTimeout
}
if cfg.DefaultServerName == "" {
cfg.DefaultServerName = DefaultServerName
cfg.DefaultServerName = Default.DefaultServerName
}
if cfg.OnDemand == nil {
cfg.OnDemand = OnDemand
cfg.OnDemand = Default.OnDemand
}
if !cfg.MustStaple {
cfg.MustStaple = MustStaple
cfg.MustStaple = Default.MustStaple
}
if cfg.Storage == nil {
cfg.Storage = Default.Storage
}
if cfg.NewManager == nil {
cfg.NewManager = Default.NewManager
}
// absolutely don't allow a nil storage,
// because that would make almost anything
// a config can do pointless
if cfg.Storage == nil {
cfg.Storage = defaultFileStorage
}
// ensure the unexported fields are valid
cfg.certificates = make(map[string]string)
cfg.certCache = certCache
cfg.acmeClients = make(map[string]*lego.Client)
cfg.acmeClientsMu = new(sync.Mutex)
@ -272,7 +322,7 @@ func (cfg *Config) Manage(domainNames []string) error {
}
// for existing certificates, make sure it is renewed
if cert.NeedsRenewal() {
if cert.NeedsRenewal(cfg) {
err := cfg.RenewCert(domainName, false)
if err != nil {
return fmt.Errorf("%s: renewing certificate: %v", domainName, err)
@ -293,6 +343,9 @@ func (cfg *Config) Manage(domainNames []string) error {
// it does not load them into memory. If interactive is true,
// the user may be shown a prompt.
func (cfg *Config) ObtainCert(name string, interactive bool) error {
if cfg.storageHasCertResources(name) {
return nil
}
skip, err := cfg.preObtainOrRenewChecks(name, interactive)
if err != nil {
return err
@ -300,17 +353,11 @@ func (cfg *Config) ObtainCert(name string, interactive bool) error {
if skip {
return nil
}
if cfg.storageHasCertResources(name) {
return nil
}
client, err := cfg.newACMEClient(interactive)
manager, err := cfg.newManager(interactive)
if err != nil {
return err
}
return client.Obtain(name)
return manager.Obtain(name)
}
// RenewCert renews the certificate for name using cfg. It stows the
@ -323,20 +370,20 @@ func (cfg *Config) RenewCert(name string, interactive bool) error {
if skip {
return nil
}
client, err := cfg.newACMEClient(interactive)
manager, err := cfg.newManager(interactive)
if err != nil {
return err
}
return client.Renew(name)
return manager.Renew(name)
}
// RevokeCert revokes the certificate for domain via ACME protocol.
func (cfg *Config) RevokeCert(domain string, interactive bool) error {
client, err := cfg.newACMEClient(interactive)
manager, err := cfg.newManager(interactive)
if err != nil {
return err
}
return client.Revoke(domain)
return manager.Revoke(domain)
}
// TLSConfig is an opinionated method that returns a
@ -397,18 +444,26 @@ func (cfg *Config) storageHasCertResources(domain string) bool {
certKey := StorageKeys.SiteCert(cfg.CA, domain)
keyKey := StorageKeys.SitePrivateKey(cfg.CA, domain)
metaKey := StorageKeys.SiteMeta(cfg.CA, domain)
return cfg.certCache.storage.Exists(certKey) &&
cfg.certCache.storage.Exists(keyKey) &&
cfg.certCache.storage.Exists(metaKey)
return cfg.Storage.Exists(certKey) &&
cfg.Storage.Exists(keyKey) &&
cfg.Storage.Exists(metaKey)
}
// managedCertNeedsRenewal returns true if certRes is
// expiring soon or already expired, or if the process
// of checking the expiration returned an error.
func (cfg *Config) managedCertNeedsRenewal(certRes certificate.Resource) bool {
cert, err := cfg.makeCertificate(certRes.Certificate, certRes.PrivateKey)
cert, err := makeCertificate(certRes.Certificate, certRes.PrivateKey)
if err != nil {
return true
}
return cert.NeedsRenewal()
return cert.NeedsRenewal(cfg)
}
// Manager is a type that can manage a certificate.
// They are usually very short-lived.
type Manager interface {
Obtain(name string) error
Renew(name string) error
Revoke(name string) error
}

View file

@ -26,8 +26,8 @@ import (
"fmt"
"hash/fnv"
"github.com/go-acme/lego/certificate"
"github.com/klauspost/cpuid"
"github.com/xenolf/lego/certificate"
)
// encodePrivateKey marshals a EC or RSA private key into a PEM-encoded array of bytes.
@ -119,20 +119,20 @@ func (cfg *Config) saveCertResource(cert *certificate.Resource) error {
},
}
return storeTx(cfg.certCache.storage, all)
return storeTx(cfg.Storage, all)
}
func (cfg *Config) loadCertResource(domain string) (certificate.Resource, error) {
var certRes certificate.Resource
certBytes, err := cfg.certCache.storage.Load(StorageKeys.SiteCert(cfg.CA, domain))
certBytes, err := cfg.Storage.Load(StorageKeys.SiteCert(cfg.CA, domain))
if err != nil {
return certRes, err
}
keyBytes, err := cfg.certCache.storage.Load(StorageKeys.SitePrivateKey(cfg.CA, domain))
keyBytes, err := cfg.Storage.Load(StorageKeys.SitePrivateKey(cfg.CA, domain))
if err != nil {
return certRes, err
}
metaBytes, err := cfg.certCache.storage.Load(StorageKeys.SiteMeta(cfg.CA, domain))
metaBytes, err := cfg.Storage.Load(StorageKeys.SiteMeta(cfg.CA, domain))
if err != nil {
return certRes, err
}

View file

@ -1,10 +1,11 @@
module github.com/mholt/certmagic
require (
github.com/cenkalti/backoff v2.1.1+incompatible // indirect
github.com/go-acme/lego v2.5.0+incompatible
github.com/klauspost/cpuid v1.2.0
github.com/miekg/dns v1.1.3 // indirect
github.com/stretchr/testify v1.3.0 // indirect
github.com/xenolf/lego v2.1.0+incompatible
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 // indirect
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect

View file

@ -1,5 +1,11 @@
github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY=
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-acme/lego v2.3.1-0.20190318164254-3684cc738d37+incompatible h1:D8mQOFMowsqoVMibY3U+xeNmd83bdNPEjTScRiPgVoc=
github.com/go-acme/lego v2.3.1-0.20190318164254-3684cc738d37+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M=
github.com/go-acme/lego v2.5.0+incompatible h1:5fNN9yRQfv8ymH3DSsxla+4aYeQt2IgfZqHKVnK8f0s=
github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M=
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/miekg/dns v1.1.3 h1:1g0r1IvskvgL8rR+AcHzUA+oFmGcQlaIm4IqakufeMM=
@ -9,8 +15,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/xenolf/lego v2.1.0+incompatible h1:zZErna+4KHeBsUC3mw6gthaXncPDoBuFJOHKCRl64Wg=
github.com/xenolf/lego v2.1.0+incompatible/go.mod h1:fwiGnfsIjG7OHPfOvgK7Y/Qo6+2Ox0iozjNTkZICKbY=
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b h1:Elez2XeF2p9uyVj0yEUDqQ56NFcDtcBNkYP7yv8YbUE=
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY=

View file

@ -25,7 +25,7 @@ import (
"sync/atomic"
"time"
"github.com/xenolf/lego/challenge/tlsalpn01"
"github.com/go-acme/lego/challenge/tlsalpn01"
)
// GetCertificate gets a certificate to satisfy clientHello. In getting
@ -94,10 +94,6 @@ func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certif
func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate, matched, defaulted bool) {
name := NormalizedName(hello.ServerName)
cfg.certCache.mu.RLock()
defer cfg.certCache.mu.RUnlock()
var certKey string
var ok bool
if name == "" {
@ -108,8 +104,7 @@ func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate,
if err == nil {
addr = ip
}
if certKey, ok = cfg.certificates[addr]; ok {
cert = cfg.certCache.cache[certKey]
if cert, ok = cfg.certCache.getFirstMatchingCert(addr); ok {
matched = true
return
}
@ -118,16 +113,14 @@ func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate,
// fall back to a "default" certificate, if specified
if cfg.DefaultServerName != "" {
normDefault := NormalizedName(cfg.DefaultServerName)
if certKey, ok := cfg.certificates[normDefault]; ok {
cert = cfg.certCache.cache[certKey]
if cert, ok = cfg.certCache.getFirstMatchingCert(normDefault); ok {
defaulted = true
return
}
}
} else {
// if SNI is specified, try an exact match first
if certKey, ok = cfg.certificates[name]; ok {
cert = cfg.certCache.cache[certKey]
if cert, ok = cfg.certCache.getFirstMatchingCert(name); ok {
matched = true
return
}
@ -138,8 +131,7 @@ func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate,
for i := range labels {
labels[i] = "*"
candidate := strings.Join(labels, ".")
if certKey, ok = cfg.certificates[candidate]; ok {
cert = cfg.certCache.cache[certKey]
if cert, ok = cfg.certCache.getFirstMatchingCert(candidate); ok {
matched = true
return
}
@ -153,7 +145,10 @@ func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate,
// whether it complies with RFC 6066 about SNI, but I think
// it does, soooo...)
// (this is how we solved the former ACME TLS-SNI challenge)
if directCert, ok := cfg.certCache.cache[name]; ok {
cfg.certCache.mu.RLock()
directCert, ok := cfg.certCache.cache[name]
cfg.certCache.mu.RUnlock()
if ok {
cert = directCert
matched = true
return
@ -316,7 +311,7 @@ func (cfg *Config) handshakeMaintenance(hello *tls.ClientHelloInfo, cert Certifi
if cert.OCSP != nil {
refreshTime := cert.OCSP.ThisUpdate.Add(cert.OCSP.NextUpdate.Sub(cert.OCSP.ThisUpdate) / 2)
if time.Now().After(refreshTime) {
err := cfg.certCache.stapleOCSP(&cert, nil)
err := stapleOCSP(cfg.Storage, &cert, nil)
if err != nil {
// An error with OCSP stapling is not the end of the world, and in fact, is
// quite common considering not all certs have issuer URLs that support it.
@ -362,15 +357,12 @@ func (cfg *Config) renewDynamicCertificate(hello *tls.ClientHelloInfo, currentCe
// even though the recursive nature of the dynamic cert loading
// would just call this function anyway, we do it here to
// make the replacement as atomic as possible.
newCert, err := currentCert.configs[0].CacheManagedCertificate(name)
newCert, err := cfg.CacheManagedCertificate(name)
if err != nil {
log.Printf("[ERROR] loading renewed certificate for %s: %v", name, err)
} else {
// replace the old certificate with the new one
err = cfg.certCache.replaceCertificate(currentCert, newCert)
if err != nil {
log.Printf("[ERROR] Replacing certificate for %s: %v", name, err)
}
cfg.certCache.replaceCertificate(currentCert, newCert)
}
}
@ -396,7 +388,7 @@ func (cfg *Config) renewDynamicCertificate(hello *tls.ClientHelloInfo, currentCe
// A boolean true is returned if a valid certificate is returned.
func (cfg *Config) tryDistributedChallengeSolver(clientHello *tls.ClientHelloInfo) (Certificate, bool, error) {
tokenKey := distributedSolver{config: cfg}.challengeTokensKey(clientHello.ServerName)
chalInfoBytes, err := cfg.certCache.storage.Load(tokenKey)
chalInfoBytes, err := cfg.Storage.Load(tokenKey)
if err != nil {
if _, ok := err.(ErrNotExist); ok {
return Certificate{}, false, nil

View file

@ -20,7 +20,7 @@ import (
"net/http"
"strings"
"github.com/xenolf/lego/challenge/http01"
"github.com/go-acme/lego/challenge/http01"
)
// HTTPChallengeHandler wraps h in a handler that can solve the ACME
@ -57,7 +57,7 @@ func (cfg *Config) HandleHTTPChallenge(w http.ResponseWriter, r *http.Request) b
if cfg.DisableHTTPChallenge {
return false
}
if !strings.HasPrefix(r.URL.Path, challengeBasePath) {
if !LooksLikeHTTPChallenge(r) {
return false
}
return cfg.distributedHTTPChallengeSolver(w, r)
@ -73,7 +73,7 @@ func (cfg *Config) distributedHTTPChallengeSolver(w http.ResponseWriter, r *http
}
tokenKey := distributedSolver{config: cfg}.challengeTokensKey(r.Host)
chalInfoBytes, err := cfg.certCache.storage.Load(tokenKey)
chalInfoBytes, err := cfg.Storage.Load(tokenKey)
if err != nil {
if _, ok := err.(ErrNotExist); !ok {
log.Printf("[ERROR][%s] Opening distributed HTTP challenge token file: %v", r.Host, err)
@ -108,4 +108,10 @@ func answerHTTPChallenge(w http.ResponseWriter, r *http.Request, chalInfo challe
return false
}
// LooksLikeHTTPChallenge returns true if r looks like an ACME
// HTTP challenge request from an ACME server.
func LooksLikeHTTPChallenge(r *http.Request) bool {
return r.Method == "GET" && strings.HasPrefix(r.URL.Path, challengeBasePath)
}
const challengeBasePath = "/.well-known/acme-challenge"

View file

@ -24,37 +24,34 @@ import (
// maintainAssets is a permanently-blocking function
// that loops indefinitely and, on a regular schedule, checks
// certificates for expiration and initiates a renewal of certs
// that are expiring soon. It also updates OCSP stapling and
// performs other maintenance of assets. It should only be
// called once per process.
//
// You must pass in the channel which you'll close when
// maintenance should stop, to allow this goroutine to clean up
// after itself and unblock. (Not that you HAVE to stop it...)
// that are expiring soon. It also updates OCSP stapling. It
// should only be called once per cache.
func (certCache *Cache) maintainAssets() {
renewalTicker := time.NewTicker(certCache.RenewInterval)
ocspTicker := time.NewTicker(certCache.OCSPInterval)
renewalTicker := time.NewTicker(certCache.options.RenewCheckInterval)
ocspTicker := time.NewTicker(certCache.options.OCSPCheckInterval)
log.Printf("[INFO][%s] Started certificate maintenance routine", certCache.storage)
log.Printf("[INFO][cache:%p] Started certificate maintenance routine", certCache)
for {
select {
case <-renewalTicker.C:
log.Printf("[INFO][%s] Scanning for expiring certificates", certCache.storage)
log.Printf("[INFO][cache:%p] Scanning for expiring certificates", certCache)
err := certCache.RenewManagedCertificates(false)
if err != nil {
log.Printf("[ERROR][%s] Renewing managed certificates: %v", certCache.storage, err)
log.Printf("[ERROR][cache:%p] Renewing managed certificates: %v", certCache, err)
}
log.Printf("[INFO][%s] Done scanning certificates", certCache.storage)
log.Printf("[INFO][cache:%p] Done scanning certificates", certCache)
case <-ocspTicker.C:
log.Printf("[INFO][%s] Scanning for stale OCSP staples", certCache.storage)
log.Printf("[INFO][cache:%p] Scanning for stale OCSP staples", certCache)
certCache.updateOCSPStaples()
certCache.deleteOldStapleFiles()
log.Printf("[INFO][%s] Done checking OCSP staples", certCache.storage)
// certCache.deleteOldStapleFiles()
log.Printf("[INFO][cache:%p] Done checking OCSP staples", certCache)
case <-certCache.stopChan:
renewalTicker.Stop()
ocspTicker.Stop()
log.Printf("[INFO][%s] Stopped certificate maintenance routine", certCache.storage)
// TODO: stop any in-progress maintenance operations and clear locks we made
log.Printf("[INFO][cache:%p] Stopped certificate maintenance routine", certCache)
close(certCache.doneChan)
return
}
}
@ -65,6 +62,10 @@ func (certCache *Cache) maintainAssets() {
// automatically on a regular basis; normally you will not
// need to call this.
func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
// configs will hold a map of certificate name to the config
// to use when managing that certificate
configs := make(map[string]*Config)
// we use the queues for a very important reason: to do any and all
// operations that could require an exclusive write lock outside
// of the read lock! otherwise we get a deadlock, yikes. in other
@ -75,11 +76,6 @@ func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
certCache.mu.RLock()
for certKey, cert := range certCache.cache {
if len(cert.configs) == 0 {
// this is bad if this happens, probably a programmer error (oops)
log.Printf("[ERROR] No associated TLS config for certificate with names %v; unable to manage", cert.Names)
continue
}
if !cert.managed {
continue
}
@ -91,13 +87,26 @@ func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
continue
}
// get the config associated with this certificate
cfg, err := certCache.getConfig(cert)
if err != nil {
log.Printf("[ERROR] Getting configuration to manage certificate for names %v; unable to renew: %v", cert.Names, err)
continue
}
if cfg == nil {
// this is bad if this happens, probably a programmer error (oops)
log.Printf("[ERROR] No configuration associated with certificate for names %v; unable to manage", cert.Names)
continue
}
configs[cert.Names[0]] = cfg
// if time is up or expires soon, we need to try to renew it
if cert.NeedsRenewal() {
if cert.NeedsRenewal(cfg) {
// see if the certificate in storage has already been renewed, possibly by another
// instance that didn't coordinate with this one; if so, just load it (this
// might happen if another instance already renewed it - kinda sloppy but checking disk
// first is a simple way to possibly drastically reduce rate limit problems)
storedCertExpiring, err := managedCertInStorageExpiresSoon(cert)
storedCertExpiring, err := cfg.managedCertInStorageExpiresSoon(cert)
if err != nil {
// hmm, weird, but not a big deal, maybe it was deleted or something
log.Printf("[NOTICE] Error while checking if certificate for %v in storage is also expiring soon: %v",
@ -125,7 +134,9 @@ func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
log.Printf("[INFO] Certificate for %v expires in %v, but is already renewed in storage; reloading stored certificate",
oldCert.Names, timeLeft)
err := certCache.reloadManagedCertificate(oldCert)
cfg := configs[oldCert.Names[0]]
err := cfg.reloadManagedCertificate(oldCert)
if err != nil {
if interactive {
return err // operator is present, so report error immediately
@ -139,28 +150,30 @@ func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
timeLeft := oldCert.NotAfter.Sub(time.Now().UTC())
log.Printf("[INFO] Certificate for %v expires in %v; attempting renewal", oldCert.Names, timeLeft)
cfg := configs[oldCert.Names[0]]
// Get the name which we should use to renew this certificate;
// we only support managing certificates with one name per cert,
// so this should be easy.
renewName := oldCert.Names[0]
// perform renewal
err := oldCert.configs[0].RenewCert(renewName, interactive)
err := cfg.RenewCert(renewName, interactive)
if err != nil {
if interactive {
// Certificate renewal failed and the operator is present. See a discussion about
// this in issue mholt/caddy#642. For a while, we only stopped if the certificate
// was expired, but in reality, there is no difference between reporting it now
// versus later, except that there's somebody present to deal withit right now.
// versus later, except that there's somebody present to deal with it right now.
// Follow-up: See issue mholt/caddy#1680. Only fail in this case if the certificate
// is dangerously close to expiration.
timeLeft := oldCert.NotAfter.Sub(time.Now().UTC())
if timeLeft < oldCert.configs[0].RenewDurationBeforeAtStartup {
if timeLeft < cfg.RenewDurationBeforeAtStartup {
return err
}
}
log.Printf("[ERROR] %v", err)
if oldCert.configs[0].OnDemand != nil {
if cfg.OnDemand != nil {
// loaded dynamically, remove dynamically
deleteQueue = append(deleteQueue, oldCert)
}
@ -169,7 +182,7 @@ func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
// successful renewal, so update in-memory cache by loading
// renewed certificate so it will be used with handshakes
err = certCache.reloadManagedCertificate(oldCert)
err = cfg.reloadManagedCertificate(oldCert)
if err != nil {
if interactive {
return err // operator is present, so report error immediately
@ -180,18 +193,7 @@ func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
// Deletion queue
for _, cert := range deleteQueue {
certCache.mu.Lock()
// remove any pointers to this certificate from Configs
for _, cfg := range cert.configs {
for name, certKey := range cfg.certificates {
if certKey == cert.Hash {
delete(cfg.certificates, name)
}
}
}
// then delete the certificate from the cache
delete(certCache.cache, cert.Hash)
certCache.mu.Unlock()
certCache.removeCertificate(cert)
}
return nil
@ -229,7 +231,18 @@ func (certCache *Cache) updateOCSPStaples() {
}
}
err := certCache.stapleOCSP(&cert, nil)
cfg, err := certCache.getConfig(cert)
if err != nil {
log.Printf("[ERROR] Getting configuration to manage OCSP for certificate with names %v; unable to refresh: %v", cert.Names, err)
continue
}
if cfg == nil {
// this is bad if this happens, probably a programmer error (oops)
log.Printf("[ERROR] No configuration associated with certificate for names %v; unable to manage OCSP", cert.Names)
continue
}
err = stapleOCSP(cfg.Storage, &cert, nil)
if err != nil {
if cert.OCSP != nil {
// if there was no staple before, that's fine; otherwise we should log the error
@ -260,16 +273,32 @@ func (certCache *Cache) updateOCSPStaples() {
}
}
// deleteOldStapleFiles deletes cached OCSP staples that have expired.
// CleanStorageOptions specifies how to clean up a storage unit.
type CleanStorageOptions struct {
OCSPStaples bool
// TODO: long-expired certificates
}
// CleanStorage tidies up the given storage according to opts; this
// generally involves deleting assets which are no longer required.
// TODO: We should do this for long-expired certificates, too.
func (certCache *Cache) deleteOldStapleFiles() {
ocspKeys, err := certCache.storage.List(prefixOCSP, false)
func CleanStorage(storage Storage, opts CleanStorageOptions) {
if opts.OCSPStaples {
err := deleteOldOCSPStaples(storage)
if err != nil {
log.Printf("[ERROR] Deleting old OCSP staples: %v", err)
}
}
}
func deleteOldOCSPStaples(storage Storage) error {
ocspKeys, err := storage.List(prefixOCSP, false)
if err != nil {
// maybe just hasn't been created yet; no big deal
return
return nil
}
for _, key := range ocspKeys {
ocspBytes, err := certCache.storage.Load(key)
ocspBytes, err := storage.Load(key)
if err != nil {
log.Printf("[ERROR] While deleting old OCSP staples, unable to load staple file: %v", err)
continue
@ -277,7 +306,7 @@ func (certCache *Cache) deleteOldStapleFiles() {
resp, err := ocsp.ParseResponse(ocspBytes, nil)
if err != nil {
// contents are invalid; delete it
err = certCache.storage.Delete(key)
err = storage.Delete(key)
if err != nil {
log.Printf("[ERROR] Purging corrupt staple file %s: %v", key, err)
}
@ -285,17 +314,18 @@ func (certCache *Cache) deleteOldStapleFiles() {
}
if time.Now().After(resp.NextUpdate) {
// response has expired; delete it
err = certCache.storage.Delete(key)
err = storage.Delete(key)
if err != nil {
log.Printf("[ERROR] Purging expired staple file %s: %v", key, err)
}
}
}
return nil
}
const (
// DefaultRenewInterval is how often to check certificates for renewal.
DefaultRenewInterval = 12 * time.Hour
// DefaultRenewCheckInterval is how often to check certificates for renewal.
DefaultRenewCheckInterval = 12 * time.Hour
// DefaultRenewDurationBefore is how long before expiration to renew certificates.
DefaultRenewDurationBefore = (24 * time.Hour) * 30
@ -304,6 +334,6 @@ const (
// a renewed certificate when the process is first starting up (see mholt/caddy#1680).
DefaultRenewDurationBeforeAtStartup = (24 * time.Hour) * 7
// DefaultOCSPInterval is how often to check if OCSP stapling needs updating.
DefaultOCSPInterval = 1 * time.Hour
// DefaultOCSPCheckInterval is how often to check if OCSP stapling needs updating.
DefaultOCSPCheckInterval = 1 * time.Hour
)

View file

@ -35,7 +35,7 @@ import (
//
// Errors here are not necessarily fatal, it could just be that the
// certificate doesn't have an issuer URL.
func (certCache *Cache) stapleOCSP(cert *Certificate, pemBundle []byte) error {
func stapleOCSP(storage Storage, cert *Certificate, pemBundle []byte) error {
if pemBundle == nil {
// we need a PEM encoding only for some function calls below
bundle := new(bytes.Buffer)
@ -53,7 +53,7 @@ func (certCache *Cache) stapleOCSP(cert *Certificate, pemBundle []byte) error {
// First try to load OCSP staple from storage and see if
// we can still use it.
ocspStapleKey := StorageKeys.OCSPStaple(cert, pemBundle)
cachedOCSP, err := certCache.storage.Load(ocspStapleKey)
cachedOCSP, err := storage.Load(ocspStapleKey)
if err == nil {
resp, err := ocsp.ParseResponse(cachedOCSP, nil)
if err == nil {
@ -69,7 +69,7 @@ func (certCache *Cache) stapleOCSP(cert *Certificate, pemBundle []byte) error {
// because we loaded it by name, whereas the maintenance routine
// just iterates the list of files, even if somehow a non-staple
// file gets in the folder. in this case we are sure it is corrupt.)
err := certCache.storage.Delete(ocspStapleKey)
err := storage.Delete(ocspStapleKey)
if err != nil {
log.Printf("[WARNING] Unable to delete invalid OCSP staple file: %v", err)
}
@ -104,7 +104,7 @@ func (certCache *Cache) stapleOCSP(cert *Certificate, pemBundle []byte) error {
cert.Certificate.OCSPStaple = ocspBytes
cert.OCSP = ocspResp
if gotNewOCSP {
err := certCache.storage.Store(ocspStapleKey, ocspBytes)
err := storage.Store(ocspStapleKey, ocspBytes)
if err != nil {
return fmt.Errorf("unable to write OCSP staple file for %v: %v", cert.Names, err)
}
@ -121,7 +121,7 @@ func (certCache *Cache) stapleOCSP(cert *Certificate, pemBundle []byte) error {
// IssuingCertificateURL in the certificate. If the []byte and/or ocsp.Response return
// values are nil, the OCSP status may be assumed OCSPUnknown.
//
// Borrowed from github.com/xenolf/lego
// Borrowed from github.com/go-acme/lego
func getOCSPForCert(bundle []byte) ([]byte, *ocsp.Response, error) {
// TODO: Perhaps this should be synchronized too, with a Locker?

View file

@ -20,8 +20,8 @@ import (
"log"
"path/filepath"
"github.com/xenolf/lego/challenge"
"github.com/xenolf/lego/challenge/tlsalpn01"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/challenge/tlsalpn01"
)
// tlsALPNSolver is a type that can solve TLS-ALPN challenges using
@ -117,7 +117,7 @@ func (dhs distributedSolver) Present(domain, token, keyAuth string) error {
return err
}
return dhs.config.certCache.storage.Store(dhs.challengeTokensKey(domain), infoBytes)
return dhs.config.Storage.Store(dhs.challengeTokensKey(domain), infoBytes)
}
// CleanUp invokes the underlying solver's CleanUp method
@ -129,7 +129,7 @@ func (dhs distributedSolver) CleanUp(domain, token, keyAuth string) error {
log.Printf("[ERROR] Cleaning up standard provider server: %v", err)
}
}
return dhs.config.certCache.storage.Delete(dhs.challengeTokensKey(domain))
return dhs.config.Storage.Delete(dhs.challengeTokensKey(domain))
}
// challengeTokensPrefix returns the key prefix for challenge info.

View file

@ -267,6 +267,3 @@ type ErrNotExist interface {
// defaultFileStorage is a convenient, default storage
// implementation using the local file system.
var defaultFileStorage = &FileStorage{Path: dataDir()}
// DefaultStorage is the default Storage implementation.
var DefaultStorage Storage = defaultFileStorage

View file

@ -29,8 +29,8 @@ import (
"sort"
"strings"
"github.com/xenolf/lego/acme"
"github.com/xenolf/lego/registration"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/registration"
)
// user represents a Let's Encrypt user account.
@ -79,15 +79,18 @@ func (cfg *Config) newUser(email string) (user, error) {
// will NOT be prompted and an empty email may be returned.
func (cfg *Config) getEmail(allowPrompts bool) error {
leEmail := cfg.Email
// First try package default email
if leEmail == "" {
leEmail = Email
leEmail = Default.Email
}
// Then try to get most recent user email from storage
var gotRecentEmail bool
if leEmail == "" {
leEmail = cfg.mostRecentUserEmail()
leEmail, gotRecentEmail = cfg.mostRecentUserEmail()
}
if leEmail == "" && allowPrompts {
if !gotRecentEmail && leEmail == "" && allowPrompts {
// Looks like there is no email address readily available,
// so we will have to ask the user if we can.
var err error
@ -95,10 +98,16 @@ func (cfg *Config) getEmail(allowPrompts bool) error {
if err != nil {
return err
}
cfg.Agreed = true
// User might have just signified their agreement
cfg.Agreed = Default.Agreed
}
// lower-casing the email is important for consistency
cfg.Email = strings.ToLower(leEmail)
// save the email for later and ensure it is consistent
// for repeated use; then update cfg with the email
Default.Email = strings.TrimSpace(strings.ToLower(leEmail))
cfg.Email = Default.Email
return nil
}
@ -106,7 +115,7 @@ func (cfg *Config) getAgreementURL() (string, error) {
if agreementTestURL != "" {
return agreementTestURL, nil
}
caURL := CA
caURL := Default.CA
if cfg.CA != "" {
caURL = cfg.CA
}
@ -123,6 +132,11 @@ func (cfg *Config) getAgreementURL() (string, error) {
return dir.Meta.TermsOfService, nil
}
// promptUserForEmail prompts the user for an email address
// and returns the email address they entered (which could
// be the empty string). If no error is returned, then Agreed
// will also be set to true, since continuing through the
// prompt signifies agreement.
func (cfg *Config) promptUserForEmail() (string, error) {
agreementURL, err := cfg.getAgreementURL()
if err != nil {
@ -139,6 +153,7 @@ func (cfg *Config) promptUserForEmail() (string, error) {
return "", fmt.Errorf("reading email address: %v", err)
}
leEmail = strings.TrimSpace(leEmail)
Default.Agreed = true
return leEmail, nil
}
@ -150,7 +165,7 @@ func (cfg *Config) promptUserForEmail() (string, error) {
func (cfg *Config) getUser(email string) (user, error) {
var user user
regBytes, err := cfg.certCache.storage.Load(StorageKeys.UserReg(cfg.CA, email))
regBytes, err := cfg.Storage.Load(StorageKeys.UserReg(cfg.CA, email))
if err != nil {
if _, ok := err.(ErrNotExist); ok {
// create a new user
@ -158,7 +173,7 @@ func (cfg *Config) getUser(email string) (user, error) {
}
return user, err
}
keyBytes, err := cfg.certCache.storage.Load(StorageKeys.UserPrivateKey(cfg.CA, email))
keyBytes, err := cfg.Storage.Load(StorageKeys.UserPrivateKey(cfg.CA, email))
if err != nil {
if _, ok := err.(ErrNotExist); ok {
// create a new user
@ -201,7 +216,7 @@ func (cfg *Config) saveUser(user user) error {
},
}
return storeTx(cfg.certCache.storage, all)
return storeTx(cfg.Storage, all)
}
// promptUserAgreement simply outputs the standard user
@ -234,21 +249,21 @@ func (cfg *Config) askUserAgreement(agreementURL string) bool {
// in s. Since this is part of a complex sequence to get a user
// account, errors here are discarded to simplify code flow in
// the caller, and errors are not important here anyway.
func (cfg *Config) mostRecentUserEmail() string {
userList, err := cfg.certCache.storage.List(StorageKeys.UsersPrefix(cfg.CA), false)
func (cfg *Config) mostRecentUserEmail() (string, bool) {
userList, err := cfg.Storage.List(StorageKeys.UsersPrefix(cfg.CA), false)
if err != nil || len(userList) == 0 {
return ""
return "", false
}
sort.Slice(userList, func(i, j int) bool {
iInfo, _ := cfg.certCache.storage.Stat(userList[i])
jInfo, _ := cfg.certCache.storage.Stat(userList[j])
iInfo, _ := cfg.Storage.Stat(userList[i])
jInfo, _ := cfg.Storage.Stat(userList[j])
return jInfo.Modified.Before(iInfo.Modified)
})
user, err := cfg.getUser(path.Base(userList[0]))
if err != nil {
return ""
return "", false
}
return user.Email
return user.Email, true
}
// agreementTestURL is set during tests to skip requiring