diff --git a/caddyhttp/proxy/reverseproxy.go b/caddyhttp/proxy/reverseproxy.go index ac0e3e1e..bb7fd4b1 100644 --- a/caddyhttp/proxy/reverseproxy.go +++ b/caddyhttp/proxy/reverseproxy.go @@ -337,6 +337,26 @@ func (rp *ReverseProxy) UseOwnCACertificates(CaCertPool *x509.CertPool) { } } +// UseClientCertificates is used to facilitate HTTPS proxying +// with locally provided certificate. +func (rp *ReverseProxy) UseClientCertificates(keyPair *tls.Certificate) { + if transport, ok := rp.Transport.(*http.Transport); ok { + if transport.TLSClientConfig == nil { + transport.TLSClientConfig = &tls.Config{} + } + transport.TLSClientConfig.Certificates = []tls.Certificate{ *keyPair } + // No http2.ConfigureTransport() here. + // For now this is only added in places where + // an http.Transport is actually created. + } else if transport, ok := rp.Transport.(*http3.RoundTripper); ok { + if transport.TLSClientConfig == nil { + transport.TLSClientConfig = &tls.Config{} + } + transport.TLSClientConfig.Certificates = []tls.Certificate{ *keyPair } + } +} + + // ServeHTTP serves the proxied request to the upstream by performing a roundtrip. // It is designed to handle websocket connection upgrades as well. func (rp *ReverseProxy) ServeHTTP(rw http.ResponseWriter, outreq *http.Request, respUpdateFn respUpdateFn) error { diff --git a/caddyhttp/proxy/upstream.go b/caddyhttp/proxy/upstream.go index 8c186dcb..90ec9f0e 100644 --- a/caddyhttp/proxy/upstream.go +++ b/caddyhttp/proxy/upstream.go @@ -76,6 +76,7 @@ type staticUpstream struct { CaCertPool *x509.CertPool upstreamHeaderReplacements headerReplacements downstreamHeaderReplacements headerReplacements + ClientKeyPair *tls.Certificate } type srvResolver interface { @@ -267,6 +268,10 @@ func (u *staticUpstream) NewHost(host string) (*UpstreamHost, error) { uh.ReverseProxy.UseOwnCACertificates(u.CaCertPool) } + if u.ClientKeyPair != nil { + uh.ReverseProxy.UseClientCertificates(u.ClientKeyPair) + } + return uh, nil } @@ -564,6 +569,20 @@ func parseBlock(c *caddyfile.Dispenser, u *staticUpstream, hasSrv bool) error { return c.Errf("unable to parse timeout duration '%s'", c.Val()) } u.Timeout = dur + case "tls_client": + if !c.NextArg() { + return c.ArgErr() + } + clientCertFile := c.Val() + if !c.NextArg() { + return c.ArgErr() + } + clientKeyFile := c.Val() + clientKeyPair, err := tls.LoadX509KeyPair(clientCertFile, clientKeyFile) + if (err != nil) { + return c.Errf("unable to load keypair from certfile:%s keyfile:%s", clientCertFile, clientKeyFile) + } + u.ClientKeyPair = &clientKeyPair default: return c.Errf("unknown property '%s'", c.Val()) }