diff --git a/README.md b/README.md index fb159287..3b3ce7da 100644 --- a/README.md +++ b/README.md @@ -649,6 +649,7 @@ transport.tls.force = true transport.tls.certFile = "certificate.crt" transport.tls.keyFile = "certificate.key" transport.tls.trustedCaFile = "ca.crt" +transport.tls.clientCertSubjectRegex = "CN=client.com(.+)" ``` You will need **a root CA cert** and **at least one SSL/TLS certificate**. It **can** be self-signed or regular (such as Let's Encrypt or another SSL/TLS certificate provider). diff --git a/pkg/config/v1/common.go b/pkg/config/v1/common.go index 422a8082..65dc0ca1 100644 --- a/pkg/config/v1/common.go +++ b/pkg/config/v1/common.go @@ -80,6 +80,9 @@ type TLSConfig struct { // ServerName specifies the custom server name of tls certificate. By // default, server name if same to ServerAddr. ServerName string `json:"serverName,omitempty"` + // ClientCertificateSubjectRegex specifies the regex that is used to validate the client certificate subject. + // If it's not set, the validation of the subject is disabled. + ClientCertificateSubjectRegex string `json:"clientCertSubjectRegex,omitempty"` } type LogConfig struct { diff --git a/pkg/util/net/tls.go b/pkg/util/net/tls.go index 6645dfaf..33b416c1 100644 --- a/pkg/util/net/tls.go +++ b/pkg/util/net/tls.go @@ -17,7 +17,10 @@ package net import ( "crypto/tls" "fmt" + v1 "github.com/fatedier/frp/pkg/config/v1" + "github.com/fatedier/frp/pkg/util/log" "net" + "regexp" "time" libnet "github.com/fatedier/golib/net" @@ -55,3 +58,35 @@ func CheckAndEnableTLSServerConnWithTimeout( } return } + +func IsClientCertificateSubjectValid(c net.Conn, tlsConfig v1.TLSServerConfig) bool { + subjectRegex := tlsConfig.ClientCertificateSubjectRegex + regex, err := regexp.Compile(subjectRegex) + if err != nil { + log.Trace("Client certificate subject validation is disabled") + return true + } + + tlsConn, ok := c.(*tls.Conn) + if !ok { + log.Warn("Skip client certificate subject validation because its not a tls connection") + return true + } + + state := tlsConn.ConnectionState() + log.Trace("Validating client certificate subject using regex: %v", subjectRegex) + if len(state.PeerCertificates) == 0 { + log.Warn("No client certificates found in TLS connection, the verification was probably called to early.") + return false + } + + for _, v := range state.PeerCertificates { + subject := fmt.Sprintf("%v", v.Subject) + if !regex.MatchString(subject) { + log.Warn("Client certificate subject %v doesn't match regex %v", v.Subject, subjectRegex) + return false + } + log.Trace("Client certificate subject is valid") + } + return true +} diff --git a/server/service.go b/server/service.go index 2629b345..16a45c3b 100644 --- a/server/service.go +++ b/server/service.go @@ -484,6 +484,13 @@ func (svr *Service) HandleListener(l net.Listener) { session.Close() return } + + // Has to be called after session.AcceptStream() so that the client certificates are available + if !utilnet.IsClientCertificateSubjectValid(c, svr.cfg.Transport.TLS) { + session.Close() + return + } + go svr.handleConnection(ctx, stream) } } else {