From 4a1e1649bc985e9658d326ed433a101d7d79ae30 Mon Sep 17 00:00:00 2001
From: Matthew Holt <mholt@users.noreply.github.com>
Date: Tue, 3 Sep 2019 15:26:09 -0600
Subject: [PATCH] reverse_proxy: Implement remaining TLS config for proxy to
 backend

---
 .../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 36dd77676..999a35242 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 {