From aa5ede6fdc9c2c6b36348223b3e265b6decd50c3 Mon Sep 17 00:00:00 2001 From: Garet Halliday <me@garet.holiday> Date: Fri, 29 Sep 2023 17:42:15 -0500 Subject: [PATCH] pgbouncer almost working --- cmd/caddygat/pgbouncer.go | 68 +++++++++++++++++++++ lib/gat/gatcaddyfile/ssl.go | 16 +++++ lib/gat/handlers/pgbouncer/config.go | 55 +++++++++++++++++ lib/gat/handlers/pgbouncer/module.go | 9 ++- lib/gat/ssl/servers/x509_key_pair/server.go | 47 ++++++++++++++ lib/gat/standard/standard.go | 1 + 6 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 cmd/caddygat/pgbouncer.go create mode 100644 lib/gat/ssl/servers/x509_key_pair/server.go diff --git a/cmd/caddygat/pgbouncer.go b/cmd/caddygat/pgbouncer.go new file mode 100644 index 00000000..e923db80 --- /dev/null +++ b/cmd/caddygat/pgbouncer.go @@ -0,0 +1,68 @@ +package main + +import ( + "errors" + "time" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + caddycmd "github.com/caddyserver/caddy/v2/cmd" + + "gfx.cafe/gfx/pggat/lib/gat" + "gfx.cafe/gfx/pggat/lib/gat/handlers/pgbouncer" + "gfx.cafe/gfx/pggat/lib/util/dur" +) + +func init() { + caddycmd.RegisterCommand(caddycmd.Command{ + Name: "pgbouncer", + Usage: "<config file>", + Short: "Runs in pgbouncer compatibility mode", + Func: func(flags caddycmd.Flags) (int, error) { + return runPgBouncer(flags) + }, + }) +} + +func runPgBouncer(flags caddycmd.Flags) (int, error) { + caddy.TrapSignals() + + file := flags.Arg(0) + if file == "" { + return caddy.ExitCodeFailedStartup, errors.New("usage: pgbouncer <config file>") + } + + config, err := pgbouncer.Load(file) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + var pggat gat.Config + pggat.StatLogPeriod = dur.Duration(time.Second) + + var server gat.ServerConfig + server.Listen = config.Listen() + server.Routes = append(server.Routes, gat.RouteConfig{ + Handle: caddyconfig.JSONModuleObject( + pgbouncer.Module{ + Config: config, + }, + "handler", + "pgbouncer", + nil, + ), + }) + pggat.Servers = append(pggat.Servers, server) + + caddyConfig := caddy.Config{ + AppsRaw: caddy.ModuleMap{ + "pggat": caddyconfig.JSON(pggat, nil), + }, + } + + if err = caddy.Run(&caddyConfig); err != nil { + return caddy.ExitCodeFailedStartup, err + } + + select {} +} diff --git a/lib/gat/gatcaddyfile/ssl.go b/lib/gat/gatcaddyfile/ssl.go index 798d6bc9..a4ae1063 100644 --- a/lib/gat/gatcaddyfile/ssl.go +++ b/lib/gat/gatcaddyfile/ssl.go @@ -7,12 +7,28 @@ import ( "gfx.cafe/gfx/pggat/lib/gat/ssl/clients/insecure_skip_verify" "gfx.cafe/gfx/pggat/lib/gat/ssl/servers/self_signed" + "gfx.cafe/gfx/pggat/lib/gat/ssl/servers/x509_key_pair" ) func init() { RegisterDirective(SSLServer, "self_signed", func(_ *caddyfile.Dispenser, _ *[]caddyconfig.Warning) (caddy.Module, error) { return &self_signed.Server{}, nil }) + RegisterDirective(SSLServer, "x509_key_pair", func(d *caddyfile.Dispenser, _ *[]caddyconfig.Warning) (caddy.Module, error) { + var module x509_key_pair.Server + + if !d.NextArg() { + return nil, d.ArgErr() + } + module.CertFile = d.Val() + + if !d.NextArg() { + return nil, d.ArgErr() + } + module.KeyFile = d.Val() + + return &module, nil + }) RegisterDirective(SSLClient, "insecure_skip_verify", func(_ *caddyfile.Dispenser, _ *[]caddyconfig.Warning) (caddy.Module, error) { return &insecure_skip_verify.Client{}, nil diff --git a/lib/gat/handlers/pgbouncer/config.go b/lib/gat/handlers/pgbouncer/config.go index 1ce4b8ed..6f3b179a 100644 --- a/lib/gat/handlers/pgbouncer/config.go +++ b/lib/gat/handlers/pgbouncer/config.go @@ -1,7 +1,16 @@ package pgbouncer import ( + "encoding/json" + "net" + "strconv" + "strings" + + "github.com/caddyserver/caddy/v2/caddyconfig" + "gfx.cafe/gfx/pggat/lib/bouncer" + "gfx.cafe/gfx/pggat/lib/gat" + "gfx.cafe/gfx/pggat/lib/gat/ssl/servers/x509_key_pair" "gfx.cafe/gfx/pggat/lib/util/encoding/ini" "gfx.cafe/gfx/pggat/lib/util/strutil" ) @@ -239,3 +248,49 @@ func Load(config string) (Config, error) { err = ini.Unmarshal(conf, &c) return c, err } + +func (T Config) Listen() []gat.ListenerConfig { + var ssl json.RawMessage + if T.PgBouncer.ClientTLSCertFile != "" && T.PgBouncer.ClientTLSKeyFile != "" { + ssl = caddyconfig.JSONModuleObject( + x509_key_pair.Server{ + CertFile: T.PgBouncer.ClientTLSCertFile, + KeyFile: T.PgBouncer.ClientTLSKeyFile, + }, + "provider", + "x509_key_pair", + nil, + ) + } + + var listeners []gat.ListenerConfig + + if T.PgBouncer.ListenAddr != "" { + listenAddr := T.PgBouncer.ListenAddr + if listenAddr == "*" { + listenAddr = "" + } + + listen := net.JoinHostPort(listenAddr, strconv.Itoa(T.PgBouncer.ListenPort)) + + listeners = append(listeners, gat.ListenerConfig{ + Address: listen, + }) + } + + // listen on unix socket + dir := T.PgBouncer.UnixSocketDir + port := T.PgBouncer.ListenPort + + if !strings.HasSuffix(dir, "/") { + dir = dir + "/" + } + dir = dir + ".s.PGSQL." + strconv.Itoa(port) + + listeners = append(listeners, gat.ListenerConfig{ + Address: dir, + SSL: ssl, + }) + + return listeners +} diff --git a/lib/gat/handlers/pgbouncer/module.go b/lib/gat/handlers/pgbouncer/module.go index 93a582c9..2792dd5a 100644 --- a/lib/gat/handlers/pgbouncer/module.go +++ b/lib/gat/handlers/pgbouncer/module.go @@ -60,9 +60,12 @@ func (*Module) CaddyModule() caddy.ModuleInfo { func (T *Module) Provision(ctx caddy.Context) error { T.log = ctx.Logger() - var err error - T.Config, err = Load(T.ConfigFile) - return err + if T.ConfigFile != "" { + var err error + T.Config, err = Load(T.ConfigFile) + return err + } + return nil } func (T *Module) Cleanup() error { diff --git a/lib/gat/ssl/servers/x509_key_pair/server.go b/lib/gat/ssl/servers/x509_key_pair/server.go new file mode 100644 index 00000000..905e5d99 --- /dev/null +++ b/lib/gat/ssl/servers/x509_key_pair/server.go @@ -0,0 +1,47 @@ +package x509_key_pair + +import ( + "crypto/tls" + + "github.com/caddyserver/caddy/v2" + + "gfx.cafe/gfx/pggat/lib/gat" +) + +type Server struct { + CertFile string `json:"cert_file"` + KeyFile string `json:"key_file"` + + tlsConfig *tls.Config +} + +func (T *Server) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.ssl.servers.x509_key_pair", + New: func() caddy.Module { + return new(Server) + }, + } +} + +func (T *Server) Provision(ctx caddy.Context) error { + cert, err := tls.LoadX509KeyPair(T.CertFile, T.KeyFile) + if err != nil { + return err + } + + T.tlsConfig = &tls.Config{ + Certificates: []tls.Certificate{ + cert, + }, + } + return nil +} + +func (T *Server) ServerTLSConfig() *tls.Config { + return T.tlsConfig +} + +var _ gat.SSLServer = (*Server)(nil) +var _ caddy.Module = (*Server)(nil) +var _ caddy.Provisioner = (*Server)(nil) diff --git a/lib/gat/standard/standard.go b/lib/gat/standard/standard.go index bbe9dfe7..66e343f9 100644 --- a/lib/gat/standard/standard.go +++ b/lib/gat/standard/standard.go @@ -9,6 +9,7 @@ import ( // ssl servers _ "gfx.cafe/gfx/pggat/lib/gat/ssl/servers/self_signed" + _ "gfx.cafe/gfx/pggat/lib/gat/ssl/servers/x509_key_pair" // ssl clients _ "gfx.cafe/gfx/pggat/lib/gat/ssl/clients/insecure_skip_verify" -- GitLab