From de1f4da126e5f092b9ded06e2947875975c7e85e Mon Sep 17 00:00:00 2001 From: yusing Date: Sun, 21 Sep 2025 10:47:37 +0800 Subject: [PATCH] feat(ReverseProxy): add SSL/TLS configuration options and build TLS config method --- internal/route/reverse_proxy.go | 9 +-- internal/route/types/http_config.go | 107 ++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 4 deletions(-) diff --git a/internal/route/reverse_proxy.go b/internal/route/reverse_proxy.go index 0ddb583a..67156a48 100755 --- a/internal/route/reverse_proxy.go +++ b/internal/route/reverse_proxy.go @@ -1,7 +1,6 @@ package route import ( - "crypto/tls" "net/http" "sync" @@ -44,10 +43,12 @@ func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, gperr.Error) { trans = a.Transport() proxyURL = nettypes.NewURL(agent.HTTPProxyURL) } else { - trans = gphttp.NewTransport() - if httpConfig.NoTLSVerify { - trans.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec + tlsConfig, err := httpConfig.BuildTLSConfig(base.ProxyURL) + if err != nil { + return nil, err } + + trans = gphttp.NewTransportWithTLSConfig(tlsConfig) if httpConfig.ResponseHeaderTimeout > 0 { trans.ResponseHeaderTimeout = httpConfig.ResponseHeaderTimeout } diff --git a/internal/route/types/http_config.go b/internal/route/types/http_config.go index aa077bc5..1bf866dc 100644 --- a/internal/route/types/http_config.go +++ b/internal/route/types/http_config.go @@ -1,11 +1,118 @@ package route import ( + "crypto/tls" + "crypto/x509" + "os" + "strings" "time" + + "github.com/yusing/go-proxy/internal/gperr" + nettypes "github.com/yusing/go-proxy/internal/net/types" ) type HTTPConfig struct { NoTLSVerify bool `json:"no_tls_verify,omitempty"` ResponseHeaderTimeout time.Duration `json:"response_header_timeout,omitempty" swaggertype:"primitive,integer"` DisableCompression bool `json:"disable_compression,omitempty"` + + // SSL/TLS proxy options (nginx-like) + SSLServerName *string `json:"ssl_server_name,omitempty"` // SNI server name + SSLTrustedCertificate string `json:"ssl_trusted_certificate,omitempty"` // Path to trusted CA certificates + SSLCertificate string `json:"ssl_certificate,omitempty"` // Path to client certificate + SSLCertificateKey string `json:"ssl_certificate_key,omitempty"` // Path to client certificate key + SSLProtocols []string `json:"ssl_protocols,omitempty"` // Allowed TLS protocols +} + +// BuildTLSConfig creates a TLS configuration based on the HTTP config options. +func (cfg *HTTPConfig) BuildTLSConfig(targetURL *nettypes.URL) (*tls.Config, gperr.Error) { + tlsConfig := &tls.Config{} + + // Handle InsecureSkipVerify (legacy NoTLSVerify option) + if cfg.NoTLSVerify { + tlsConfig.InsecureSkipVerify = true + } + + // Handle ssl_server_name (SNI) + if cfg.SSLServerName != nil { + switch *cfg.SSLServerName { + case "off": + // Disable SNI by setting empty string + tlsConfig.ServerName = "" + case "on", "": + // Use hostname from target URL for SNI + tlsConfig.ServerName = targetURL.Hostname() + default: + tlsConfig.ServerName = *cfg.SSLServerName + } + } else { + // Default behavior - use hostname for SNI + tlsConfig.ServerName = targetURL.Hostname() + } + + // Handle ssl_trusted_certificate + if cfg.SSLTrustedCertificate != "" { + caCertData, err := os.ReadFile(cfg.SSLTrustedCertificate) + if err != nil { + return nil, gperr.New("failed to read trusted certificate file"). + Subject(cfg.SSLTrustedCertificate). + With(err) + } + + caCertPool := x509.NewCertPool() + if !caCertPool.AppendCertsFromPEM(caCertData) { + return nil, gperr.New("failed to parse trusted certificates"). + Subject(cfg.SSLTrustedCertificate) + } + tlsConfig.RootCAs = caCertPool + } + + // Handle ssl_certificate and ssl_certificate_key (client certificates) + if cfg.SSLCertificate != "" { + if cfg.SSLCertificateKey == "" { + return nil, gperr.New("ssl_certificate_key is required when ssl_certificate is specified") + } + + clientCert, err := tls.LoadX509KeyPair(cfg.SSLCertificate, cfg.SSLCertificateKey) + if err != nil { + return nil, gperr.New("failed to load client certificate"). + Subject(cfg.SSLCertificate). + With(err) + } + tlsConfig.Certificates = []tls.Certificate{clientCert} + } + + // Handle ssl_protocols (TLS versions) + if len(cfg.SSLProtocols) > 0 { + var minVersion, maxVersion uint16 + + for _, protocol := range cfg.SSLProtocols { + var version uint16 + switch strings.ToLower(protocol) { + case "tlsv1.0": + version = tls.VersionTLS10 + case "tlsv1.1": + version = tls.VersionTLS11 + case "tlsv1.2": + version = tls.VersionTLS12 + case "tlsv1.3": + version = tls.VersionTLS13 + default: + return nil, gperr.New("unsupported TLS protocol"). + Subject(protocol) + } + + if minVersion == 0 || version < minVersion { + minVersion = version + } + if maxVersion == 0 || version > maxVersion { + maxVersion = version + } + } + + tlsConfig.MinVersion = minVersion + tlsConfig.MaxVersion = maxVersion + } + + return tlsConfig, nil }