traefik/pkg/tcp/dialer.go
Douglas De Toni Machado 8392503df7
Add TCP Healthcheck
2025-10-22 11:42:05 +02:00

259 lines
7.6 KiB
Go

package tcp
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net"
"sync"
"time"
"github.com/pires/go-proxyproto"
"github.com/rs/zerolog/log"
"github.com/spiffe/go-spiffe/v2/bundle/x509bundle"
"github.com/spiffe/go-spiffe/v2/spiffeid"
"github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
"github.com/spiffe/go-spiffe/v2/svid/x509svid"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
traefiktls "github.com/traefik/traefik/v3/pkg/tls"
"github.com/traefik/traefik/v3/pkg/types"
)
// ClientConn is the interface that provides information about the client connection.
type ClientConn interface {
// LocalAddr returns the local network address, if known.
LocalAddr() net.Addr
// RemoteAddr returns the remote network address, if known.
RemoteAddr() net.Addr
}
// Dialer is an interface to dial a network connection, with support for PROXY protocol and termination delay.
type Dialer interface {
Dial(network, addr string, clientConn ClientConn) (c net.Conn, err error)
DialContext(ctx context.Context, network, addr string, clientConn ClientConn) (c net.Conn, err error)
TerminationDelay() time.Duration
}
type tcpDialer struct {
dialer *net.Dialer
terminationDelay time.Duration
proxyProtocol *dynamic.ProxyProtocol
}
// TerminationDelay returns the termination delay duration.
func (d tcpDialer) TerminationDelay() time.Duration {
return d.terminationDelay
}
// Dial dials a network connection and optionally sends a PROXY protocol header.
func (d tcpDialer) Dial(network, addr string, clientConn ClientConn) (net.Conn, error) {
return d.DialContext(context.Background(), network, addr, clientConn)
}
// DialContext dials a network connection and optionally sends a PROXY protocol header, with context.
func (d tcpDialer) DialContext(ctx context.Context, network, addr string, clientConn ClientConn) (net.Conn, error) {
conn, err := d.dialer.DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
if d.proxyProtocol != nil && clientConn != nil && d.proxyProtocol.Version > 0 && d.proxyProtocol.Version < 3 {
header := proxyproto.HeaderProxyFromAddrs(byte(d.proxyProtocol.Version), clientConn.RemoteAddr(), clientConn.LocalAddr())
if _, err := header.WriteTo(conn); err != nil {
_ = conn.Close()
return nil, fmt.Errorf("writing PROXY Protocol header: %w", err)
}
}
return conn, nil
}
type tcpTLSDialer struct {
tcpDialer
tlsConfig *tls.Config
}
// Dial dials a network connection with the wrapped tcpDialer and performs a TLS handshake.
func (d tcpTLSDialer) Dial(network, addr string, clientConn ClientConn) (net.Conn, error) {
return d.DialContext(context.Background(), network, addr, clientConn)
}
// DialContext dials a network connection with the wrapped tcpDialer and performs a TLS handshake, with context.
func (d tcpTLSDialer) DialContext(ctx context.Context, network, addr string, clientConn ClientConn) (net.Conn, error) {
conn, err := d.tcpDialer.DialContext(ctx, network, addr, clientConn)
if err != nil {
return nil, err
}
// Now perform TLS handshake on the connection
tlsConn := tls.Client(conn, d.tlsConfig)
if err := tlsConn.Handshake(); err != nil {
_ = conn.Close()
return nil, fmt.Errorf("TLS handshake failed: %w", err)
}
return tlsConn, nil
}
// SpiffeX509Source allows retrieving a x509 SVID and bundle.
type SpiffeX509Source interface {
x509svid.Source
x509bundle.Source
}
// DialerManager handles dialer for the reverse proxy.
type DialerManager struct {
serversTransportsMu sync.RWMutex
serversTransports map[string]*dynamic.TCPServersTransport
spiffeX509Source SpiffeX509Source
}
// NewDialerManager creates a new DialerManager.
func NewDialerManager(spiffeX509Source SpiffeX509Source) *DialerManager {
return &DialerManager{
serversTransports: make(map[string]*dynamic.TCPServersTransport),
spiffeX509Source: spiffeX509Source,
}
}
// Update updates the TCP serversTransport configurations.
func (d *DialerManager) Update(configs map[string]*dynamic.TCPServersTransport) {
d.serversTransportsMu.Lock()
defer d.serversTransportsMu.Unlock()
d.serversTransports = configs
}
// Build builds a dialer by name.
func (d *DialerManager) Build(config *dynamic.TCPServersLoadBalancer, isTLS bool) (Dialer, error) {
name := "default@internal"
if config.ServersTransport != "" {
name = config.ServersTransport
}
var st *dynamic.TCPServersTransport
d.serversTransportsMu.RLock()
st, ok := d.serversTransports[name]
d.serversTransportsMu.RUnlock()
if !ok || st == nil {
return nil, fmt.Errorf("no transport configuration found for %q", name)
}
// Handle TerminationDelay and ProxyProtocol deprecated options.
var terminationDelay ptypes.Duration
if config.TerminationDelay != nil {
terminationDelay = ptypes.Duration(*config.TerminationDelay)
}
proxyProtocol := config.ProxyProtocol
if config.ServersTransport != "" {
terminationDelay = st.TerminationDelay
proxyProtocol = st.ProxyProtocol
}
if proxyProtocol != nil && (proxyProtocol.Version < 1 || proxyProtocol.Version > 2) {
return nil, fmt.Errorf("unknown proxyProtocol version: %d", proxyProtocol.Version)
}
var tlsConfig *tls.Config
if st.TLS != nil {
if st.TLS.Spiffe != nil {
if d.spiffeX509Source == nil {
return nil, errors.New("SPIFFE is enabled for this transport, but not configured")
}
authorizer, err := buildSpiffeAuthorizer(st.TLS.Spiffe)
if err != nil {
return nil, fmt.Errorf("unable to build SPIFFE authorizer: %w", err)
}
tlsConfig = tlsconfig.MTLSClientConfig(d.spiffeX509Source, d.spiffeX509Source, authorizer)
}
if st.TLS.InsecureSkipVerify || len(st.TLS.RootCAs) > 0 || len(st.TLS.ServerName) > 0 || len(st.TLS.Certificates) > 0 || st.TLS.PeerCertURI != "" {
if tlsConfig != nil {
return nil, errors.New("TLS and SPIFFE configuration cannot be defined at the same time")
}
tlsConfig = &tls.Config{
ServerName: st.TLS.ServerName,
InsecureSkipVerify: st.TLS.InsecureSkipVerify,
RootCAs: createRootCACertPool(st.TLS.RootCAs),
Certificates: st.TLS.Certificates.GetCertificates(),
}
if st.TLS.PeerCertURI != "" {
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
return traefiktls.VerifyPeerCertificate(st.TLS.PeerCertURI, tlsConfig, rawCerts)
}
}
}
}
dialer := tcpDialer{
dialer: &net.Dialer{
Timeout: time.Duration(st.DialTimeout),
KeepAlive: time.Duration(st.DialKeepAlive),
},
terminationDelay: time.Duration(terminationDelay),
proxyProtocol: proxyProtocol,
}
if !isTLS {
return dialer, nil
}
return tcpTLSDialer{dialer, tlsConfig}, nil
}
func createRootCACertPool(rootCAs []types.FileOrContent) *x509.CertPool {
if len(rootCAs) == 0 {
return nil
}
roots := x509.NewCertPool()
for _, cert := range rootCAs {
certContent, err := cert.Read()
if err != nil {
log.Err(err).Msg("Error while read RootCAs")
continue
}
roots.AppendCertsFromPEM(certContent)
}
return roots
}
func buildSpiffeAuthorizer(cfg *dynamic.Spiffe) (tlsconfig.Authorizer, error) {
switch {
case len(cfg.IDs) > 0:
spiffeIDs := make([]spiffeid.ID, 0, len(cfg.IDs))
for _, rawID := range cfg.IDs {
id, err := spiffeid.FromString(rawID)
if err != nil {
return nil, fmt.Errorf("invalid SPIFFE ID: %w", err)
}
spiffeIDs = append(spiffeIDs, id)
}
return tlsconfig.AuthorizeOneOf(spiffeIDs...), nil
case cfg.TrustDomain != "":
trustDomain, err := spiffeid.TrustDomainFromString(cfg.TrustDomain)
if err != nil {
return nil, fmt.Errorf("invalid SPIFFE trust domain: %w", err)
}
return tlsconfig.AuthorizeMemberOf(trustDomain), nil
default:
return tlsconfig.AuthorizeAny(), nil
}
}