diff options
Diffstat (limited to 'core/https/https.go')
-rw-r--r-- | core/https/https.go | 339 |
1 files changed, 0 insertions, 339 deletions
diff --git a/core/https/https.go b/core/https/https.go deleted file mode 100644 index 99ef2fef6..000000000 --- a/core/https/https.go +++ /dev/null @@ -1,339 +0,0 @@ -// Package https facilitates the management of TLS assets and integrates -// Let's Encrypt functionality into CoreDNS with first-class support for -// creating and renewing certificates automatically. It is designed to -// configure sites for HTTPS by default. -package https - -import ( - "encoding/json" - "errors" - "io/ioutil" - "net" - "os" - - "github.com/miekg/coredns/server" - "github.com/xenolf/lego/acme" -) - -// Activate sets up TLS for each server config in configs -// as needed; this consists of acquiring and maintaining -// certificates and keys for qualifying configs and enabling -// OCSP stapling for all TLS-enabled configs. -// -// This function may prompt the user to provide an email -// address if none is available through other means. It -// prefers the email address specified in the config, but -// if that is not available it will check the command line -// argument. If absent, it will use the most recent email -// address from last time. If there isn't one, the user -// will be prompted and shown SA link. -// -// Also note that calling this function activates asset -// management automatically, which keeps certificates -// renewed and OCSP stapling updated. -// -// Activate returns the updated list of configs, since -// some may have been appended, for example, to redirect -// plaintext HTTP requests to their HTTPS counterpart. -// This function only appends; it does not splice. -func Activate(configs []server.Config) ([]server.Config, error) { - // just in case previous caller forgot... - Deactivate() - - // pre-screen each config and earmark the ones that qualify for managed TLS - MarkQualified(configs) - - // place certificates and keys on disk - err := ObtainCerts(configs, true, false) - if err != nil { - return configs, err - } - - // update TLS configurations - err = EnableTLS(configs, true) - if err != nil { - return configs, err - } - - // renew all relevant certificates that need renewal. this is important - // to do right away for a couple reasons, mainly because each restart, - // the renewal ticker is reset, so if restarts happen more often than - // the ticker interval, renewals would never happen. but doing - // it right away at start guarantees that renewals aren't missed. - err = renewManagedCertificates(true) - if err != nil { - return configs, err - } - - // keep certificates renewed and OCSP stapling updated - go maintainAssets(stopChan) - - return configs, nil -} - -// Deactivate cleans up long-term, in-memory resources -// allocated by calling Activate(). Essentially, it stops -// the asset maintainer from running, meaning that certificates -// will not be renewed, OCSP staples will not be updated, etc. -func Deactivate() (err error) { - defer func() { - if rec := recover(); rec != nil { - err = errors.New("already deactivated") - } - }() - close(stopChan) - stopChan = make(chan struct{}) - return -} - -// MarkQualified scans each config and, if it qualifies for managed -// TLS, it sets the Managed field of the TLSConfig to true. -func MarkQualified(configs []server.Config) { - for i := 0; i < len(configs); i++ { - if ConfigQualifies(configs[i]) { - configs[i].TLS.Managed = true - } - } -} - -// ObtainCerts obtains certificates for all these configs as long as a -// certificate does not already exist on disk. It does not modify the -// configs at all; it only obtains and stores certificates and keys to -// the disk. If allowPrompts is true, the user may be shown a prompt. -// If proxyACME is true, the ACME challenges will be proxied to our alt port. -func ObtainCerts(configs []server.Config, allowPrompts, proxyACME bool) error { - // We group configs by email so we don't make the same clients over and - // over. This has the potential to prompt the user for an email, but we - // prevent that by assuming that if we already have a listener that can - // proxy ACME challenge requests, then the server is already running and - // the operator is no longer present. - groupedConfigs := groupConfigsByEmail(configs, allowPrompts) - - for email, group := range groupedConfigs { - // Wait as long as we can before creating the client, because it - // may not be needed, for example, if we already have what we - // need on disk. Creating a client involves the network and - // potentially prompting the user, etc., so only do if necessary. - var client *ACMEClient - - for _, cfg := range group { - if existingCertAndKey(cfg.Host) { - continue - } - - // Now we definitely do need a client - if client == nil { - var err error - client, err = NewACMEClient(email, allowPrompts) - if err != nil { - return errors.New("error creating client: " + err.Error()) - } - } - - // c.Configure assumes that allowPrompts == !proxyACME, - // but that's not always true. For example, a restart where - // the user isn't present and we're not listening on port 80. - // TODO: This could probably be refactored better. - if proxyACME { - client.SetHTTPAddress(net.JoinHostPort(cfg.BindHost, AlternatePort)) - client.SetTLSAddress(net.JoinHostPort(cfg.BindHost, AlternatePort)) - client.ExcludeChallenges([]acme.Challenge{acme.TLSSNI01, acme.DNS01}) - } else { - client.SetHTTPAddress(net.JoinHostPort(cfg.BindHost, "")) - client.SetTLSAddress(net.JoinHostPort(cfg.BindHost, "")) - client.ExcludeChallenges([]acme.Challenge{acme.DNS01}) - } - - err := client.Obtain([]string{cfg.Host}) - if err != nil { - return err - } - } - } - - return nil -} - -// groupConfigsByEmail groups configs by the email address to be used by an -// ACME client. It only groups configs that have TLS enabled and that are -// marked as Managed. If userPresent is true, the operator MAY be prompted -// for an email address. -func groupConfigsByEmail(configs []server.Config, userPresent bool) map[string][]server.Config { - initMap := make(map[string][]server.Config) - for _, cfg := range configs { - if !cfg.TLS.Managed { - continue - } - leEmail := getEmail(cfg, userPresent) - initMap[leEmail] = append(initMap[leEmail], cfg) - } - return initMap -} - -// EnableTLS configures each config to use TLS according to default settings. -// It will only change configs that are marked as managed, and assumes that -// certificates and keys are already on disk. If loadCertificates is true, -// the certificates will be loaded from disk into the cache for this process -// to use. If false, TLS will still be enabled and configured with default -// settings, but no certificates will be parsed loaded into the cache, and -// the returned error value will always be nil. -func EnableTLS(configs []server.Config, loadCertificates bool) error { - for i := 0; i < len(configs); i++ { - if !configs[i].TLS.Managed { - continue - } - configs[i].TLS.Enabled = true - if loadCertificates { - _, err := cacheManagedCertificate(configs[i].Host, false) - if err != nil { - return err - } - } - setDefaultTLSParams(&configs[i]) - } - return nil -} - -// hostHasOtherPort returns true if there is another config in the list with the same -// hostname that has port otherPort, or false otherwise. All the configs are checked -// against the hostname of allConfigs[thisConfigIdx]. -func hostHasOtherPort(allConfigs []server.Config, thisConfigIdx int, otherPort string) bool { - for i, otherCfg := range allConfigs { - if i == thisConfigIdx { - continue // has to be a config OTHER than the one we're comparing against - } - if otherCfg.Host == allConfigs[thisConfigIdx].Host && otherCfg.Port == otherPort { - return true - } - } - return false -} - -// ConfigQualifies returns true if cfg qualifies for -// fully managed TLS (but not on-demand TLS, which is -// not considered here). It does NOT check to see if a -// cert and key already exist for the config. If the -// config does qualify, you should set cfg.TLS.Managed -// to true and check that instead, because the process of -// setting up the config may make it look like it -// doesn't qualify even though it originally did. -func ConfigQualifies(cfg server.Config) bool { - return (!cfg.TLS.Manual || cfg.TLS.OnDemand) && // user might provide own cert and key - - // user can force-disable automatic HTTPS for this host - cfg.Port != "80" && - cfg.TLS.LetsEncryptEmail != "off" && - - // we get can't certs for some kinds of hostnames, but - // on-demand TLS allows empty hostnames at startup - cfg.TLS.OnDemand -} - -// existingCertAndKey returns true if the host has a certificate -// and private key in storage already, false otherwise. -func existingCertAndKey(host string) bool { - _, err := os.Stat(storage.SiteCertFile(host)) - if err != nil { - return false - } - _, err = os.Stat(storage.SiteKeyFile(host)) - if err != nil { - return false - } - return true -} - -// saveCertResource saves the certificate resource to disk. This -// includes the certificate file itself, the private key, and the -// metadata file. -func saveCertResource(cert acme.CertificateResource) error { - err := os.MkdirAll(storage.Site(cert.Domain), 0700) - if err != nil { - return err - } - - // Save cert - err = ioutil.WriteFile(storage.SiteCertFile(cert.Domain), cert.Certificate, 0600) - if err != nil { - return err - } - - // Save private key - err = ioutil.WriteFile(storage.SiteKeyFile(cert.Domain), cert.PrivateKey, 0600) - if err != nil { - return err - } - - // Save cert metadata - jsonBytes, err := json.MarshalIndent(&cert, "", "\t") - if err != nil { - return err - } - err = ioutil.WriteFile(storage.SiteMetaFile(cert.Domain), jsonBytes, 0600) - if err != nil { - return err - } - - return nil -} - -// Revoke revokes the certificate for host via ACME protocol. -func Revoke(host string) error { - if !existingCertAndKey(host) { - return errors.New("no certificate and key for " + host) - } - - email := getEmail(server.Config{Host: host}, true) - if email == "" { - return errors.New("email is required to revoke") - } - - client, err := NewACMEClient(email, true) - if err != nil { - return err - } - - certFile := storage.SiteCertFile(host) - certBytes, err := ioutil.ReadFile(certFile) - if err != nil { - return err - } - - err = client.RevokeCertificate(certBytes) - if err != nil { - return err - } - - err = os.Remove(certFile) - if err != nil { - return errors.New("certificate revoked, but unable to delete certificate file: " + err.Error()) - } - - return nil -} - -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 - - // CAUrl represents the base URL to the CA's ACME endpoint - CAUrl string -) - -// AlternatePort is the port on which the acme client will open a -// listener and solve the CA's challenges. If this alternate port -// is used instead of the default port (80 or 443), then the -// default port for the challenge must be forwarded to this one. -const AlternatePort = "5033" - -// KeyType is the type to use for new keys. -// This shouldn't need to change except for in tests; -// the size can be drastically reduced for speed. -var KeyType = acme.EC384 - -// stopChan is used to signal the maintenance goroutine -// to terminate. -var stopChan chan struct{} |