package proxy import ( "crypto/tls" "fmt" "net" "git.secnex.io/secnex/masterlog" "git.secnex.io/secnex/pgproxy/config" "github.com/caddyserver/certmagic" ) // Proxy handles PostgreSQL connection proxying type Proxy struct { listener net.Listener config *config.Config mappings map[string]struct { host string port int } certmagic *certmagic.Config // For Let's Encrypt certificate management certificate *tls.Certificate // For regular TLS (non-Let's Encrypt) } // NewProxy creates a new proxy instance func NewProxy(config *config.Config) (*Proxy, error) { addr := fmt.Sprintf("%s:%d", config.Listen.Address, config.Listen.Port) var listener net.Listener var err error var certmagicConfig *certmagic.Config var certificate *tls.Certificate // Always create a TCP listener (not TLS listener) // We'll handle TLS in handleConnection to support both TLS and non-TLS connections tcpListener, err := net.Listen("tcp", addr) if err != nil { return nil, err } // If TLS is enabled, prepare certificates but don't wrap listener in TLS // This allows both TLS and non-TLS connections if config.TLS.Enabled { // Check if Let's Encrypt is enabled if config.TLS.LetsEncrypt.Enabled { // Setup Let's Encrypt certmagicConfig, err = getCertificateForMappingsWithLetsEncrypt(config) if err != nil { return nil, fmt.Errorf("failed to setup Let's Encrypt: %w", err) } masterlog.Info("TLS enabled with Let's Encrypt (optional)", map[string]interface{}{ "email": config.TLS.LetsEncrypt.Email, "staging": config.TLS.LetsEncrypt.Staging, "cacheDir": config.TLS.LetsEncrypt.CacheDir, }) } else { // Get certificate (load from files or generate self-signed) cert, err := getCertificateForMappings(config) if err != nil { return nil, fmt.Errorf("failed to get TLS certificate: %w", err) } certificate = &cert masterlog.Info("TLS enabled (optional)", map[string]interface{}{ "certFile": config.TLS.CertFile, "keyFile": config.TLS.KeyFile, }) } } listener = tcpListener // Build mappings map for quick lookup mappings := make(map[string]struct { host string port int }) for _, mapping := range config.Mappings { port := mapping.Port if port == 0 { port = 5432 // Default PostgreSQL port } mappings[mapping.External] = struct { host string port int }{ host: mapping.Internal, port: port, } } return &Proxy{ listener: listener, config: config, mappings: mappings, certmagic: certmagicConfig, certificate: certificate, }, nil } // Start starts the proxy server func (p *Proxy) Start() error { masterlog.Info("PostgreSQL proxy listening", map[string]interface{}{"address": p.config.Listen.Address, "port": p.config.Listen.Port}) for { conn, err := p.listener.Accept() if err != nil { masterlog.Error("Error accepting connection", map[string]interface{}{"error": err}) continue } go p.handleConnection(conn) } }