diff options
Diffstat (limited to 'core/https/setup.go')
-rw-r--r-- | core/https/setup.go | 321 |
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, +} |