mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-23 22:27:38 -05:00
a798e0c951
- Server types no longer need to store their own contexts; they are stored on the caddy.Instance, which means each context will be properly GC'ed when the instance is stopped. Server types should use type assertions to convert from caddy.Context to their concrete context type when they need to use it. - Pass the entire context into httpserver.GetConfig instead of only the Key field. - caddy.NewTestController now requires a server type string so it can create a controller with the proper concrete context associated with that server type. Tests still need more attention so that we can test the proper creation of startup functions, etc.
275 lines
7.7 KiB
Go
275 lines
7.7 KiB
Go
package caddytls
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/tls"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/mholt/caddy"
|
|
)
|
|
|
|
func init() {
|
|
caddy.RegisterPlugin("tls", caddy.Plugin{Action: setupTLS})
|
|
}
|
|
|
|
// setupTLS 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 setupTLS(c *caddy.Controller) error {
|
|
configGetter, ok := configGetters[c.ServerType()]
|
|
if !ok {
|
|
return fmt.Errorf("no caddytls.ConfigGetter for %s server type; must call RegisterConfigGetter", c.ServerType())
|
|
}
|
|
config := configGetter(c)
|
|
if config == nil {
|
|
return fmt.Errorf("no caddytls.Config to set up for %s", c.Key)
|
|
}
|
|
|
|
config.Enabled = true
|
|
|
|
for c.Next() {
|
|
var certificateFile, keyFile, loadDir, maxCerts string
|
|
|
|
args := c.RemainingArgs()
|
|
switch len(args) {
|
|
case 1:
|
|
// even if the email is one of the special values below,
|
|
// it is still necessary for future analysis that we store
|
|
// that value in the ACMEEmail field.
|
|
config.ACMEEmail = args[0]
|
|
|
|
// user can force-disable managed TLS this way
|
|
if args[0] == "off" {
|
|
config.Enabled = false
|
|
return nil
|
|
}
|
|
|
|
// user might want a temporary, in-memory, self-signed cert
|
|
if args[0] == "self_signed" {
|
|
config.SelfSigned = true
|
|
}
|
|
case 2:
|
|
certificateFile = args[0]
|
|
keyFile = args[1]
|
|
config.Manual = true
|
|
}
|
|
|
|
// Optional block with extra parameters
|
|
var hadBlock bool
|
|
for c.NextBlock() {
|
|
hadBlock = true
|
|
switch c.Val() {
|
|
case "key_type":
|
|
arg := c.RemainingArgs()
|
|
value, ok := supportedKeyTypes[strings.ToUpper(arg[0])]
|
|
if !ok {
|
|
return c.Errf("Wrong key type name or key type not supported: '%s'", c.Val())
|
|
}
|
|
config.KeyType = value
|
|
case "protocols":
|
|
args := c.RemainingArgs()
|
|
if len(args) != 2 {
|
|
return c.ArgErr()
|
|
}
|
|
value, ok := supportedProtocols[strings.ToLower(args[0])]
|
|
if !ok {
|
|
return c.Errf("Wrong protocol name or protocol not supported: '%s'", args[0])
|
|
}
|
|
config.ProtocolMinVersion = value
|
|
value, ok = supportedProtocols[strings.ToLower(args[1])]
|
|
if !ok {
|
|
return c.Errf("Wrong protocol name or protocol not supported: '%s'", args[1])
|
|
}
|
|
config.ProtocolMaxVersion = value
|
|
case "ciphers":
|
|
for c.NextArg() {
|
|
value, ok := supportedCiphersMap[strings.ToUpper(c.Val())]
|
|
if !ok {
|
|
return c.Errf("Wrong cipher name or cipher not supported: '%s'", c.Val())
|
|
}
|
|
config.Ciphers = append(config.Ciphers, value)
|
|
}
|
|
case "clients":
|
|
clientCertList := c.RemainingArgs()
|
|
if len(clientCertList) == 0 {
|
|
return c.ArgErr()
|
|
}
|
|
|
|
listStart, mustProvideCA := 1, true
|
|
switch clientCertList[0] {
|
|
case "request":
|
|
config.ClientAuth = tls.RequestClientCert
|
|
mustProvideCA = false
|
|
case "require":
|
|
config.ClientAuth = tls.RequireAnyClientCert
|
|
mustProvideCA = false
|
|
case "verify_if_given":
|
|
config.ClientAuth = tls.VerifyClientCertIfGiven
|
|
default:
|
|
config.ClientAuth = tls.RequireAndVerifyClientCert
|
|
listStart = 0
|
|
}
|
|
if mustProvideCA && len(clientCertList) <= listStart {
|
|
return c.ArgErr()
|
|
}
|
|
|
|
config.ClientCerts = clientCertList[listStart:]
|
|
case "load":
|
|
c.Args(&loadDir)
|
|
config.Manual = true
|
|
case "max_certs":
|
|
c.Args(&maxCerts)
|
|
config.OnDemand = true
|
|
case "dns":
|
|
args := c.RemainingArgs()
|
|
if len(args) != 1 {
|
|
return c.ArgErr()
|
|
}
|
|
dnsProvName := args[0]
|
|
if _, ok := dnsProviders[dnsProvName]; !ok {
|
|
return c.Errf("Unsupported DNS provider '%s'", args[0])
|
|
}
|
|
config.DNSProvider = args[0]
|
|
default:
|
|
return 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 c.ArgErr()
|
|
}
|
|
|
|
// set certificate limit if on-demand TLS is enabled
|
|
if maxCerts != "" {
|
|
maxCertsNum, err := strconv.Atoi(maxCerts)
|
|
if err != nil || maxCertsNum < 1 {
|
|
return 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 !config.Enabled || !config.Manual {
|
|
continue
|
|
}
|
|
|
|
// load a single certificate and key, if specified
|
|
if certificateFile != "" && keyFile != "" {
|
|
err := cacheUnmanagedCertificatePEMFile(certificateFile, keyFile)
|
|
if err != nil {
|
|
return c.Errf("Unable to load certificate and key files for '%s': %v", c.Key, 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 err
|
|
}
|
|
}
|
|
}
|
|
|
|
SetDefaultTLSParams(config)
|
|
|
|
// generate self-signed cert if needed
|
|
if config.SelfSigned {
|
|
err := makeSelfSignedCert(config)
|
|
if err != nil {
|
|
return fmt.Errorf("self-signed: %v", err)
|
|
}
|
|
}
|
|
|
|
return 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 *caddy.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.Key, err)
|
|
}
|
|
log.Printf("[INFO] Successfully loaded TLS assets from %s", path)
|
|
}
|
|
return nil
|
|
})
|
|
}
|