411
vendor/github.com/mholt/certmagic/config.go
generated
vendored
Normal file
411
vendor/github.com/mholt/certmagic/config.go
generated
vendored
Normal file
@@ -0,0 +1,411 @@
|
||||
// Copyright 2015 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
// Config configures a certificate manager instance.
|
||||
// An empty Config is not valid: use New() to obtain
|
||||
// a valid Config.
|
||||
type Config struct {
|
||||
// The endpoint of the directory for the ACME
|
||||
// CA we are to use
|
||||
CA string
|
||||
|
||||
// The email address to use when creating or
|
||||
// selecting an existing ACME server account
|
||||
Email string
|
||||
|
||||
// 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 time.Duration
|
||||
|
||||
// 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 time.Duration
|
||||
|
||||
// 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.KeyType
|
||||
|
||||
// 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
|
||||
|
||||
// DefaultServerName specifies a server name
|
||||
// to use when choosing a certificate if the
|
||||
// ClientHello's ServerName field is empty
|
||||
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
|
||||
|
||||
// Map of hostname to certificate hash; used
|
||||
// to complete handshakes and serve the right
|
||||
// certificate given SNI
|
||||
certificates map[string]string
|
||||
|
||||
// Pointer to the certificate store to use
|
||||
certCache *Cache
|
||||
|
||||
// Map of client config key to ACME clients
|
||||
// so they can be reused
|
||||
acmeClients map[string]*lego.Client
|
||||
acmeClientsMu *sync.Mutex
|
||||
}
|
||||
|
||||
// NewDefault returns a new, valid, default config.
|
||||
//
|
||||
// Calling this function signifies your acceptance to
|
||||
// the CA's Subscriber Agreement and/or Terms of Service.
|
||||
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()
|
||||
}
|
||||
if certCache.storage == nil {
|
||||
certCache.storage = DefaultStorage
|
||||
}
|
||||
|
||||
// fill in default values
|
||||
if cfg.CA == "" {
|
||||
cfg.CA = CA
|
||||
}
|
||||
if cfg.Email == "" {
|
||||
cfg.Email = Email
|
||||
}
|
||||
if cfg.OnDemand == nil {
|
||||
cfg.OnDemand = OnDemand
|
||||
}
|
||||
if !cfg.Agreed {
|
||||
cfg.Agreed = Agreed
|
||||
}
|
||||
if !cfg.DisableHTTPChallenge {
|
||||
cfg.DisableHTTPChallenge = DisableHTTPChallenge
|
||||
}
|
||||
if !cfg.DisableTLSALPNChallenge {
|
||||
cfg.DisableTLSALPNChallenge = DisableTLSALPNChallenge
|
||||
}
|
||||
if cfg.RenewDurationBefore == 0 {
|
||||
cfg.RenewDurationBefore = RenewDurationBefore
|
||||
}
|
||||
if cfg.RenewDurationBeforeAtStartup == 0 {
|
||||
cfg.RenewDurationBeforeAtStartup = RenewDurationBeforeAtStartup
|
||||
}
|
||||
if cfg.OnEvent == nil {
|
||||
cfg.OnEvent = OnEvent
|
||||
}
|
||||
if cfg.ListenHost == "" {
|
||||
cfg.ListenHost = ListenHost
|
||||
}
|
||||
if cfg.AltHTTPPort == 0 {
|
||||
cfg.AltHTTPPort = AltHTTPPort
|
||||
}
|
||||
if cfg.AltTLSALPNPort == 0 {
|
||||
cfg.AltTLSALPNPort = AltTLSALPNPort
|
||||
}
|
||||
if cfg.DNSProvider == nil {
|
||||
cfg.DNSProvider = DNSProvider
|
||||
}
|
||||
if cfg.KeyType == "" {
|
||||
cfg.KeyType = KeyType
|
||||
}
|
||||
if cfg.CertObtainTimeout == 0 {
|
||||
cfg.CertObtainTimeout = CertObtainTimeout
|
||||
}
|
||||
if cfg.DefaultServerName == "" {
|
||||
cfg.DefaultServerName = DefaultServerName
|
||||
}
|
||||
if cfg.OnDemand == nil {
|
||||
cfg.OnDemand = OnDemand
|
||||
}
|
||||
if !cfg.MustStaple {
|
||||
cfg.MustStaple = MustStaple
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
return &cfg
|
||||
}
|
||||
|
||||
// Manage causes the certificates for domainNames to be managed
|
||||
// according to cfg. If cfg is enabled for OnDemand, then this
|
||||
// simply whitelists the domain names. Otherwise, the certificate(s)
|
||||
// for each name are loaded from storage or obtained from the CA;
|
||||
// and if loaded from storage, renewed if they are expiring or
|
||||
// expired. It then caches the certificate in memory and is
|
||||
// prepared to serve them up during TLS handshakes.
|
||||
func (cfg *Config) Manage(domainNames []string) error {
|
||||
for _, domainName := range domainNames {
|
||||
if !HostQualifies(domainName) {
|
||||
return fmt.Errorf("name does not qualify for automatic certificate management: %s", domainName)
|
||||
}
|
||||
|
||||
// if on-demand is configured, simply whitelist this name
|
||||
if cfg.OnDemand != nil {
|
||||
if !cfg.OnDemand.whitelistContains(domainName) {
|
||||
cfg.OnDemand.HostWhitelist = append(cfg.OnDemand.HostWhitelist, domainName)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// try loading an existing certificate; if it doesn't
|
||||
// exist yet, obtain one and try loading it again
|
||||
cert, err := cfg.CacheManagedCertificate(domainName)
|
||||
if err != nil {
|
||||
if _, ok := err.(ErrNotExist); ok {
|
||||
// if it doesn't exist, get it, then try loading it again
|
||||
err := cfg.ObtainCert(domainName, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: obtaining certificate: %v", domainName, err)
|
||||
}
|
||||
cert, err = cfg.CacheManagedCertificate(domainName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: caching certificate after obtaining it: %v", domainName, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("%s: caching certificate: %v", domainName, err)
|
||||
}
|
||||
|
||||
// for existing certificates, make sure it is renewed
|
||||
if cert.NeedsRenewal() {
|
||||
err := cfg.RenewCert(domainName, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: renewing certificate: %v", domainName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ObtainCert obtains a certificate for name using cfg, as long
|
||||
// as a certificate does not already exist in storage for that
|
||||
// name. The name must qualify and cfg must be flagged as Managed.
|
||||
// This function is a no-op if storage already has a certificate
|
||||
// for name.
|
||||
//
|
||||
// It only obtains and stores certificates (and their keys),
|
||||
// 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
|
||||
}
|
||||
if skip {
|
||||
return nil
|
||||
}
|
||||
client, err := cfg.newACMEClient(interactive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return client.Obtain(name)
|
||||
}
|
||||
|
||||
// RenewCert renews the certificate for name using cfg. It stows the
|
||||
// renewed certificate and its assets in storage if successful.
|
||||
func (cfg *Config) RenewCert(name string, interactive bool) error {
|
||||
skip, err := cfg.preObtainOrRenewChecks(name, interactive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skip {
|
||||
return nil
|
||||
}
|
||||
client, err := cfg.newACMEClient(interactive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return client.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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return client.Revoke(domain)
|
||||
}
|
||||
|
||||
// TLSConfig is an opinionated method that returns a
|
||||
// recommended, modern TLS configuration that can be
|
||||
// used to configure TLS listeners, which also supports
|
||||
// the TLS-ALPN challenge and serves up certificates
|
||||
// managed by cfg.
|
||||
//
|
||||
// Unlike the package TLS() function, this method does
|
||||
// not, by itself, enable certificate management for
|
||||
// any domain names.
|
||||
//
|
||||
// Feel free to further customize the returned tls.Config,
|
||||
// but do not mess with the GetCertificate or NextProtos
|
||||
// fields unless you know what you're doing, as they're
|
||||
// necessary to solve the TLS-ALPN challenge.
|
||||
func (cfg *Config) TLSConfig() *tls.Config {
|
||||
return &tls.Config{
|
||||
// these two fields necessary for TLS-ALPN challenge
|
||||
GetCertificate: cfg.GetCertificate,
|
||||
NextProtos: []string{"h2", "http/1.1", tlsalpn01.ACMETLS1Protocol},
|
||||
|
||||
// the rest recommended for modern TLS servers
|
||||
MinVersion: tls.VersionTLS12,
|
||||
CurvePreferences: []tls.CurveID{
|
||||
tls.X25519,
|
||||
tls.CurveP256,
|
||||
},
|
||||
CipherSuites: preferredDefaultCipherSuites(),
|
||||
PreferServerCipherSuites: true,
|
||||
}
|
||||
}
|
||||
|
||||
// preObtainOrRenewChecks perform a few simple checks before
|
||||
// obtaining or renewing a certificate with ACME, and returns
|
||||
// whether this name should be skipped (like if it's not
|
||||
// managed TLS) as well as any error. It ensures that the
|
||||
// config is Managed, that the name qualifies for a certificate,
|
||||
// and that an email address is available.
|
||||
func (cfg *Config) preObtainOrRenewChecks(name string, allowPrompts bool) (bool, error) {
|
||||
if !HostQualifies(name) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
err := cfg.getEmail(allowPrompts)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// storageHasCertResources returns true if the storage
|
||||
// associated with cfg's certificate cache has all the
|
||||
// resources related to the certificate for domain: the
|
||||
// certificate, the private key, and the metadata.
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return cert.NeedsRenewal()
|
||||
}
|
||||
Reference in New Issue
Block a user