From 9515878d7d0af4d88a8fb5a2631df8cbdc9f3007 Mon Sep 17 00:00:00 2001 From: Torben Schmitz Date: Tue, 10 Feb 2026 08:28:08 -0800 Subject: [PATCH] Add config option to use a custom mTLS client certificate PiperOrigin-RevId: 868159942 --- fleetspeak/src/client/comms.go | 1 + fleetspeak/src/client/comms/comms.go | 11 ++++++++--- fleetspeak/src/client/config/config.go | 20 +++++++++++++++++--- fleetspeak/src/client/https/https.go | 19 ++++++++++++------- 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/fleetspeak/src/client/comms.go b/fleetspeak/src/client/comms.go index 53913955..b60a89fc 100644 --- a/fleetspeak/src/client/comms.go +++ b/fleetspeak/src/client/comms.go @@ -163,6 +163,7 @@ func (c commsContext) ServerInfo() (comms.ServerInfo, error) { Servers: cfg.Servers, Proxy: cfg.Proxy, ClientCertificateHeader: cfg.ClientCertificateHeader, + GetAuthCertificate: cfg.GetAuthCertificate, ServerName: cfg.ServerName, }, nil } diff --git a/fleetspeak/src/client/comms/comms.go b/fleetspeak/src/client/comms/comms.go index f9f4ee55..f429923c 100644 --- a/fleetspeak/src/client/comms/comms.go +++ b/fleetspeak/src/client/comms/comms.go @@ -19,6 +19,7 @@ package comms import ( "context" "crypto" + "crypto/tls" "crypto/x509" "io" "net/url" @@ -74,14 +75,18 @@ type ServerInfo struct { // attempt to communicate with. Servers []string - // If non-nil, proxy used for connecting to the server. + // If set, proxy used for connecting to the server. // See https://golang.org/pkg/net/http/#Transport.Proxy for details. Proxy *url.URL - // If non-empty, populated with the header to use for the - // client certificate header + // If set, the communicator must include the client certificate in this header + // in all requests to the server. ClientCertificateHeader string + // If set, the communicator must use the certificate provided by this function + // for authentication, instead of the client certificate. + GetAuthCertificate func(*tls.CertificateRequestInfo) (*tls.Certificate, error) + // If set, used for SNI and certificate validation. ServerName string } diff --git a/fleetspeak/src/client/config/config.go b/fleetspeak/src/client/config/config.go index 3f80eea7..1747c55f 100644 --- a/fleetspeak/src/client/config/config.go +++ b/fleetspeak/src/client/config/config.go @@ -16,6 +16,7 @@ package config import ( + "crypto/tls" "crypto/x509" "net/url" @@ -78,10 +79,23 @@ type Configuration struct { // See https://golang.org/pkg/net/http/#Transport.Proxy for details. Proxy *url.URL - // If set, the server will validate the client certificate from the request header. - // This should be used if TLS is terminated at the load balancer and client certificates - // can be passed upstream to the fleetspeak server as an http header. + // If set, the communicator includes the client certificate in this header, + // which the server will use to identify the client. This should be used if + // TLS is terminated by e.g. a load balancer that can forward the certificate + // to the server as another header, so that the server can check the + // forwarded certificate against this header. + // If the proxy can not forward the certificate to the server, there is no + // guarantee that the certificate in this header is the actual client's + // certificate, making identity spoofing possible. ClientCertificateHeader string + + // If set, used instead of the client certificate for authenticating with a + // the server or a proxy. Requires ClientCertificateHeader to be set for the + // server to still be able to identify the client. + // Using this setting waives Fleetspeak's identity guarantees and offloads + // the identity verification to the system that provides and verifies the + // certificate. + GetAuthCertificate func(*tls.CertificateRequestInfo) (*tls.Certificate, error) } // PersistenceHandler manages client's configuration storage. diff --git a/fleetspeak/src/client/https/https.go b/fleetspeak/src/client/https/https.go index bb820555..364a972c 100644 --- a/fleetspeak/src/client/https/https.go +++ b/fleetspeak/src/client/https/https.go @@ -104,16 +104,21 @@ func makeTransport(cctx comms.Context, dc func(ctx context.Context, network, add nextProtos = []string{"h2", "http/1.1"} } + getClientCert := si.GetAuthCertificate + if getClientCert == nil { + getClientCert = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { + return &tls.Certificate{ + Certificate: [][]byte{certBytes}, + PrivateKey: ci.Private, + }, nil + } + } + tr := &http.Transport{ Proxy: proxy, TLSClientConfig: &tls.Config{ - RootCAs: si.TrustedCerts, - GetClientCertificate: func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { - return &tls.Certificate{ - Certificate: [][]byte{certBytes}, - PrivateKey: ci.Private, - }, nil - }, + RootCAs: si.TrustedCerts, + GetClientCertificate: getClientCert, CipherSuites: []uint16{ // We implement both endpoints, so we might as well require long keys and // perfect forward secrecy. Note that TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256