aboutsummaryrefslogtreecommitdiff
path: root/core/https/setup.go
diff options
context:
space:
mode:
Diffstat (limited to 'core/https/setup.go')
-rw-r--r--core/https/setup.go321
1 files changed, 321 insertions, 0 deletions
diff --git a/core/https/setup.go b/core/https/setup.go
new file mode 100644
index 000000000..ec90e0284
--- /dev/null
+++ b/core/https/setup.go
@@ -0,0 +1,321 @@
+package https
+
+import (
+ "bytes"
+ "crypto/tls"
+ "encoding/pem"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/miekg/coredns/core/setup"
+ "github.com/miekg/coredns/middleware"
+ "github.com/miekg/coredns/server"
+)
+
+// Setup sets up the TLS configuration and installs certificates that
+// are specified by the user in the config file. All the automatic HTTPS
+// stuff comes later outside of this function.
+func Setup(c *setup.Controller) (middleware.Middleware, error) {
+ if c.Port == "80" {
+ c.TLS.Enabled = false
+ log.Printf("[WARNING] TLS disabled for %s.", c.Address())
+ return nil, nil
+ }
+ c.TLS.Enabled = true
+
+ // TODO(miek): disabled for now
+ return nil, nil
+
+ for c.Next() {
+ var certificateFile, keyFile, loadDir, maxCerts string
+
+ args := c.RemainingArgs()
+ switch len(args) {
+ case 1:
+ c.TLS.LetsEncryptEmail = args[0]
+
+ // user can force-disable managed TLS this way
+ if c.TLS.LetsEncryptEmail == "off" {
+ c.TLS.Enabled = false
+ return nil, nil
+ }
+ case 2:
+ certificateFile = args[0]
+ keyFile = args[1]
+ c.TLS.Manual = true
+ }
+
+ // Optional block with extra parameters
+ var hadBlock bool
+ for c.NextBlock() {
+ hadBlock = true
+ switch c.Val() {
+ case "protocols":
+ args := c.RemainingArgs()
+ if len(args) != 2 {
+ return nil, c.ArgErr()
+ }
+ value, ok := supportedProtocols[strings.ToLower(args[0])]
+ if !ok {
+ return nil, c.Errf("Wrong protocol name or protocol not supported '%s'", c.Val())
+ }
+ c.TLS.ProtocolMinVersion = value
+ value, ok = supportedProtocols[strings.ToLower(args[1])]
+ if !ok {
+ return nil, c.Errf("Wrong protocol name or protocol not supported '%s'", c.Val())
+ }
+ c.TLS.ProtocolMaxVersion = value
+ case "ciphers":
+ for c.NextArg() {
+ value, ok := supportedCiphersMap[strings.ToUpper(c.Val())]
+ if !ok {
+ return nil, c.Errf("Wrong cipher name or cipher not supported '%s'", c.Val())
+ }
+ c.TLS.Ciphers = append(c.TLS.Ciphers, value)
+ }
+ case "clients":
+ c.TLS.ClientCerts = c.RemainingArgs()
+ if len(c.TLS.ClientCerts) == 0 {
+ return nil, c.ArgErr()
+ }
+ case "load":
+ c.Args(&loadDir)
+ c.TLS.Manual = true
+ case "max_certs":
+ c.Args(&maxCerts)
+ c.TLS.OnDemand = true
+ default:
+ return nil, c.Errf("Unknown keyword '%s'", c.Val())
+ }
+ }
+
+ // tls requires at least one argument if a block is not opened
+ if len(args) == 0 && !hadBlock {
+ return nil, c.ArgErr()
+ }
+
+ // set certificate limit if on-demand TLS is enabled
+ if maxCerts != "" {
+ maxCertsNum, err := strconv.Atoi(maxCerts)
+ if err != nil || maxCertsNum < 1 {
+ return nil, c.Err("max_certs must be a positive integer")
+ }
+ if onDemandMaxIssue == 0 || int32(maxCertsNum) < onDemandMaxIssue { // keep the minimum; TODO: We have to do this because it is global; should be per-server or per-vhost...
+ onDemandMaxIssue = int32(maxCertsNum)
+ }
+ }
+
+ // don't try to load certificates unless we're supposed to
+ if !c.TLS.Enabled || !c.TLS.Manual {
+ continue
+ }
+
+ // load a single certificate and key, if specified
+ if certificateFile != "" && keyFile != "" {
+ err := cacheUnmanagedCertificatePEMFile(certificateFile, keyFile)
+ if err != nil {
+ return nil, c.Errf("Unable to load certificate and key files for %s: %v", c.Host, err)
+ }
+ log.Printf("[INFO] Successfully loaded TLS assets from %s and %s", certificateFile, keyFile)
+ }
+
+ // load a directory of certificates, if specified
+ if loadDir != "" {
+ err := loadCertsInDir(c, loadDir)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ setDefaultTLSParams(c.Config)
+
+ return nil, nil
+}
+
+// loadCertsInDir loads all the certificates/keys in dir, as long as
+// the file ends with .pem. This method of loading certificates is
+// modeled after haproxy, which expects the certificate and key to
+// be bundled into the same file:
+// https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#5.1-crt
+//
+// This function may write to the log as it walks the directory tree.
+func loadCertsInDir(c *setup.Controller, dir string) error {
+ return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ log.Printf("[WARNING] Unable to traverse into %s; skipping", path)
+ return nil
+ }
+ if info.IsDir() {
+ return nil
+ }
+ if strings.HasSuffix(strings.ToLower(info.Name()), ".pem") {
+ certBuilder, keyBuilder := new(bytes.Buffer), new(bytes.Buffer)
+ var foundKey bool // use only the first key in the file
+
+ bundle, err := ioutil.ReadFile(path)
+ if err != nil {
+ return err
+ }
+
+ for {
+ // Decode next block so we can see what type it is
+ var derBlock *pem.Block
+ derBlock, bundle = pem.Decode(bundle)
+ if derBlock == nil {
+ break
+ }
+
+ if derBlock.Type == "CERTIFICATE" {
+ // Re-encode certificate as PEM, appending to certificate chain
+ pem.Encode(certBuilder, derBlock)
+ } else if derBlock.Type == "EC PARAMETERS" {
+ // EC keys generated from openssl can be composed of two blocks:
+ // parameters and key (parameter block should come first)
+ if !foundKey {
+ // Encode parameters
+ pem.Encode(keyBuilder, derBlock)
+
+ // Key must immediately follow
+ derBlock, bundle = pem.Decode(bundle)
+ if derBlock == nil || derBlock.Type != "EC PRIVATE KEY" {
+ return c.Errf("%s: expected elliptic private key to immediately follow EC parameters", path)
+ }
+ pem.Encode(keyBuilder, derBlock)
+ foundKey = true
+ }
+ } else if derBlock.Type == "PRIVATE KEY" || strings.HasSuffix(derBlock.Type, " PRIVATE KEY") {
+ // RSA key
+ if !foundKey {
+ pem.Encode(keyBuilder, derBlock)
+ foundKey = true
+ }
+ } else {
+ return c.Errf("%s: unrecognized PEM block type: %s", path, derBlock.Type)
+ }
+ }
+
+ certPEMBytes, keyPEMBytes := certBuilder.Bytes(), keyBuilder.Bytes()
+ if len(certPEMBytes) == 0 {
+ return c.Errf("%s: failed to parse PEM data", path)
+ }
+ if len(keyPEMBytes) == 0 {
+ return c.Errf("%s: no private key block found", path)
+ }
+
+ err = cacheUnmanagedCertificatePEMBytes(certPEMBytes, keyPEMBytes)
+ if err != nil {
+ return c.Errf("%s: failed to load cert and key for %s: %v", path, c.Host, err)
+ }
+ log.Printf("[INFO] Successfully loaded TLS assets from %s", path)
+ }
+ return nil
+ })
+}
+
+// setDefaultTLSParams sets the default TLS cipher suites, protocol versions,
+// and server preferences of a server.Config if they were not previously set
+// (it does not overwrite; only fills in missing values). It will also set the
+// port to 443 if not already set, TLS is enabled, TLS is manual, and the host
+// does not equal localhost.
+func setDefaultTLSParams(c *server.Config) {
+ // If no ciphers provided, use default list
+ if len(c.TLS.Ciphers) == 0 {
+ c.TLS.Ciphers = defaultCiphers
+ }
+
+ // Not a cipher suite, but still important for mitigating protocol downgrade attacks
+ // (prepend since having it at end breaks http2 due to non-h2-approved suites before it)
+ c.TLS.Ciphers = append([]uint16{tls.TLS_FALLBACK_SCSV}, c.TLS.Ciphers...)
+
+ // Set default protocol min and max versions - must balance compatibility and security
+ if c.TLS.ProtocolMinVersion == 0 {
+ c.TLS.ProtocolMinVersion = tls.VersionTLS10
+ }
+ if c.TLS.ProtocolMaxVersion == 0 {
+ c.TLS.ProtocolMaxVersion = tls.VersionTLS12
+ }
+
+ // Prefer server cipher suites
+ c.TLS.PreferServerCipherSuites = true
+
+ // Default TLS port is 443; only use if port is not manually specified,
+ // TLS is enabled, and the host is not localhost
+ if c.Port == "" && c.TLS.Enabled && (!c.TLS.Manual || c.TLS.OnDemand) && c.Host != "localhost" {
+ c.Port = "443"
+ }
+}
+
+// Map of supported protocols.
+// SSLv3 will be not supported in future release.
+// HTTP/2 only supports TLS 1.2 and higher.
+var supportedProtocols = map[string]uint16{
+ "ssl3.0": tls.VersionSSL30,
+ "tls1.0": tls.VersionTLS10,
+ "tls1.1": tls.VersionTLS11,
+ "tls1.2": tls.VersionTLS12,
+}
+
+// Map of supported ciphers, used only for parsing config.
+//
+// Note that, at time of writing, HTTP/2 blacklists 276 cipher suites,
+// including all but two of the suites below (the two GCM suites).
+// See https://http2.github.io/http2-spec/#BadCipherSuites
+//
+// TLS_FALLBACK_SCSV is not in this list because we manually ensure
+// it is always added (even though it is not technically a cipher suite).
+//
+// This map, like any map, is NOT ORDERED. Do not range over this map.
+var supportedCiphersMap = map[string]uint16{
+ "ECDHE-RSA-AES256-GCM-SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ "ECDHE-ECDSA-AES256-GCM-SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+ "ECDHE-RSA-AES128-GCM-SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ "ECDHE-ECDSA-AES128-GCM-SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ "ECDHE-RSA-AES128-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ "ECDHE-RSA-AES256-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ "ECDHE-ECDSA-AES256-CBC-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ "ECDHE-ECDSA-AES128-CBC-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ "RSA-AES128-CBC-SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
+ "RSA-AES256-CBC-SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
+ "ECDHE-RSA-3DES-EDE-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ "RSA-3DES-EDE-CBC-SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
+}
+
+// List of supported cipher suites in descending order of preference.
+// Ordering is very important! Getting the wrong order will break
+// mainstream clients, especially with HTTP/2.
+//
+// Note that TLS_FALLBACK_SCSV is not in this list since it is always
+// added manually.
+var supportedCiphers = []uint16{
+ tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ tls.TLS_RSA_WITH_AES_256_CBC_SHA,
+ tls.TLS_RSA_WITH_AES_128_CBC_SHA,
+ tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
+}
+
+// List of all the ciphers we want to use by default
+var defaultCiphers = []uint16{
+ tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ tls.TLS_RSA_WITH_AES_256_CBC_SHA,
+ tls.TLS_RSA_WITH_AES_128_CBC_SHA,
+}