~emersion/public-inbox

tlstunnel: Add client_auth directive v1 APPLIED

Tom Lebreux: 1
 Add client_auth directive

 3 files changed, 57 insertions(+), 3 deletions(-)
#1210200 .build.yml success
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~emersion/public-inbox/patches/51517/mbox | git am -3
Learn more about email & git

[PATCH tlstunnel] Add client_auth directive Export this patch

---
As mentioned in IRC, I'm making use of client certificates to
authenticate an application that doesn't have any kind of authentication
built-in. This works great.

Here's an example where I'm requiring the certificate (meaning the
client MUST send one) and I'm also verifying it against the provided
cert (cert2.pem).

	frontend localhost:5443 {
		backend localhost:8000
		protocol http/1.1
		tls {
			load cert.pem key.pem
			client_auth require_and_verify cert2.pem
		}
	}

I figured I'd support all possible types Go supports (request, require,
verify, request and verify). If someone doesn't want to use client_auth,
then they shouldn't specify it in the config. It's optional.

I have tested and reloading works for both changing the type AND
changing the certificate.

 config.go       | 39 ++++++++++++++++++++++++++++++++++++++-
 server.go       | 11 +++++++++--
 tlstunnel.1.scd | 10 ++++++++++
 3 files changed, 57 insertions(+), 3 deletions(-)

diff --git a/config.go b/config.go
index 00e8d6ea24e6..2decb9c1805a 100644
--- a/config.go
+++ b/config.go
@@ -39,7 +39,8 @@ type frontendConfig struct {
	} `scfg:"listen"`
	Backend *backendConfig `scfg:"backend"`
	TLS     struct {
		Load *[2]string `scfg:"load"`
		Load       *[2]string `scfg:"load"`
		ClientAuth *[2]string `scfg:"client_auth"`
	} `scfg:"tls"`
	Protocol []string `scfg:"protocol"`
}
@@ -129,6 +130,25 @@ func parseFrontend(srv *Server, cfg *frontendConfig) error {
		srv.UnmanagedCerts = append(srv.UnmanagedCerts, cert)
		unmanaged = true
	}
	if cfg.TLS.ClientAuth != nil {
		clientAuth, err := parseClientAuth(cfg.TLS.ClientAuth[0])
		if err != nil {
			return fmt.Errorf(`directive "tls.client_auth": %w`, err)
		}

		clientCAs, err := os.ReadFile(cfg.TLS.ClientAuth[1])
		if err != nil {
			return fmt.Errorf(`directive "tls.client_auth": %w`, err)
		}

		pool := x509.NewCertPool()
		if ok := pool.AppendCertsFromPEM(clientCAs); !ok {
			return fmt.Errorf("failed to append to client pool")
		}

		frontend.ClientAuth = clientAuth
		frontend.ClientCAs = pool
	}

	frontend.Protocols = cfg.Protocol

@@ -292,3 +312,20 @@ func parseTLSOnDemand(srv *Server, cfg *tlsOnDemandConfig) error {

	return nil
}

func parseClientAuth(clientAuth string) (tls.ClientAuthType, error) {
	var auth tls.ClientAuthType
	switch clientAuth {
	case "request":
		auth = tls.RequestClientCert
	case "require":
		auth = tls.RequireAnyClientCert
	case "verify":
		auth = tls.RequireAnyClientCert
	case "require_and_verify":
		auth = tls.RequireAndVerifyClientCert
	default:
		return auth, fmt.Errorf("unknown client auth %s", clientAuth)
	}
	return auth, nil
}
diff --git a/server.go b/server.go
index f4411961aa18..7f5d7e2e630c 100644
--- a/server.go
+++ b/server.go
@@ -3,6 +3,7 @@ package tlstunnel
import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"io"
@@ -312,6 +313,10 @@ func (ln *Listener) handle(conn net.Conn) error {
		}

		tlsConfig.NextProtos = append(tlsConfig.NextProtos, fe.Protocols...)
		if fe.ClientAuth != tls.NoClientCert {
			tlsConfig.ClientAuth = fe.ClientAuth
			tlsConfig.ClientCAs = fe.ClientCAs
		}
		return tlsConfig, nil
	}
	tlsConn := tls.Server(conn, tlsConfig)
@@ -362,8 +367,10 @@ func (ln *Listener) matchFrontend(serverName string) (*Frontend, error) {
}

type Frontend struct {
	Backend   Backend
	Protocols []string
	Backend    Backend
	Protocols  []string
	ClientAuth tls.ClientAuthType
	ClientCAs  *x509.CertPool
}

func (fe *Frontend) handle(downstream net.Conn, tlsState *tls.ConnectionState) error {
diff --git a/tlstunnel.1.scd b/tlstunnel.1.scd
index e8d3527417de..465ffceb33aa 100644
--- a/tlstunnel.1.scd
+++ b/tlstunnel.1.scd
@@ -93,6 +93,16 @@ The following directives are supported:

			This disables automatic TLS.

		*client_auth* <type> <cert>
			Configures client authentication.

			The available verification types are the following:
			request, require, verify, require_and_verify. They are
			defined here:
			https://pkg.go.dev/crypto/tls#ClientAuthType.

			The certificate must be a PEM file.

	*protocol* <name>...
		List of supported application-layer protocols.

-- 
2.44.0
tlstunnel/patches/.build.yml: SUCCESS in 28s

[Add client_auth directive][0] from [Tom Lebreux][1]

[0]: https://lists.sr.ht/~emersion/public-inbox/patches/51517
[1]: mailto:me@tomlebreux.com

✓ #1210200 SUCCESS tlstunnel/patches/.build.yml https://builds.sr.ht/~emersion/job/1210200
Pushed, thanks!