mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-29 12:16:16 +08:00
a368230ba5
* introduced own ChallengeProvider type, based on acme.ChallengeProvider to avoid vendoring/version mismatches in Caddy plugins; see Caddy issue #1697 * fixed up comments for ChallengeProvider * moved ChallengeProvider to caddytls/tls.go
194 lines
6.5 KiB
Go
194 lines
6.5 KiB
Go
// Package caddytls facilitates the management of TLS assets and integrates
|
|
// Let's Encrypt functionality into Caddy with first-class support for
|
|
// creating and renewing certificates automatically. It also implements
|
|
// the tls directive.
|
|
//
|
|
// This package is meant to be used by Caddy server types. To use the
|
|
// tls directive, a server type must import this package and call
|
|
// RegisterConfigGetter(). The server type must make and keep track of
|
|
// the caddytls.Config structs that this package produces. It must also
|
|
// add tls to its list of directives. When it comes time to make the
|
|
// server instances, the server type can call MakeTLSConfig() to convert
|
|
// a []caddytls.Config to a single tls.Config for use in tls.NewListener().
|
|
// It is also recommended to call RotateSessionTicketKeys() when
|
|
// starting a new listener.
|
|
package caddytls
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net"
|
|
"strings"
|
|
|
|
"github.com/mholt/caddy"
|
|
"github.com/xenolf/lego/acme"
|
|
)
|
|
|
|
// HostQualifies returns true if the hostname alone
|
|
// appears eligible for automatic HTTPS. For example,
|
|
// localhost, empty hostname, and IP addresses are
|
|
// not eligible because we cannot obtain certificates
|
|
// for those names.
|
|
func HostQualifies(hostname string) bool {
|
|
return hostname != "localhost" && // localhost is ineligible
|
|
|
|
// hostname must not be empty
|
|
strings.TrimSpace(hostname) != "" &&
|
|
|
|
// must not contain wildcard (*) characters (until CA supports it)
|
|
!strings.Contains(hostname, "*") &&
|
|
|
|
// must not start or end with a dot
|
|
!strings.HasPrefix(hostname, ".") &&
|
|
!strings.HasSuffix(hostname, ".") &&
|
|
|
|
// cannot be an IP address, see
|
|
// https://community.letsencrypt.org/t/certificate-for-static-ip/84/2?u=mholt
|
|
net.ParseIP(hostname) == nil
|
|
}
|
|
|
|
// saveCertResource saves the certificate resource to disk. This
|
|
// includes the certificate file itself, the private key, and the
|
|
// metadata file.
|
|
func saveCertResource(storage Storage, cert acme.CertificateResource) error {
|
|
// Save cert, private key, and metadata
|
|
siteData := &SiteData{
|
|
Cert: cert.Certificate,
|
|
Key: cert.PrivateKey,
|
|
}
|
|
var err error
|
|
siteData.Meta, err = json.MarshalIndent(&cert, "", "\t")
|
|
if err == nil {
|
|
err = storage.StoreSite(cert.Domain, siteData)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Revoke revokes the certificate for host via ACME protocol.
|
|
// It assumes the certificate was obtained from the
|
|
// CA at DefaultCAUrl.
|
|
func Revoke(host string) error {
|
|
client, err := newACMEClient(new(Config), true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return client.Revoke(host)
|
|
}
|
|
|
|
// tlsSniSolver is a type that can solve tls-sni challenges using
|
|
// an existing listener and our custom, in-memory certificate cache.
|
|
type tlsSniSolver struct{}
|
|
|
|
// Present adds the challenge certificate to the cache.
|
|
func (s tlsSniSolver) Present(domain, token, keyAuth string) error {
|
|
cert, acmeDomain, err := acme.TLSSNI01ChallengeCert(keyAuth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cacheCertificate(Certificate{
|
|
Certificate: cert,
|
|
Names: []string{acmeDomain},
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// CleanUp removes the challenge certificate from the cache.
|
|
func (s tlsSniSolver) CleanUp(domain, token, keyAuth string) error {
|
|
_, acmeDomain, err := acme.TLSSNI01ChallengeCert(keyAuth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
uncacheCertificate(acmeDomain)
|
|
return nil
|
|
}
|
|
|
|
// ConfigHolder is any type that has a Config; it presumably is
|
|
// connected to a hostname and port on which it is serving.
|
|
type ConfigHolder interface {
|
|
TLSConfig() *Config
|
|
Host() string
|
|
Port() string
|
|
}
|
|
|
|
// QualifiesForManagedTLS returns true if c qualifies for
|
|
// for managed TLS (but not on-demand TLS specifically).
|
|
// It does NOT check to see if a cert and key already exist
|
|
// for the config. If the return value is true, you should
|
|
// be OK to set c.TLSConfig().Managed to true; then you should
|
|
// check that value in the future instead, because the process
|
|
// of setting up the config may make it look like it doesn't
|
|
// qualify even though it originally did.
|
|
func QualifiesForManagedTLS(c ConfigHolder) bool {
|
|
if c == nil {
|
|
return false
|
|
}
|
|
tlsConfig := c.TLSConfig()
|
|
if tlsConfig == nil {
|
|
return false
|
|
}
|
|
|
|
return (!tlsConfig.Manual || tlsConfig.OnDemand) && // user might provide own cert and key
|
|
|
|
// if self-signed, we've already generated one to use
|
|
!tlsConfig.SelfSigned &&
|
|
|
|
// user can force-disable managed TLS
|
|
c.Port() != "80" &&
|
|
tlsConfig.ACMEEmail != "off" &&
|
|
|
|
// we get can't certs for some kinds of hostnames, but
|
|
// on-demand TLS allows empty hostnames at startup
|
|
(HostQualifies(c.Host()) || tlsConfig.OnDemand)
|
|
}
|
|
|
|
// ChallengeProvider defines an own type that should be used in Caddy plugins
|
|
// over acme.ChallengeProvider. Using acme.ChallengeProvider causes version mismatches
|
|
// with vendored dependencies (see https://github.com/mattfarina/golang-broken-vendor)
|
|
//
|
|
// acme.ChallengeProvider is an interface that allows the implementation of custom
|
|
// challenge providers. For more details, see:
|
|
// https://godoc.org/github.com/xenolf/lego/acme#ChallengeProvider
|
|
type ChallengeProvider acme.ChallengeProvider
|
|
|
|
// DNSProviderConstructor is a function that takes credentials and
|
|
// returns a type that can solve the ACME DNS challenges.
|
|
type DNSProviderConstructor func(credentials ...string) (ChallengeProvider, error)
|
|
|
|
// dnsProviders is the list of DNS providers that have been plugged in.
|
|
var dnsProviders = make(map[string]DNSProviderConstructor)
|
|
|
|
// RegisterDNSProvider registers provider by name for solving the ACME DNS challenge.
|
|
func RegisterDNSProvider(name string, provider DNSProviderConstructor) {
|
|
dnsProviders[name] = provider
|
|
caddy.RegisterPlugin("tls.dns."+name, caddy.Plugin{})
|
|
}
|
|
|
|
var (
|
|
// DefaultEmail represents the Let's Encrypt account email to use if none provided.
|
|
DefaultEmail string
|
|
|
|
// Agreed indicates whether user has agreed to the Let's Encrypt SA.
|
|
Agreed bool
|
|
|
|
// DefaultCAUrl is the default URL to the CA's ACME directory endpoint.
|
|
// It's very important to set this unless you set it in every Config.
|
|
DefaultCAUrl string
|
|
|
|
// DefaultKeyType is used as the type of key for new certificates
|
|
// when no other key type is specified.
|
|
DefaultKeyType = acme.RSA2048
|
|
|
|
// DisableHTTPChallenge will disable all HTTP challenges.
|
|
DisableHTTPChallenge bool
|
|
|
|
// DisableTLSSNIChallenge will disable all TLS-SNI challenges.
|
|
DisableTLSSNIChallenge bool
|
|
)
|
|
|
|
var storageProviders = make(map[string]StorageConstructor)
|
|
|
|
// RegisterStorageProvider registers provider by name for storing tls data
|
|
func RegisterStorageProvider(name string, provider StorageConstructor) {
|
|
storageProviders[name] = provider
|
|
caddy.RegisterPlugin("tls.storage."+name, caddy.Plugin{})
|
|
}
|