proxy-pool/internal/controlplane/clienttransport/transport.go

97 lines
2.9 KiB
Go

// Package clienttransport builds the authenticated client side of the shared
// Controller gRPC endpoint for Gateway and Checker processes.
package clienttransport
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net"
"os"
"strconv"
"strings"
"proxy-pool/internal/config"
"proxy-pool/internal/controlplane/tlsreload"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)
var ErrInvalidTransport = errors.New("invalid control-plane client transport")
// New creates a TLS 1.3 client transport for the supplied client role. The
// caller supplies the role-specific certificate bundle, while the Controller
// validates its SPIFFE role/ID on the peer certificate.
func New(
configuration config.ControlPlane,
address string,
clientTLS config.ClientTLS,
override credentials.TransportCredentials,
role string,
) (credentials.TransportCredentials, error) {
if override != nil {
return override, nil
}
if !ValidDialAddress(address) || strings.TrimSpace(role) != role || role == "" {
return nil, ErrInvalidTransport
}
switch configuration.TLS.Mode {
case "disabled":
if !loopbackAddress(address) {
return nil, fmt.Errorf("%w: plaintext control-plane target must be loopback", ErrInvalidTransport)
}
return insecure.NewCredentials(), nil
case "mtls":
if clientTLS.CertFile == "" || clientTLS.KeyFile == "" || clientTLS.ServerCAFile == "" {
return nil, fmt.Errorf("%w: controlPlane.%s is required for mtls", ErrInvalidTransport, role)
}
certificate, err := tlsreload.NewCertificateProvider(clientTLS.CertFile, clientTLS.KeyFile)
if err != nil {
return nil, fmt.Errorf("load %s control-plane certificate: %w", role, err)
}
caPEM, err := os.ReadFile(clientTLS.ServerCAFile)
if err != nil {
return nil, fmt.Errorf("read %s control-plane CA: %w", role, err)
}
roots := x509.NewCertPool()
if !roots.AppendCertsFromPEM(caPEM) {
return nil, fmt.Errorf("parse %s control-plane CA", role)
}
host, _, _ := net.SplitHostPort(address)
return credentials.NewTLS(&tls.Config{
MinVersion: tls.VersionTLS13, GetClientCertificate: certificate.ClientCertificate,
RootCAs: roots, ServerName: strings.Trim(host, "[]"),
}), nil
default:
return nil, fmt.Errorf("%w: unsupported control-plane tls mode", ErrInvalidTransport)
}
}
func ValidDialAddress(address string) bool {
host, port, err := net.SplitHostPort(address)
if err != nil || host == "" {
return false
}
value, err := strconv.ParseUint(port, 10, 16)
if err != nil || value == 0 {
return false
}
parsed := net.ParseIP(strings.Trim(host, "[]"))
return parsed == nil || !parsed.IsUnspecified()
}
func loopbackAddress(address string) bool {
host, _, err := net.SplitHostPort(address)
if err != nil {
return false
}
host = strings.Trim(host, "[]")
if strings.EqualFold(host, "localhost") {
return true
}
ip := net.ParseIP(host)
return ip != nil && ip.IsLoopback()
}