aboutsummaryrefslogtreecommitdiff
path: root/core/https/https.go
diff options
context:
space:
mode:
Diffstat (limited to 'core/https/https.go')
-rw-r--r--core/https/https.go339
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{}