From 4a1e1649bc985e9658d326ed433a101d7d79ae30 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Tue, 3 Sep 2019 15:26:09 -0600 Subject: reverse_proxy: Implement remaining TLS config for proxy to backend --- modules/caddyhttp/reverseproxy/httptransport.go | 86 +++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 5 deletions(-) diff --git a/modules/caddyhttp/reverseproxy/httptransport.go b/modules/caddyhttp/reverseproxy/httptransport.go index 36dd776..999a352 100644 --- a/modules/caddyhttp/reverseproxy/httptransport.go +++ b/modules/caddyhttp/reverseproxy/httptransport.go @@ -15,8 +15,13 @@ package reverseproxy import ( + "crypto/tls" + "crypto/x509" + "encoding/base64" + "fmt" "net" "net/http" + "reflect" "time" "github.com/caddyserver/caddy/v2" @@ -76,7 +81,12 @@ func (h *HTTPTransport) Provision(ctx caddy.Context) error { if h.TLS != nil { rt.TLSHandshakeTimeout = time.Duration(h.TLS.HandshakeTimeout) - // TODO: rest of TLS config + + var err error + rt.TLSClientConfig, err = h.TLS.MakeTLSClientConfig() + if err != nil { + return fmt.Errorf("making TLS client config: %v", err) + } } if h.KeepAlive != nil { @@ -103,11 +113,77 @@ func (h HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) { return h.RoundTripper.RoundTrip(req) } +func defaultTLSConfig() *tls.Config { + return &tls.Config{ + NextProtos: []string{"h2", "http/1.1"}, // TODO: ensure this makes HTTP/2 work + } +} + type TLSConfig struct { - CAPool []string `json:"ca_pool,omitempty"` - ClientCertificate string `json:"client_certificate,omitempty"` - InsecureSkipVerify bool `json:"insecure_skip_verify,omitempty"` - HandshakeTimeout caddy.Duration `json:"handshake_timeout,omitempty"` + RootCAPool []string `json:"root_ca_pool,omitempty"` + // TODO: Should the client cert+key config use caddytls.CertificateLoader modules? + ClientCertificateFile string `json:"client_certificate_file,omitempty"` + ClientCertificateKeyFile string `json:"client_certificate_key_file,omitempty"` + InsecureSkipVerify bool `json:"insecure_skip_verify,omitempty"` + HandshakeTimeout caddy.Duration `json:"handshake_timeout,omitempty"` +} + +// MakeTLSClientConfig returns a tls.Config usable by a client to a backend. +// If there is no custom TLS configuration, a nil config may be returned. +func (t TLSConfig) MakeTLSClientConfig() (*tls.Config, error) { + cfg := new(tls.Config) + + // client auth + if t.ClientCertificateFile != "" && t.ClientCertificateKeyFile == "" { + return nil, fmt.Errorf("client_certificate_file specified without client_certificate_key_file") + } + if t.ClientCertificateFile == "" && t.ClientCertificateKeyFile != "" { + return nil, fmt.Errorf("client_certificate_key_file specified without client_certificate_file") + } + if t.ClientCertificateFile != "" && t.ClientCertificateKeyFile != "" { + cert, err := tls.LoadX509KeyPair(t.ClientCertificateFile, t.ClientCertificateKeyFile) + if err != nil { + return nil, fmt.Errorf("loading client certificate key pair: %v", err) + } + cfg.Certificates = []tls.Certificate{cert} + } + + // trusted root CAs + if len(t.RootCAPool) > 0 { + rootPool := x509.NewCertPool() + for _, encodedCACert := range t.RootCAPool { + caCert, err := decodeBase64DERCert(encodedCACert) + if err != nil { + return nil, fmt.Errorf("parsing CA certificate: %v", err) + } + rootPool.AddCert(caCert) + } + cfg.RootCAs = rootPool + } + + // throw all security out the window + cfg.InsecureSkipVerify = t.InsecureSkipVerify + + // only return a config if it's not empty + if reflect.DeepEqual(cfg, new(tls.Config)) { + return nil, nil + } + + cfg.NextProtos = []string{"h2", "http/1.1"} // TODO: ensure that this actually enables HTTP/2 + + return cfg, nil +} + +// decodeBase64DERCert base64-decodes, then DER-decodes, certStr. +func decodeBase64DERCert(certStr string) (*x509.Certificate, error) { + // decode base64 + derBytes, err := base64.StdEncoding.DecodeString(certStr) + if err != nil { + return nil, err + } + + // parse the DER-encoded certificate + return x509.ParseCertificate(derBytes) } type KeepAlive struct { -- cgit v1.2.3