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, }