diff --git a/config.go b/config.go index 00e8d6e..2decb9c 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 f441196..7f5d7e2 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 e8d3527..3e668ac 100644 --- a/tlstunnel.1.scd +++ b/tlstunnel.1.scd @@ -93,6 +93,17 @@ The following directives are supported: This disables automatic TLS. + *client_auth* + 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 and is used as a CA to validate + client certificates. + *protocol* ... List of supported application-layer protocols.