95 lines
3.0 KiB
Go
95 lines
3.0 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"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"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)
|
|
}
|
|
trust, err := tlsreload.NewTrustProvider(clientTLS.ServerCAFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load %s control-plane CA: %w", role, err)
|
|
}
|
|
host, _, _ := net.SplitHostPort(address)
|
|
serverName := strings.Trim(host, "[]")
|
|
return credentials.NewTLS(&tls.Config{
|
|
MinVersion: tls.VersionTLS13, GetClientCertificate: certificate.ClientCertificate,
|
|
ServerName: serverName,
|
|
// VerifyConnection retains standard verification semantics while allowing
|
|
// the CA bundle to reload for the next control-plane handshake.
|
|
InsecureSkipVerify: true, VerifyConnection: trust.VerifyServer(serverName),
|
|
}), 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()
|
|
}
|