[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!