diff --git a/cmd/caddygat/main.go b/cmd/caddygat/main.go new file mode 100644 index 0000000000000000000000000000000000000000..b4652df67cc53102e76d50d9ba733559bc2260ed --- /dev/null +++ b/cmd/caddygat/main.go @@ -0,0 +1,13 @@ +package main + +import ( + caddycmd "github.com/caddyserver/caddy/v2/cmd" + + _ "gfx.cafe/gfx/pggat/lib/gat/gatcaddyfile" + + _ "gfx.cafe/gfx/pggat/lib/gat/standard" +) + +func main() { + caddycmd.Main() +} diff --git a/cmd/caddygat/pgbouncer.go b/cmd/caddygat/pgbouncer.go new file mode 100644 index 0000000000000000000000000000000000000000..4fe2f957a4c5b74a0040857b730b2c0720245d11 --- /dev/null +++ b/cmd/caddygat/pgbouncer.go @@ -0,0 +1,66 @@ +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: runPgbouncer, + }) +} + +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{ + ConfigFile: file, + }, + "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/cmd/cgat/main.go b/cmd/cgat/main.go deleted file mode 100644 index 3963519cda736d7e97a9417e97d71e339dc6b256..0000000000000000000000000000000000000000 --- a/cmd/cgat/main.go +++ /dev/null @@ -1,108 +0,0 @@ -package main - -import ( - "errors" - "net/http" - _ "net/http/pprof" - "os" - "time" - - "tuxpa.in/a/zlog/log" - - "gfx.cafe/gfx/pggat/lib/gat" - "gfx.cafe/gfx/pggat/lib/gat/metrics" - "gfx.cafe/gfx/pggat/lib/gat/modules/cloud_sql_discovery" - "gfx.cafe/gfx/pggat/lib/gat/modules/digitalocean_discovery" - "gfx.cafe/gfx/pggat/lib/gat/modules/pgbouncer" - "gfx.cafe/gfx/pggat/lib/gat/modules/ssl_endpoint" - "gfx.cafe/gfx/pggat/lib/gat/modules/zalando" - "gfx.cafe/gfx/pggat/lib/gat/modules/zalando_operator_discovery" -) - -func loadModule(mode string) (gat.Module, error) { - switch mode { - case "pggat": - conf, err := pgbouncer.Load(os.Args[1]) - if err != nil { - return nil, err - } - return pgbouncer.NewModule(conf) - case "pgbouncer": - conf, err := pgbouncer.Load(os.Args[1]) - if err != nil { - return nil, err - } - return pgbouncer.NewModule(conf) - case "pgbouncer_spilo": - conf, err := zalando.Load() - if err != nil { - return nil, err - } - return zalando.NewModule(conf) - case "zalando_kubernetes_operator": - conf, err := zalando_operator_discovery.Load() - if err != nil { - return nil, err - } - return zalando_operator_discovery.NewModule(conf) - case "google_cloud_sql": - conf, err := cloud_sql_discovery.Load() - if err != nil { - return nil, err - } - return cloud_sql_discovery.NewModule(conf) - case "digitalocean_databases": - conf, err := digitalocean_discovery.Load() - if err != nil { - return nil, err - } - return digitalocean_discovery.NewModule(conf) - default: - return nil, errors.New("Unknown PGGAT_RUN_MODE: " + mode) - } -} - -func main() { - go func() { - panic(http.ListenAndServe(":8080", nil)) - }() - - runMode := os.Getenv("PGGAT_RUN_MODE") - if runMode == "" { - runMode = "pgbouncer" - } - - log.Printf("Starting pggat (%s)...", runMode) - - var server gat.Server - - // load and add main module - module, err := loadModule(runMode) - if err != nil { - panic(err) - } - server.AddModule(module) - - // back up ssl endpoint (for modules that don't have endpoints by default such as discovery) - ep, err := ssl_endpoint.NewModule() - if err != nil { - panic(err) - } - server.AddModule(ep) - - go func() { - var m metrics.Server - for { - time.Sleep(1 * time.Minute) - server.ReadMetrics(&m) - log.Printf("%s", m.String()) - m.Clear() - } - }() - - err = server.ListenAndServe() - if err != nil { - panic(err) - } - return -} diff --git a/entrypoint.sh b/entrypoint.sh index 61f26ffc99437cabcc2421fb715dfa125df0f63e..d0263cf346bc950b7e09a98665ac8e4b1b06717f 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -3,18 +3,4 @@ set -e EXEC_BIN_PATH=${PGGAT_BIN_PATH:=/usr/bin/pggat} -pggat() { - exec $EXEC_BIN_PATH ${@} -} - - -case "${1}" in - "") - pggat ${@} - ;; - *) - export PGGAT_RUN_MODE=${1} - shift - pggat ${@} - ;; -esac +$EXEC_BIN_PATH run --adapter="caddyfile" --config="/presets/${PGGAT_RUN_MODE:=default}.Caddyfile" diff --git a/examples/manual_routing.Caddyfile b/examples/manual_routing.Caddyfile new file mode 100644 index 0000000000000000000000000000000000000000..5ebad0b36394483a3195efe5185bb1c12fd064a6 --- /dev/null +++ b/examples/manual_routing.Caddyfile @@ -0,0 +1,42 @@ +{ + stat_log_period 1s +} + +:6432 { + ssl self_signed + require_ssl + + @bob { + user bob + } + password @bob password123 + + @jeff { + user jeff + } + password @jeff password456 + + @other { + not or { + user jeff + user bob + } + } + + error @other "you are not bob or jeff" + + pool /bobland localhost:5432 bobland postgres password + pool /jeffland { + pooler session + + address localhost:5432 + ssl require insecure_skip_verify + + database jeffland + username postgres + password password + + parameter TimeZone=America/Chicago + parameter myParameter=FooBarBaz + } +} diff --git a/go.mod b/go.mod index f0ca51a41eda75669c74dcaf7e93c71ada04ce1b..706ee3ef0a02205ea95d65c8de93efd4799b8496 100644 --- a/go.mod +++ b/go.mod @@ -4,64 +4,90 @@ go 1.20 require ( gfx.cafe/ghalliday1/scram v0.0.2 - gfx.cafe/util/go/gun v0.0.0-20230721185457-c559e86c829c + github.com/caddyserver/caddy/v2 v2.7.4 github.com/digitalocean/godo v1.102.1 github.com/google/uuid v1.3.0 github.com/zalando/postgres-operator v1.8.2 - google.golang.org/api v0.30.0 + go.uber.org/zap v1.25.0 + google.golang.org/api v0.126.0 k8s.io/apimachinery v0.27.4 k8s.io/client-go v0.27.4 - tuxpa.in/a/zlog v1.61.0 ) require ( - cloud.google.com/go v0.65.0 // indirect - github.com/cristalhq/aconfig v0.18.3 // indirect - github.com/cristalhq/aconfig/aconfigdotenv v0.17.1 // indirect + cloud.google.com/go/compute v1.20.1 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/caddyserver/certmagic v0.19.2 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect - github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/logr v1.2.4 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.1.0 // indirect - github.com/googleapis/gax-go/v2 v2.0.5 // indirect + github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect + github.com/google/s2a-go v0.1.4 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect + github.com/googleapis/gax-go/v2 v2.11.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.4 // indirect - github.com/imdario/mergo v0.3.6 // indirect - github.com/joho/godotenv v1.4.0 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/kr/text v0.2.0 // indirect + github.com/libdns/libdns v0.2.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mholt/acmez v1.2.0 // indirect + github.com/miekg/dns v1.1.55 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/motomux/pretty v0.0.0-20161209205251-b2aad2c9a95d // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/rs/zerolog v1.28.0 // indirect - github.com/sirupsen/logrus v1.9.0 // indirect + github.com/onsi/ginkgo/v2 v2.9.5 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect + github.com/quic-go/qpack v0.4.0 // indirect + github.com/quic-go/qtls-go1-20 v0.3.1 // indirect + github.com/quic-go/quic-go v0.37.5 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect - go.opencensus.io v0.22.4 // indirect - golang.org/x/crypto v0.8.0 // indirect - golang.org/x/net v0.9.0 // indirect - golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect - golang.org/x/sys v0.7.0 // indirect - golang.org/x/term v0.7.0 // indirect + github.com/zeebo/blake3 v0.2.3 // indirect + go.opencensus.io v0.24.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.12.0 // indirect + golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 // indirect + golang.org/x/mod v0.11.0 // indirect + golang.org/x/net v0.14.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/term v0.11.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect + golang.org/x/tools v0.10.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect - google.golang.org/grpc v1.47.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/grpc v1.56.2 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index f571d8eb76a8e8bf72cda03eb9b80b95fb2901d9..91e67319bb9289efbb9802a3f740a5306fbb6e00 100644 --- a/go.sum +++ b/go.sum @@ -12,7 +12,6 @@ cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bP cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= @@ -20,6 +19,10 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg= +cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= @@ -32,22 +35,32 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -gfx.cafe/ghalliday1/scram v0.0.1 h1:CUhXNgquodVk+8hIUYGzqQg585T+yQ4cRs2RU6wG2tA= -gfx.cafe/ghalliday1/scram v0.0.1/go.mod h1:qt6+WJoFcX2id67G5w+/dktBJ56Se0sZAa7WjqfNNu0= gfx.cafe/ghalliday1/scram v0.0.2 h1:uuScaL7DUEP/lKWJnA5kKHq5RJev26q8DMbP3gKviAg= gfx.cafe/ghalliday1/scram v0.0.2/go.mod h1:qt6+WJoFcX2id67G5w+/dktBJ56Se0sZAa7WjqfNNu0= -gfx.cafe/util/go/gun v0.0.0-20230721185457-c559e86c829c h1:4XxKaHfYPam36FibTiy1Te7ycfW4+ys08WYyDih5VmU= -gfx.cafe/util/go/gun v0.0.0-20230721185457-c559e86c829c/go.mod h1:zxq7FdmfdrI4oGeze0MPJt9WqdkFj3BDDSAWRuB63JQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw= +github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/caddyserver/caddy/v2 v2.7.4 h1:J8nisjdOxnYHXlorUKXY75Gr6iBfudfoGhrJ8t7/flI= +github.com/caddyserver/caddy/v2 v2.7.4/go.mod h1:/OH2g/56QCSCajEWsFa8kjwacziG/YFxeWgKacnK6KE= +github.com/caddyserver/certmagic v0.19.2 h1:HZd1AKLx4592MalEGQS39DKs2ZOAJCEM/xYPMQ2/ui0= +github.com/caddyserver/certmagic v0.19.2/go.mod h1:fsL01NomQ6N+kE2j37ZCnig2MFosG+MIO4ztnmG/zz8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -55,52 +68,51 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cristalhq/aconfig v0.17.0/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E= -github.com/cristalhq/aconfig v0.18.3 h1:Or12LIWIF+2mQpcGWA2PQnNc55+WiHFAqRjYh/pQNtM= -github.com/cristalhq/aconfig v0.18.3/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E= -github.com/cristalhq/aconfig/aconfigdotenv v0.17.1 h1:HG2ql5fGe4FLL2fUv6o+o0YRyF1mWEcYkNfWGWD82k4= -github.com/cristalhq/aconfig/aconfigdotenv v0.17.1/go.mod h1:gQIKkh+HkVcODvMNz/cLbH65Pk9b0r4tfolCOsI8G9I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/digitalocean/godo v1.102.1 h1:BrNePwIXjQWjOJXVTBqkURMjm70BRR0qXbRKfHNBF24= github.com/digitalocean/godo v1.102.1/go.mod h1:SaUYccN7r+CO1QtsbXGypAsgobDrmSfVMJESEfXgoEg= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -116,6 +128,8 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -136,7 +150,6 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -147,8 +160,9 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -166,18 +180,19 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= +github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= +github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= @@ -188,20 +203,31 @@ github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= -github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -210,95 +236,137 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis= +github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= +github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= +github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= +github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/motomux/pretty v0.0.0-20161209205251-b2aad2c9a95d h1:LznySqW8MqVeFh+pW6rOkFdld9QQ7jRydBKKM6jyPVI= github.com/motomux/pretty v0.0.0-20161209205251-b2aad2c9a95d/go.mod h1:u3hJ0kqCQu/cPpsu3RbCOPZ0d7V3IjPjv1adNRleM9I= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= -github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= +github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/qtls-go1-20 v0.3.1 h1:O4BLOM3hwfVF3AcktIylQXyl7Yi2iBNVy5QsV+ySxbg= +github.com/quic-go/qtls-go1-20 v0.3.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/quic-go v0.37.5 h1:pzkYe8AgaxHi+7KJrYBMF+u2rLO5a9kwyCp2dAsljzk= +github.com/quic-go/quic-go v0.37.5/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= -github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zalando/postgres-operator v1.8.2 h1:3FW3j2gXua1MSeE+NiSvB8cxM7k7fyoun46G1v++CCA= github.com/zalando/postgres-operator v1.8.2/go.mod h1:f7AXk8LO/tWFdW4myPJZCwMueGg6fI4RqTuOA0BefZE= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= +github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= +github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= +github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= -go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= -go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= -go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= -go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= +go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -309,6 +377,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 h1:LGJsf5LRplCck6jUCH3dBL2dmycNruWNF5xugkSlfXw= +golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -329,9 +399,13 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -339,6 +413,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -357,19 +432,25 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -379,11 +460,17 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -392,6 +479,7 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -404,32 +492,35 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= @@ -481,8 +572,10 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= +golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -502,8 +595,9 @@ google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= +google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -543,8 +637,10 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 h1:Au6te5hbKUV8pIYWHqOUZ1pva5qK/rwbIhoXEUB9Lu8= +google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 h1:XVeBY8d/FaK4848myy41HBqnDwvxeV3zMZhwN1TvAMU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -558,10 +654,11 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= +google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -574,10 +671,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -586,10 +682,13 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -626,5 +725,3 @@ sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kF sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= -tuxpa.in/a/zlog v1.61.0 h1:7wrS6G4QwpnOmgHRQknrr7IgiMXrfGpekkU0PjM9FhE= -tuxpa.in/a/zlog v1.61.0/go.mod h1:CNpMe8laDHLSypx/DyxfX1S0oyxUydeo3aGTEbtRBhg= diff --git a/lib/bouncer/backends/v0/accept.go b/lib/bouncer/backends/v0/accept.go index 3168ce0a0b6923aa3f7a66bf0871d6fb13efcd39..d094f2f19f5f39bf5b38ca68f71a7a589ba7c5c9 100644 --- a/lib/bouncer/backends/v0/accept.go +++ b/lib/bouncer/backends/v0/accept.go @@ -1,16 +1,18 @@ package backends import ( + "crypto/tls" "errors" "io" "gfx.cafe/gfx/pggat/lib/auth" + "gfx.cafe/gfx/pggat/lib/bouncer" "gfx.cafe/gfx/pggat/lib/fed" packets "gfx.cafe/gfx/pggat/lib/fed/packets/v3.0" "gfx.cafe/gfx/pggat/lib/util/strutil" ) -func authenticationSASLChallenge(ctx *AcceptContext, encoder auth.SASLEncoder) (done bool, err error) { +func authenticationSASLChallenge(ctx *acceptContext, encoder auth.SASLEncoder) (done bool, err error) { ctx.Packet, err = ctx.Conn.ReadPacket(true, ctx.Packet) if err != nil { return @@ -54,7 +56,7 @@ func authenticationSASLChallenge(ctx *AcceptContext, encoder auth.SASLEncoder) ( } } -func authenticationSASL(ctx *AcceptContext, mechanisms []string, creds auth.SASLClient) error { +func authenticationSASL(ctx *acceptContext, mechanisms []string, creds auth.SASLClient) error { mechanism, encoder, err := creds.EncodeSASL(mechanisms) if err != nil { return err @@ -89,7 +91,7 @@ func authenticationSASL(ctx *AcceptContext, mechanisms []string, creds auth.SASL return nil } -func authenticationMD5(ctx *AcceptContext, salt [4]byte, creds auth.MD5Client) error { +func authenticationMD5(ctx *acceptContext, salt [4]byte, creds auth.MD5Client) error { pw := packets.PasswordMessage{ Password: creds.EncodeMD5(salt), } @@ -101,7 +103,7 @@ func authenticationMD5(ctx *AcceptContext, salt [4]byte, creds auth.MD5Client) e return nil } -func authenticationCleartext(ctx *AcceptContext, creds auth.CleartextClient) error { +func authenticationCleartext(ctx *acceptContext, creds auth.CleartextClient) error { pw := packets.PasswordMessage{ Password: creds.EncodeCleartext(), } @@ -113,13 +115,14 @@ func authenticationCleartext(ctx *AcceptContext, creds auth.CleartextClient) err return nil } -func authentication(ctx *AcceptContext) (done bool, err error) { +func authentication(ctx *acceptContext) (done bool, err error) { var method int32 ctx.Packet.ReadInt32(&method) // they have more authentication methods than there are pokemon switch method { case 0: // we're good to go, that was easy + ctx.Conn.Authenticated = true return true, nil case 2: err = errors.New("kerberos v5 is not supported") @@ -170,7 +173,7 @@ func authentication(ctx *AcceptContext) (done bool, err error) { } } -func startup0(ctx *AcceptContext) (done bool, err error) { +func startup0(ctx *acceptContext) (done bool, err error) { ctx.Packet, err = ctx.Conn.ReadPacket(true, ctx.Packet) if err != nil { return @@ -197,7 +200,7 @@ func startup0(ctx *AcceptContext) (done bool, err error) { } } -func startup1(ctx *AcceptContext, params *AcceptParams) (done bool, err error) { +func startup1(ctx *acceptContext) (done bool, err error) { ctx.Packet, err = ctx.Conn.ReadPacket(true, ctx.Packet) if err != nil { return @@ -205,7 +208,7 @@ func startup1(ctx *AcceptContext, params *AcceptParams) (done bool, err error) { switch ctx.Packet.Type() { case packets.TypeBackendKeyData: - ctx.Packet.ReadBytes(params.BackendKey[:]) + ctx.Packet.ReadBytes(ctx.Conn.BackendKey[:]) return false, nil case packets.TypeParameterStatus: var ps packets.ParameterStatus @@ -214,10 +217,10 @@ func startup1(ctx *AcceptContext, params *AcceptParams) (done bool, err error) { return } ikey := strutil.MakeCIString(ps.Key) - if params.InitialParameters == nil { - params.InitialParameters = make(map[strutil.CIString]string) + if ctx.Conn.InitialParameters == nil { + ctx.Conn.InitialParameters = make(map[strutil.CIString]string) } - params.InitialParameters[ikey] = ps.Value + ctx.Conn.InitialParameters[ikey] = ps.Value return false, nil case packets.TypeReadyForQuery: return true, nil @@ -238,7 +241,7 @@ func startup1(ctx *AcceptContext, params *AcceptParams) (done bool, err error) { } } -func enableSSL(ctx *AcceptContext) (bool, error) { +func enableSSL(ctx *acceptContext) (bool, error) { ctx.Packet = ctx.Packet.Reset(0, 4) ctx.Packet = ctx.Packet.AppendUint16(1234) ctx.Packet = ctx.Packet.AppendUint16(5679) @@ -246,7 +249,7 @@ func enableSSL(ctx *AcceptContext) (bool, error) { return false, err } - byteReader, ok := ctx.Conn.(io.ByteReader) + byteReader, ok := ctx.Conn.ReadWriteCloser.(io.ByteReader) if !ok { return false, errors.New("server must be io.ByteReader to enable ssl") } @@ -262,7 +265,7 @@ func enableSSL(ctx *AcceptContext) (bool, error) { return false, nil } - sslClient, ok := ctx.Conn.(fed.SSLClient) + sslClient, ok := ctx.Conn.ReadWriteCloser.(fed.SSLClient) if !ok { return false, errors.New("server must be fed.SSLClient to enable ssl") } @@ -274,23 +277,20 @@ func enableSSL(ctx *AcceptContext) (bool, error) { return true, nil } -func Accept(ctx *AcceptContext) (AcceptParams, error) { +func accept(ctx *acceptContext) error { username := ctx.Options.Username if ctx.Options.Database == "" { ctx.Options.Database = username } - var params AcceptParams - if ctx.Options.SSLMode.ShouldAttempt() { - var err error - params.SSLEnabled, err = enableSSL(ctx) + sslEnabled, err := enableSSL(ctx) if err != nil { - return AcceptParams{}, err + return err } - if !params.SSLEnabled && ctx.Options.SSLMode.IsRequired() { - return AcceptParams{}, errors.New("server rejected SSL encryption") + if !sslEnabled && ctx.Options.SSLMode.IsRequired() { + return errors.New("server rejected SSL encryption") } } @@ -315,14 +315,14 @@ func Accept(ctx *AcceptContext) (AcceptParams, error) { err := ctx.Conn.WritePacket(ctx.Packet) if err != nil { - return AcceptParams{}, err + return err } for { var done bool done, err = startup0(ctx) if err != nil { - return AcceptParams{}, err + return err } if done { break @@ -331,9 +331,9 @@ func Accept(ctx *AcceptContext) (AcceptParams, error) { for { var done bool - done, err = startup1(ctx, ¶ms) + done, err = startup1(ctx) if err != nil { - return AcceptParams{}, err + return err } if done { break @@ -341,5 +341,28 @@ func Accept(ctx *AcceptContext) (AcceptParams, error) { } // startup complete, connection is ready for queries - return params, nil + return nil +} + +func Accept( + conn *fed.Conn, + sslMode bouncer.SSLMode, + sslConfig *tls.Config, + username string, + credentials auth.Credentials, + database string, + startupParameters map[strutil.CIString]string, +) error { + ctx := acceptContext{ + Conn: conn, + Options: acceptOptions{ + SSLMode: sslMode, + SSLConfig: sslConfig, + Username: username, + Credentials: credentials, + Database: database, + StartupParameters: startupParameters, + }, + } + return accept(&ctx) } diff --git a/lib/bouncer/backends/v0/cancel.go b/lib/bouncer/backends/v0/cancel.go index 1ff2e957ab15fc15b14429e55f7656a40e1cf4cf..23769b8dbed161c791b0e64ee450820a24d8bb9d 100644 --- a/lib/bouncer/backends/v0/cancel.go +++ b/lib/bouncer/backends/v0/cancel.go @@ -2,7 +2,7 @@ package backends import "gfx.cafe/gfx/pggat/lib/fed" -func Cancel(server fed.ReadWriter, key [8]byte) error { +func Cancel(server *fed.Conn, key [8]byte) error { packet := fed.NewPacket(0, 12) packet = packet.AppendUint16(1234) packet = packet.AppendUint16(5678) diff --git a/lib/bouncer/backends/v0/context.go b/lib/bouncer/backends/v0/context.go index 5d4f08c717962c2d1eec88edd092de5feab717be..01945b65e47630a4bdfa299abf446d1bbdc7f05c 100644 --- a/lib/bouncer/backends/v0/context.go +++ b/lib/bouncer/backends/v0/context.go @@ -4,38 +4,38 @@ import ( "gfx.cafe/gfx/pggat/lib/fed" ) -type AcceptContext struct { +type acceptContext struct { Packet fed.Packet - Conn fed.Conn - Options AcceptOptions + Conn *fed.Conn + Options acceptOptions } -type Context struct { - Server fed.ReadWriter +type context struct { + Server *fed.Conn Packet fed.Packet - Peer fed.ReadWriter + Peer *fed.Conn PeerError error TxState byte } -func (T *Context) ServerRead() error { +func (T *context) ServerRead() error { var err error T.Packet, err = T.Server.ReadPacket(true, T.Packet) return err } -func (T *Context) ServerWrite() error { +func (T *context) ServerWrite() error { return T.Server.WritePacket(T.Packet) } -func (T *Context) PeerOK() bool { +func (T *context) PeerOK() bool { if T == nil { return false } return T.Peer != nil && T.PeerError == nil } -func (T *Context) PeerFail(err error) { +func (T *context) PeerFail(err error) { if T == nil { return } @@ -43,7 +43,7 @@ func (T *Context) PeerFail(err error) { T.PeerError = err } -func (T *Context) PeerRead() bool { +func (T *context) PeerRead() bool { if T == nil { return false } @@ -59,7 +59,7 @@ func (T *Context) PeerRead() bool { return true } -func (T *Context) PeerWrite() { +func (T *context) PeerWrite() { if T == nil { return } diff --git a/lib/bouncer/backends/v0/options.go b/lib/bouncer/backends/v0/options.go index 5da0b1e9ff38afe1c0707d50dea63915d4b8a055..9d98d466be8d1eb0219b78a869f09d646b37f274 100644 --- a/lib/bouncer/backends/v0/options.go +++ b/lib/bouncer/backends/v0/options.go @@ -8,7 +8,7 @@ import ( "gfx.cafe/gfx/pggat/lib/util/strutil" ) -type AcceptOptions struct { +type acceptOptions struct { SSLMode bouncer.SSLMode SSLConfig *tls.Config Username string diff --git a/lib/bouncer/backends/v0/params.go b/lib/bouncer/backends/v0/params.go deleted file mode 100644 index c3f6cb0ece1003bdc382db66106cd4783a80bf4d..0000000000000000000000000000000000000000 --- a/lib/bouncer/backends/v0/params.go +++ /dev/null @@ -1,9 +0,0 @@ -package backends - -import "gfx.cafe/gfx/pggat/lib/util/strutil" - -type AcceptParams struct { - SSLEnabled bool - InitialParameters map[strutil.CIString]string - BackendKey [8]byte -} diff --git a/lib/bouncer/backends/v0/query.go b/lib/bouncer/backends/v0/query.go index c968947408f213a445482de8bbe4028cbbe4f385..89a8bd3f82d758a7f924f14d7944e0285e4376ef 100644 --- a/lib/bouncer/backends/v0/query.go +++ b/lib/bouncer/backends/v0/query.go @@ -3,11 +3,12 @@ package backends import ( "fmt" + "gfx.cafe/gfx/pggat/lib/fed" packets "gfx.cafe/gfx/pggat/lib/fed/packets/v3.0" "gfx.cafe/gfx/pggat/lib/util/strutil" ) -func CopyIn(ctx *Context) error { +func copyIn(ctx *context) error { ctx.PeerWrite() for { @@ -32,7 +33,7 @@ func CopyIn(ctx *Context) error { } } -func CopyOut(ctx *Context) error { +func copyOut(ctx *context) error { ctx.PeerWrite() for { @@ -56,7 +57,7 @@ func CopyOut(ctx *Context) error { } } -func Query(ctx *Context) error { +func query(ctx *context) error { if err := ctx.ServerWrite(); err != nil { return err } @@ -78,11 +79,11 @@ func Query(ctx *Context) error { packets.TypeNotificationResponse: ctx.PeerWrite() case packets.TypeCopyInResponse: - if err = CopyIn(ctx); err != nil { + if err = copyIn(ctx); err != nil { return err } case packets.TypeCopyOutResponse: - if err = CopyOut(ctx); err != nil { + if err = copyOut(ctx); err != nil { return err } case packets.TypeReadyForQuery: @@ -99,20 +100,34 @@ func Query(ctx *Context) error { } } -func QueryString(ctx *Context, query string) error { - q := packets.Query(query) - ctx.Packet = q.IntoPacket(ctx.Packet) - return Query(ctx) +func queryString(ctx *context, q string) error { + qq := packets.Query(q) + ctx.Packet = qq.IntoPacket(ctx.Packet) + return query(ctx) } -func SetParameter(ctx *Context, name strutil.CIString, value string) error { +func QueryString(server, peer *fed.Conn, buffer fed.Packet, query string) (err, peerError error, packet fed.Packet) { + ctx := context{ + Server: server, + Peer: peer, + Packet: buffer, + } + err = queryString(&ctx, query) + peerError = ctx.PeerError + packet = ctx.Packet + return +} + +func SetParameter(server, peer *fed.Conn, buffer fed.Packet, name strutil.CIString, value string) (err, peerError error, packet fed.Packet) { return QueryString( - ctx, + server, + peer, + buffer, fmt.Sprintf(`SET "%s" = '%s'`, strutil.Escape(name.String(), '"'), strutil.Escape(value, '\'')), ) } -func FunctionCall(ctx *Context) error { +func functionCall(ctx *context) error { if err := ctx.ServerWrite(); err != nil { return err } @@ -144,7 +159,7 @@ func FunctionCall(ctx *Context) error { } } -func Sync(ctx *Context) (bool, error) { +func sync(ctx *context) (bool, error) { ctx.Packet = ctx.Packet.Reset(packets.TypeSync) if err := ctx.ServerWrite(); err != nil { return false, err @@ -175,13 +190,13 @@ func Sync(ctx *Context) (bool, error) { packets.TypeNotificationResponse: ctx.PeerWrite() case packets.TypeCopyInResponse: - if err = CopyIn(ctx); err != nil { + if err = copyIn(ctx); err != nil { return false, err } // why return false, nil case packets.TypeCopyOutResponse: - if err = CopyOut(ctx); err != nil { + if err = copyOut(ctx); err != nil { return false, err } case packets.TypeReadyForQuery: @@ -198,7 +213,19 @@ func Sync(ctx *Context) (bool, error) { } } -func EQP(ctx *Context) error { +func Sync(server, peer *fed.Conn, buffer fed.Packet) (err, peerErr error, packet fed.Packet) { + ctx := context{ + Server: server, + Peer: peer, + Packet: buffer, + } + _, err = sync(&ctx) + peerErr = ctx.PeerError + packet = ctx.Packet + return +} + +func eqp(ctx *context) error { if err := ctx.ServerWrite(); err != nil { return err } @@ -206,7 +233,7 @@ func EQP(ctx *Context) error { for { if !ctx.PeerRead() { for { - ok, err := Sync(ctx) + ok, err := sync(ctx) if err != nil { return err } @@ -218,7 +245,7 @@ func EQP(ctx *Context) error { switch ctx.Packet.Type() { case packets.TypeSync: - ok, err := Sync(ctx) + ok, err := sync(ctx) if err != nil { return err } @@ -235,18 +262,15 @@ func EQP(ctx *Context) error { } } -func Transaction(ctx *Context) error { - if ctx.TxState == '\x00' { - ctx.TxState = 'I' - } +func transaction(ctx *context) error { for { switch ctx.Packet.Type() { case packets.TypeQuery: - if err := Query(ctx); err != nil { + if err := query(ctx); err != nil { return err } case packets.TypeFunctionCall: - if err := FunctionCall(ctx); err != nil { + if err := functionCall(ctx); err != nil { return err } case packets.TypeSync: @@ -255,7 +279,7 @@ func Transaction(ctx *Context) error { ctx.Packet = rfq.IntoPacket(ctx.Packet) ctx.PeerWrite() case packets.TypeParse, packets.TypeBind, packets.TypeClose, packets.TypeDescribe, packets.TypeExecute, packets.TypeFlush: - if err := EQP(ctx); err != nil { + if err := eqp(ctx); err != nil { return err } default: @@ -268,7 +292,7 @@ func Transaction(ctx *Context) error { if !ctx.PeerRead() { // abort tx - err := QueryString(ctx, "ABORT;") + err := queryString(ctx, "ABORT;") if err != nil { return err } @@ -280,3 +304,15 @@ func Transaction(ctx *Context) error { } } } + +func Transaction(server, peer *fed.Conn, initialPacket fed.Packet) (err, peerError error, packet fed.Packet) { + ctx := context{ + Server: server, + Peer: peer, + Packet: initialPacket, + } + err = transaction(&ctx) + peerError = ctx.PeerError + packet = ctx.Packet + return +} diff --git a/lib/bouncer/bouncers/v2/bouncer.go b/lib/bouncer/bouncers/v2/bouncer.go index 797ba8095b18d139e7b43515afe02c7805866ef2..573b8cc28a3ac92de6dcf1c5a03cfa5ace827877 100644 --- a/lib/bouncer/bouncers/v2/bouncer.go +++ b/lib/bouncer/bouncers/v2/bouncer.go @@ -7,31 +7,24 @@ import ( "gfx.cafe/gfx/pggat/lib/perror" ) -func clientFail(ctx *backends.Context, client fed.ReadWriter, err perror.Error) { +func clientFail(packet fed.Packet, client *fed.Conn, err perror.Error) fed.Packet { // send fatal error to client resp := packets.ErrorResponse{ Error: err, } - ctx.Packet = resp.IntoPacket(ctx.Packet) - _ = client.WritePacket(ctx.Packet) + packet = resp.IntoPacket(packet) + _ = client.WritePacket(packet) + return packet } -func Bounce(client, server fed.ReadWriter, initialPacket fed.Packet) (packet fed.Packet, clientError error, serverError error) { - ctx := backends.Context{ - Server: server, - Packet: initialPacket, - Peer: client, - } - serverError = backends.Transaction(&ctx) - clientError = ctx.PeerError +func Bounce(client, server *fed.Conn, initialPacket fed.Packet) (packet fed.Packet, clientError error, serverError error) { + serverError, clientError, packet = backends.Transaction(server, client, initialPacket) if clientError != nil { - clientFail(&ctx, client, perror.Wrap(clientError)) + packet = clientFail(packet, client, perror.Wrap(clientError)) } else if serverError != nil { - clientFail(&ctx, client, perror.Wrap(serverError)) + packet = clientFail(packet, client, perror.Wrap(serverError)) } - packet = ctx.Packet - return } diff --git a/lib/bouncer/frontends/v0/accept.go b/lib/bouncer/frontends/v0/accept.go index aff54a78a61da396e892728ff8d7e05cfc509113..5fadb9e3e51ba004a9e06fa86f103944bb7b09fd 100644 --- a/lib/bouncer/frontends/v0/accept.go +++ b/lib/bouncer/frontends/v0/accept.go @@ -1,21 +1,20 @@ package frontends import ( - "fmt" + "crypto/tls" "io" "strings" "gfx.cafe/gfx/pggat/lib/fed" packets "gfx.cafe/gfx/pggat/lib/fed/packets/v3.0" "gfx.cafe/gfx/pggat/lib/perror" - "gfx.cafe/gfx/pggat/lib/util/slices" "gfx.cafe/gfx/pggat/lib/util/strutil" ) func startup0( - ctx *AcceptContext, - params *AcceptParams, -) (done bool, err perror.Error) { + ctx *acceptContext, + params *acceptParams, +) (cancelling bool, done bool, err perror.Error) { var err2 error ctx.Packet, err2 = ctx.Conn.ReadPacket(false, ctx.Packet) if err2 != nil { @@ -34,22 +33,11 @@ func startup0( case 5678: // Cancel p.ReadBytes(params.CancelKey[:]) - - if params.CancelKey == [8]byte{} { - // very rare that this would ever happen - // and it's ok if we don't honor cancel requests - err = perror.New( - perror.FATAL, - perror.ProtocolViolation, - "cancel key cannot be null", - ) - return - } - + cancelling = true done = true return case 5679: - byteWriter, ok := ctx.Conn.(io.ByteWriter) + byteWriter, ok := ctx.Conn.ReadWriteCloser.(io.ByteWriter) if !ok { err = perror.New( perror.FATAL, @@ -65,13 +53,9 @@ func startup0( return } - sslServer, ok := ctx.Conn.(fed.SSLServer) + sslServer, ok := ctx.Conn.ReadWriteCloser.(fed.SSLServer) if !ok { - err = perror.New( - perror.FATAL, - perror.FeatureNotSupported, - "SSL is not supported", - ) + err = perror.Wrap(byteWriter.WriteByte('N')) return } @@ -82,10 +66,9 @@ func startup0( if err = perror.Wrap(sslServer.EnableSSLServer(ctx.Options.SSLConfig)); err != nil { return } - params.SSLEnabled = true return case 5680: - byteWriter, ok := ctx.Conn.(io.ByteWriter) + byteWriter, ok := ctx.Conn.ReadWriteCloser.(io.ByteWriter) if !ok { err = perror.New( perror.FATAL, @@ -131,9 +114,9 @@ func startup0( switch key { case "user": - params.User = value + ctx.Conn.User = value case "database": - params.Database = value + ctx.Conn.Database = value case "options": fields := strings.Fields(value) for i := 0; i < len(fields); i++ { @@ -154,19 +137,10 @@ func startup0( ikey := strutil.MakeCIString(key) - if !slices.Contains(ctx.Options.AllowedStartupOptions, ikey) { - err = perror.New( - perror.FATAL, - perror.FeatureNotSupported, - fmt.Sprintf(`Startup parameter "%s" is not allowed`, key), - ) - return - } - - if params.InitialParameters == nil { - params.InitialParameters = make(map[strutil.CIString]string) + if ctx.Conn.InitialParameters == nil { + ctx.Conn.InitialParameters = make(map[strutil.CIString]string) } - params.InitialParameters[ikey] = value + ctx.Conn.InitialParameters[ikey] = value default: err = perror.New( perror.FATAL, @@ -190,19 +164,10 @@ func startup0( } else { ikey := strutil.MakeCIString(key) - if !slices.Contains(ctx.Options.AllowedStartupOptions, ikey) { - err = perror.New( - perror.FATAL, - perror.FeatureNotSupported, - fmt.Sprintf(`Startup parameter "%s" is not allowed`, key), - ) - return - } - - if params.InitialParameters == nil { - params.InitialParameters = make(map[strutil.CIString]string) + if ctx.Conn.InitialParameters == nil { + ctx.Conn.InitialParameters = make(map[strutil.CIString]string) } - params.InitialParameters[ikey] = value + ctx.Conn.InitialParameters[ikey] = value } } } @@ -220,7 +185,7 @@ func startup0( } } - if params.User == "" { + if ctx.Conn.User == "" { err = perror.New( perror.FATAL, perror.InvalidAuthorizationSpecification, @@ -228,20 +193,20 @@ func startup0( ) return } - if params.Database == "" { - params.Database = params.User + if ctx.Conn.Database == "" { + ctx.Conn.Database = ctx.Conn.User } done = true return } -func accept( - ctx *AcceptContext, -) (params AcceptParams, err perror.Error) { +func accept0( + ctx *acceptContext, +) (params acceptParams, err perror.Error) { for { var done bool - done, err = startup0(ctx, ¶ms) + params.IsCanceling, done, err = startup0(ctx, ¶ms) if err != nil { return } @@ -250,23 +215,10 @@ func accept( } } - if params.CancelKey != [8]byte{} { - return - } - - if ctx.Options.SSLRequired && !params.SSLEnabled { - err = perror.New( - perror.FATAL, - perror.InvalidPassword, - "SSL is required", - ) - return - } - return } -func fail(packet fed.Packet, client fed.Conn, err perror.Error) { +func fail(packet fed.Packet, client fed.ReadWriter, err perror.Error) { resp := packets.ErrorResponse{ Error: err, } @@ -274,11 +226,29 @@ func fail(packet fed.Packet, client fed.Conn, err perror.Error) { _ = client.WritePacket(packet) } -func Accept(ctx *AcceptContext) (AcceptParams, perror.Error) { - params, err := accept(ctx) +func accept(ctx *acceptContext) (acceptParams, perror.Error) { + params, err := accept0(ctx) if err != nil { fail(ctx.Packet, ctx.Conn, err) - return AcceptParams{}, err + return acceptParams{}, err } return params, nil } + +func Accept(conn *fed.Conn, tlsConfig *tls.Config) ( + cancelKey [8]byte, + isCanceling bool, + err perror.Error, +) { + ctx := acceptContext{ + Conn: conn, + Options: acceptOptions{ + SSLConfig: tlsConfig, + }, + } + var params acceptParams + params, err = accept(&ctx) + cancelKey = params.CancelKey + isCanceling = params.IsCanceling + return +} diff --git a/lib/bouncer/frontends/v0/authenticate.go b/lib/bouncer/frontends/v0/authenticate.go index b81603e3f1d59fccb2779a1b9125732c3986ec1c..c22f4e3920af9877f2bceb6ca3d56dc49e4532ac 100644 --- a/lib/bouncer/frontends/v0/authenticate.go +++ b/lib/bouncer/frontends/v0/authenticate.go @@ -6,11 +6,12 @@ import ( "io" "gfx.cafe/gfx/pggat/lib/auth" + "gfx.cafe/gfx/pggat/lib/fed" packets "gfx.cafe/gfx/pggat/lib/fed/packets/v3.0" "gfx.cafe/gfx/pggat/lib/perror" ) -func authenticationSASLInitial(ctx *AuthenticateContext, creds auth.SASLServer) (tool auth.SASLVerifier, resp []byte, done bool, err perror.Error) { +func authenticationSASLInitial(ctx *authenticateContext, creds auth.SASLServer) (tool auth.SASLVerifier, resp []byte, done bool, err perror.Error) { // check which authentication method the client wants var err2 error ctx.Packet, err2 = ctx.Conn.ReadPacket(true, ctx.Packet) @@ -42,7 +43,7 @@ func authenticationSASLInitial(ctx *AuthenticateContext, creds auth.SASLServer) return } -func authenticationSASLContinue(ctx *AuthenticateContext, tool auth.SASLVerifier) (resp []byte, done bool, err perror.Error) { +func authenticationSASLContinue(ctx *authenticateContext, tool auth.SASLVerifier) (resp []byte, done bool, err perror.Error) { var err2 error ctx.Packet, err2 = ctx.Conn.ReadPacket(true, ctx.Packet) if err2 != nil { @@ -67,7 +68,7 @@ func authenticationSASLContinue(ctx *AuthenticateContext, tool auth.SASLVerifier return } -func authenticationSASL(ctx *AuthenticateContext, creds auth.SASLServer) perror.Error { +func authenticationSASL(ctx *authenticateContext, creds auth.SASLServer) perror.Error { saslInitial := packets.AuthenticationSASL{ Mechanisms: creds.SupportedSASLMechanisms(), } @@ -109,7 +110,7 @@ func authenticationSASL(ctx *AuthenticateContext, creds auth.SASLServer) perror. return nil } -func authenticationMD5(ctx *AuthenticateContext, creds auth.MD5Server) perror.Error { +func authenticationMD5(ctx *authenticateContext, creds auth.MD5Server) perror.Error { var salt [4]byte _, err := rand.Read(salt[:]) if err != nil { @@ -141,7 +142,7 @@ func authenticationMD5(ctx *AuthenticateContext, creds auth.MD5Server) perror.Er return nil } -func authenticate(ctx *AuthenticateContext) (params AuthenticateParams, err perror.Error) { +func authenticate(ctx *authenticateContext) (err perror.Error) { if ctx.Options.Credentials != nil { if credsSASL, ok := ctx.Options.Credentials.(auth.SASLServer); ok { err = authenticationSASL(ctx, credsSASL) @@ -165,16 +166,17 @@ func authenticate(ctx *AuthenticateContext) (params AuthenticateParams, err perr if err = perror.Wrap(ctx.Conn.WritePacket(ctx.Packet)); err != nil { return } + ctx.Conn.Authenticated = true // send backend key data - _, err2 := rand.Read(params.BackendKey[:]) + _, err2 := rand.Read(ctx.Conn.BackendKey[:]) if err2 != nil { err = perror.Wrap(err2) return } keyData := packets.BackendKeyData{ - CancellationKey: params.BackendKey, + CancellationKey: ctx.Conn.BackendKey, } ctx.Packet = keyData.IntoPacket(ctx.Packet) if err = perror.Wrap(ctx.Conn.WritePacket(ctx.Packet)); err != nil { @@ -184,11 +186,18 @@ func authenticate(ctx *AuthenticateContext) (params AuthenticateParams, err perr return } -func Authenticate(ctx *AuthenticateContext) (AuthenticateParams, perror.Error) { - params, err := authenticate(ctx) - if err != nil { - fail(ctx.Packet, ctx.Conn, err) - return AuthenticateParams{}, err +func Authenticate(conn *fed.Conn, creds auth.Credentials) (err perror.Error) { + if conn.Authenticated { + // already authenticated + return } - return params, nil + + ctx := authenticateContext{ + Conn: conn, + Options: authenticateOptions{ + Credentials: creds, + }, + } + err = authenticate(&ctx) + return } diff --git a/lib/bouncer/frontends/v0/context.go b/lib/bouncer/frontends/v0/context.go index 859532528081af9edecf9d1ca3bb9bfbb2adb3da..e0668cef9a24a5c7287da9562012e47f27cda543 100644 --- a/lib/bouncer/frontends/v0/context.go +++ b/lib/bouncer/frontends/v0/context.go @@ -2,14 +2,14 @@ package frontends import "gfx.cafe/gfx/pggat/lib/fed" -type AcceptContext struct { +type acceptContext struct { Packet fed.Packet - Conn fed.Conn - Options AcceptOptions + Conn *fed.Conn + Options acceptOptions } -type AuthenticateContext struct { +type authenticateContext struct { Packet fed.Packet - Conn fed.Conn - Options AuthenticateOptions + Conn *fed.Conn + Options authenticateOptions } diff --git a/lib/bouncer/frontends/v0/options.go b/lib/bouncer/frontends/v0/options.go index b06d9889b7cb9f7d13ebbfb5413e080fcb9d6eff..304f7b82256efcd4edf0dc1d7125c25491f93320 100644 --- a/lib/bouncer/frontends/v0/options.go +++ b/lib/bouncer/frontends/v0/options.go @@ -4,15 +4,12 @@ import ( "crypto/tls" "gfx.cafe/gfx/pggat/lib/auth" - "gfx.cafe/gfx/pggat/lib/util/strutil" ) -type AcceptOptions struct { - SSLRequired bool - SSLConfig *tls.Config - AllowedStartupOptions []strutil.CIString +type acceptOptions struct { + SSLConfig *tls.Config } -type AuthenticateOptions struct { +type authenticateOptions struct { Credentials auth.Credentials } diff --git a/lib/bouncer/frontends/v0/params.go b/lib/bouncer/frontends/v0/params.go index a182469535682589611815d51acd31be83e4acd0..0d960dbf3edbad11e492c22e899dd71e08824172 100644 --- a/lib/bouncer/frontends/v0/params.go +++ b/lib/bouncer/frontends/v0/params.go @@ -1,18 +1,6 @@ package frontends -import "gfx.cafe/gfx/pggat/lib/util/strutil" - -type AcceptParams struct { - CancelKey [8]byte - - // or - - SSLEnabled bool - User string - Database string - InitialParameters map[strutil.CIString]string -} - -type AuthenticateParams struct { - BackendKey [8]byte +type acceptParams struct { + CancelKey [8]byte + IsCanceling bool } diff --git a/lib/fed/conn.go b/lib/fed/conn.go index 394e451bdfa0b6e8c393900d4e3ee1f507aae460..c0b8e990d29292906f42356c127a0b1e7cce87db 100644 --- a/lib/fed/conn.go +++ b/lib/fed/conn.go @@ -1,123 +1,61 @@ package fed import ( - "bufio" - "crypto/tls" - "encoding/binary" - "errors" - "io" - "net" - - "gfx.cafe/gfx/pggat/lib/util/slices" + "gfx.cafe/gfx/pggat/lib/util/decorator" + "gfx.cafe/gfx/pggat/lib/util/strutil" ) -type Conn interface { - ReadWriter - - Close() error -} +type Conn struct { + noCopy decorator.NoCopy -type netConn struct { - conn net.Conn - writer bufio.Writer - reader bufio.Reader + ReadWriteCloser - headerBuf [5]byte -} + Middleware []Middleware -func WrapNetConn(conn net.Conn) Conn { - c := &netConn{ - conn: conn, - } - c.writer.Reset(conn) - c.reader.Reset(conn) - return c + User string + Database string + InitialParameters map[strutil.CIString]string + Authenticated bool + BackendKey [8]byte } -func (T *netConn) EnableSSLClient(config *tls.Config) error { - if err := T.writer.Flush(); err != nil { - return err - } - if T.reader.Buffered() > 0 { - return errors.New("expected empty read buffer") +func NewConn(rw ReadWriteCloser) *Conn { + return &Conn{ + ReadWriteCloser: rw, } - sslConn := tls.Client(T.conn, config) - T.writer.Reset(sslConn) - T.reader.Reset(sslConn) - T.conn = sslConn - return sslConn.Handshake() } -func (T *netConn) EnableSSLServer(config *tls.Config) error { - if err := T.writer.Flush(); err != nil { - return err - } - if T.reader.Buffered() > 0 { - return errors.New("expected empty read buffer") - } - sslConn := tls.Server(T.conn, config) - T.writer.Reset(sslConn) - T.reader.Reset(sslConn) - T.conn = sslConn - return sslConn.Handshake() -} - -func (T *netConn) ReadByte() (byte, error) { - if err := T.writer.Flush(); err != nil { - return 0, err - } - return T.reader.ReadByte() -} - -func (T *netConn) ReadPacket(typed bool, buffer Packet) (packet Packet, err error) { +func (T *Conn) ReadPacket(typed bool, buffer Packet) (packet Packet, err error) { packet = buffer - - if err = T.writer.Flush(); err != nil { - return - } - - if typed { - _, err = io.ReadFull(&T.reader, T.headerBuf[:]) + for { + packet, err = T.ReadWriteCloser.ReadPacket(typed, buffer) if err != nil { return } - } else { - _, err = io.ReadFull(&T.reader, T.headerBuf[1:]) - if err != nil { + for _, middleware := range T.Middleware { + packet, err = middleware.ReadPacket(packet) + if err != nil { + return + } + if len(packet) == 0 { + break + } + } + if len(packet) != 0 { return } } - - length := binary.BigEndian.Uint32(T.headerBuf[1:]) - - packet = slices.Resize(buffer, int(length)+1) - copy(packet, T.headerBuf[:]) - - _, err = io.ReadFull(&T.reader, packet.Payload()) - if err != nil { - return - } - return } -func (T *netConn) WriteByte(b byte) error { - return T.writer.WriteByte(b) -} - -func (T *netConn) WritePacket(packet Packet) error { - _, err := T.writer.Write(packet.Bytes()) - return err -} - -func (T *netConn) Close() error { - if err := T.writer.Flush(); err != nil { - return err +func (T *Conn) WritePacket(packet Packet) (err error) { + for _, middleware := range T.Middleware { + packet, err = middleware.WritePacket(packet) + if err != nil || len(packet) == 0 { + return + } } - return T.conn.Close() + err = T.ReadWriteCloser.WritePacket(packet) + return } -var _ Conn = (*netConn)(nil) -var _ SSLServer = (*netConn)(nil) -var _ SSLClient = (*netConn)(nil) -var _ io.ByteReader = (*netConn)(nil) -var _ io.ByteWriter = (*netConn)(nil) +var _ ReadWriteCloser = (*Conn)(nil) diff --git a/lib/fed/middleware.go b/lib/fed/middleware.go new file mode 100644 index 0000000000000000000000000000000000000000..c47a3f7043a23b693c72bacff6f43c6898ab283d --- /dev/null +++ b/lib/fed/middleware.go @@ -0,0 +1,7 @@ +package fed + +// Middleware intercepts packets and possibly changes them. Return a 0 length packet to cancel. +type Middleware interface { + ReadPacket(packet Packet) (Packet, error) + WritePacket(packet Packet) (Packet, error) +} diff --git a/lib/fed/middlewares/eqp/client.go b/lib/fed/middlewares/eqp/client.go new file mode 100644 index 0000000000000000000000000000000000000000..47417442094ac0b94e8304be45c5fb1af67a8cbd --- /dev/null +++ b/lib/fed/middlewares/eqp/client.go @@ -0,0 +1,25 @@ +package eqp + +import ( + "gfx.cafe/gfx/pggat/lib/fed" +) + +type Client struct { + state State +} + +func NewClient() *Client { + return new(Client) +} + +func (T *Client) ReadPacket(packet fed.Packet) (fed.Packet, error) { + T.state.C2S(packet) + return packet, nil +} + +func (T *Client) WritePacket(packet fed.Packet) (fed.Packet, error) { + T.state.S2C(packet) + return packet, nil +} + +var _ fed.Middleware = (*Client)(nil) diff --git a/lib/fed/middlewares/eqp/server.go b/lib/fed/middlewares/eqp/server.go new file mode 100644 index 0000000000000000000000000000000000000000..f1b9e7889ac2b5bf3fae74e07edf4e4c73080bcc --- /dev/null +++ b/lib/fed/middlewares/eqp/server.go @@ -0,0 +1,25 @@ +package eqp + +import ( + "gfx.cafe/gfx/pggat/lib/fed" +) + +type Server struct { + state State +} + +func NewServer() *Server { + return new(Server) +} + +func (T *Server) ReadPacket(packet fed.Packet) (fed.Packet, error) { + T.state.S2C(packet) + return packet, nil +} + +func (T *Server) WritePacket(packet fed.Packet) (fed.Packet, error) { + T.state.C2S(packet) + return packet, nil +} + +var _ fed.Middleware = (*Server)(nil) diff --git a/lib/middleware/middlewares/eqp/state.go b/lib/fed/middlewares/eqp/state.go similarity index 100% rename from lib/middleware/middlewares/eqp/state.go rename to lib/fed/middlewares/eqp/state.go diff --git a/lib/middleware/middlewares/eqp/sync.go b/lib/fed/middlewares/eqp/sync.go similarity index 92% rename from lib/middleware/middlewares/eqp/sync.go rename to lib/fed/middlewares/eqp/sync.go index 4bcd2cf58a1c1bc111d5ea7d408e6092d28de4cf..150d8e0d9ef2824a7cbbdc4f9029dc92d29abf7d 100644 --- a/lib/middleware/middlewares/eqp/sync.go +++ b/lib/fed/middlewares/eqp/sync.go @@ -6,7 +6,7 @@ import ( packets "gfx.cafe/gfx/pggat/lib/fed/packets/v3.0" ) -func Sync(c *Client, server fed.ReadWriter, s *Server) error { +func Sync(c *Client, server *fed.Conn, s *Server) error { var needsBackendSync bool // close all portals on server @@ -83,11 +83,8 @@ func Sync(c *Client, server fed.ReadWriter, s *Server) error { } if needsBackendSync { - ctx := backends.Context{ - Packet: packet, - Server: server, - } - _, err := backends.Sync(&ctx) + var err error + err, _, packet = backends.Sync(server, nil, packet) return err } diff --git a/lib/middleware/middlewares/ps/client.go b/lib/fed/middlewares/ps/client.go similarity index 69% rename from lib/middleware/middlewares/ps/client.go rename to lib/fed/middlewares/ps/client.go index c0b4fca687d57a1c992325b781eb40f5de025da1..ecb819f992b19c47dbf313604db87df7e0f7b2d9 100644 --- a/lib/middleware/middlewares/ps/client.go +++ b/lib/fed/middlewares/ps/client.go @@ -5,7 +5,6 @@ import ( "gfx.cafe/gfx/pggat/lib/fed" packets "gfx.cafe/gfx/pggat/lib/fed/packets/v3.0" - "gfx.cafe/gfx/pggat/lib/middleware" "gfx.cafe/gfx/pggat/lib/util/strutil" ) @@ -20,29 +19,28 @@ func NewClient(parameters map[strutil.CIString]string) *Client { } } -func (T *Client) Read(_ middleware.Context, _ fed.Packet) error { - return nil +func (T *Client) ReadPacket(packet fed.Packet) (fed.Packet, error) { + return packet, nil } -func (T *Client) Write(ctx middleware.Context, packet fed.Packet) error { +func (T *Client) WritePacket(packet fed.Packet) (fed.Packet, error) { switch packet.Type() { case packets.TypeParameterStatus: var ps packets.ParameterStatus if !ps.ReadFromPacket(packet) { - return errors.New("bad packet format i") + return packet, errors.New("bad packet format i") } ikey := strutil.MakeCIString(ps.Key) if T.synced && T.parameters[ikey] == ps.Value { // already set - ctx.Cancel() - break + return packet[:0], nil } if T.parameters == nil { T.parameters = make(map[strutil.CIString]string) } T.parameters[ikey] = ps.Value } - return nil + return packet, nil } -var _ middleware.Middleware = (*Client)(nil) +var _ fed.Middleware = (*Client)(nil) diff --git a/lib/middleware/middlewares/ps/server.go b/lib/fed/middlewares/ps/server.go similarity index 68% rename from lib/middleware/middlewares/ps/server.go rename to lib/fed/middlewares/ps/server.go index dc11a6e14d8eeb44d87dec7313859f314cb06afd..f74f4f0c8c2c15414a34524984b83d313cc53159 100644 --- a/lib/middleware/middlewares/ps/server.go +++ b/lib/fed/middlewares/ps/server.go @@ -5,7 +5,6 @@ import ( "gfx.cafe/gfx/pggat/lib/fed" packets "gfx.cafe/gfx/pggat/lib/fed/packets/v3.0" - "gfx.cafe/gfx/pggat/lib/middleware" "gfx.cafe/gfx/pggat/lib/util/strutil" ) @@ -19,12 +18,12 @@ func NewServer(parameters map[strutil.CIString]string) *Server { } } -func (T *Server) Read(_ middleware.Context, packet fed.Packet) error { +func (T *Server) ReadPacket(packet fed.Packet) (fed.Packet, error) { switch packet.Type() { case packets.TypeParameterStatus: var ps packets.ParameterStatus if !ps.ReadFromPacket(packet) { - return errors.New("bad packet format j") + return packet, errors.New("bad packet format j") } ikey := strutil.MakeCIString(ps.Key) if T.parameters == nil { @@ -32,11 +31,11 @@ func (T *Server) Read(_ middleware.Context, packet fed.Packet) error { } T.parameters[ikey] = ps.Value } - return nil + return packet, nil } -func (T *Server) Write(_ middleware.Context, _ fed.Packet) error { - return nil +func (T *Server) WritePacket(packet fed.Packet) (fed.Packet, error) { + return packet, nil } -var _ middleware.Middleware = (*Server)(nil) +var _ fed.Middleware = (*Server)(nil) diff --git a/lib/middleware/middlewares/ps/sync.go b/lib/fed/middlewares/ps/sync.go similarity index 76% rename from lib/middleware/middlewares/ps/sync.go rename to lib/fed/middlewares/ps/sync.go index 4910ae52412e127b36f8c3eb54fc63fed4aab973..be296b9c3421570d64d5d46460fdf623419c5325 100644 --- a/lib/middleware/middlewares/ps/sync.go +++ b/lib/fed/middlewares/ps/sync.go @@ -8,7 +8,7 @@ import ( "gfx.cafe/gfx/pggat/lib/util/strutil" ) -func sync(tracking []strutil.CIString, client fed.ReadWriter, c *Client, server fed.ReadWriter, s *Server, name strutil.CIString) error { +func sync(tracking []strutil.CIString, client *fed.Conn, c *Client, server *fed.Conn, s *Server, name strutil.CIString) error { value, hasValue := c.parameters[name] expected, hasExpected := s.parameters[name] @@ -31,14 +31,10 @@ func sync(tracking []strutil.CIString, client fed.ReadWriter, c *Client, server var doSet bool if hasValue && slices.Contains(tracking, name) { - ctx := backends.Context{ - Packet: packet, - Server: server, - } - if err := backends.SetParameter(&ctx, name, value); err != nil { + var err error + if err, _, packet = backends.SetParameter(server, nil, packet, name, value); err != nil { return err } - packet = ctx.Packet if s.parameters == nil { s.parameters = make(map[strutil.CIString]string) } @@ -63,7 +59,7 @@ func sync(tracking []strutil.CIString, client fed.ReadWriter, c *Client, server return nil } -func Sync(tracking []strutil.CIString, client fed.ReadWriter, c *Client, server fed.ReadWriter, s *Server) (clientErr, serverErr error) { +func Sync(tracking []strutil.CIString, client *fed.Conn, c *Client, server *fed.Conn, s *Server) (clientErr, serverErr error) { for name := range c.parameters { if serverErr = sync(tracking, client, c, server, s, name); serverErr != nil { return diff --git a/lib/middleware/middlewares/unterminate/unterminate.go b/lib/fed/middlewares/unterminate/unterminate.go similarity index 60% rename from lib/middleware/middlewares/unterminate/unterminate.go rename to lib/fed/middlewares/unterminate/unterminate.go index dcd27cfd12fea58f3c58dd28f742478b7490d98d..f04fb06b5e66dd9556aad67f44255e4aa6d45129 100644 --- a/lib/middleware/middlewares/unterminate/unterminate.go +++ b/lib/fed/middlewares/unterminate/unterminate.go @@ -5,7 +5,6 @@ import ( "gfx.cafe/gfx/pggat/lib/fed" packets "gfx.cafe/gfx/pggat/lib/fed/packets/v3.0" - "gfx.cafe/gfx/pggat/lib/middleware" ) // Unterminate catches the Terminate packet and returns io.EOF instead. @@ -14,15 +13,15 @@ var Unterminate = unterm{} type unterm struct{} -func (unterm) Read(_ middleware.Context, packet fed.Packet) error { +func (unterm) ReadPacket(packet fed.Packet) (fed.Packet, error) { if packet.Type() == packets.TypeTerminate { - return io.EOF + return packet, io.EOF } - return nil + return packet, nil } -func (unterm) Write(_ middleware.Context, _ fed.Packet) error { - return nil +func (unterm) WritePacket(packet fed.Packet) (fed.Packet, error) { + return packet, nil } -var _ middleware.Middleware = unterm{} +var _ fed.Middleware = unterm{} diff --git a/lib/fed/netconn.go b/lib/fed/netconn.go new file mode 100644 index 0000000000000000000000000000000000000000..480edb12b2c66cca2eea13888daac31ca226c404 --- /dev/null +++ b/lib/fed/netconn.go @@ -0,0 +1,144 @@ +package fed + +import ( + "bufio" + "crypto/tls" + "encoding/binary" + "errors" + "io" + "net" + + "gfx.cafe/gfx/pggat/lib/util/slices" +) + +type NetConn struct { + conn net.Conn + writer bufio.Writer + reader bufio.Reader + sslEnabled bool + + headerBuf [5]byte +} + +func NewNetConn(conn net.Conn) *NetConn { + c := &NetConn{ + conn: conn, + } + c.writer.Reset(conn) + c.reader.Reset(conn) + return c +} + +func (T *NetConn) LocalAddr() net.Addr { + return T.conn.LocalAddr() +} + +func (T *NetConn) RemoteAddr() net.Addr { + return T.conn.RemoteAddr() +} + +// SSL + +var errSSLAlreadyEnabled = errors.New("ssl is already enabled") + +func (T *NetConn) SSL() bool { + return T.sslEnabled +} + +func (T *NetConn) EnableSSLClient(config *tls.Config) error { + if T.sslEnabled { + return errSSLAlreadyEnabled + } + T.sslEnabled = true + + if err := T.writer.Flush(); err != nil { + return err + } + if T.reader.Buffered() > 0 { + return errors.New("expected empty read buffer") + } + sslConn := tls.Client(T.conn, config) + T.writer.Reset(sslConn) + T.reader.Reset(sslConn) + T.conn = sslConn + return sslConn.Handshake() +} + +func (T *NetConn) EnableSSLServer(config *tls.Config) error { + if T.sslEnabled { + return errSSLAlreadyEnabled + } + T.sslEnabled = true + + if err := T.writer.Flush(); err != nil { + return err + } + if T.reader.Buffered() > 0 { + return errors.New("expected empty read buffer") + } + sslConn := tls.Server(T.conn, config) + T.writer.Reset(sslConn) + T.reader.Reset(sslConn) + T.conn = sslConn + return sslConn.Handshake() +} + +func (T *NetConn) ReadByte() (byte, error) { + if err := T.writer.Flush(); err != nil { + return 0, err + } + return T.reader.ReadByte() +} + +func (T *NetConn) ReadPacket(typed bool, buffer Packet) (packet Packet, err error) { + packet = buffer + + if err = T.writer.Flush(); err != nil { + return + } + + if typed { + _, err = io.ReadFull(&T.reader, T.headerBuf[:]) + if err != nil { + return + } + } else { + _, err = io.ReadFull(&T.reader, T.headerBuf[1:]) + if err != nil { + return + } + } + + length := binary.BigEndian.Uint32(T.headerBuf[1:]) + + packet = slices.Resize(buffer, int(length)+1) + copy(packet, T.headerBuf[:]) + + _, err = io.ReadFull(&T.reader, packet.Payload()) + if err != nil { + return + } + return +} + +func (T *NetConn) WriteByte(b byte) error { + return T.writer.WriteByte(b) +} + +func (T *NetConn) WritePacket(packet Packet) error { + _, err := T.writer.Write(packet.Bytes()) + return err +} + +func (T *NetConn) Close() error { + if err := T.writer.Flush(); err != nil { + return err + } + return T.conn.Close() +} + +var _ ReadWriteCloser = (*NetConn)(nil) +var _ SSLServer = (*NetConn)(nil) +var _ SSLClient = (*NetConn)(nil) +var _ io.ByteReader = (*NetConn)(nil) +var _ io.ByteWriter = (*NetConn)(nil) diff --git a/lib/fed/readwriter.go b/lib/fed/readwriter.go index e26d66f933db5e2e3368ace6f4ab398330c98f2e..bde9a0d17f45d0b48b682e4c87d2108b419ff9dd 100644 --- a/lib/fed/readwriter.go +++ b/lib/fed/readwriter.go @@ -13,7 +13,8 @@ type ReadWriter interface { Writer } -type CombinedReadWriter struct { - Reader - Writer +type ReadWriteCloser interface { + ReadWriter + + Close() error } diff --git a/lib/fed/ssl.go b/lib/fed/ssl.go index 580d83f983f4a5696610a2b643a5f8c9dec0a3e2..9b830d23ded6eef3b32461fb91053ec67a3d61ae 100644 --- a/lib/fed/ssl.go +++ b/lib/fed/ssl.go @@ -2,10 +2,18 @@ package fed import "crypto/tls" +type SSL interface { + SSL() bool +} + type SSLClient interface { + SSL + EnableSSLClient(config *tls.Config) error } type SSLServer interface { + SSL + EnableSSLServer(config *tls.Config) error } diff --git a/lib/gat/app.go b/lib/gat/app.go new file mode 100644 index 0000000000000000000000000000000000000000..2bb1a58f93f78afe42bcb955927bf3b593236394 --- /dev/null +++ b/lib/gat/app.go @@ -0,0 +1,106 @@ +package gat + +import ( + "time" + + "github.com/caddyserver/caddy/v2" + "go.uber.org/zap" + + "gfx.cafe/gfx/pggat/lib/gat/metrics" + "gfx.cafe/gfx/pggat/lib/util/dur" +) + +type Config struct { + StatLogPeriod dur.Duration `json:"stat_log_period,omitempty"` + Servers []ServerConfig `json:"servers,omitempty"` +} + +func init() { + caddy.RegisterModule((*App)(nil)) +} + +type App struct { + Config + + servers []*Server + + closed chan struct{} + + log *zap.Logger +} + +func (T *App) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat", + New: func() caddy.Module { + return new(App) + }, + } +} + +func (T *App) Provision(ctx caddy.Context) error { + T.log = ctx.Logger() + + T.servers = make([]*Server, 0, len(T.Servers)) + for _, config := range T.Servers { + server := &Server{ + ServerConfig: config, + } + if err := server.Provision(ctx); err != nil { + return err + } + T.servers = append(T.servers, server) + } + + return nil +} + +func (T *App) statLogLoop() { + t := time.NewTicker(T.StatLogPeriod.Duration()) + defer t.Stop() + + var stats metrics.Server + for { + select { + case <-t.C: + for _, server := range T.servers { + server.ReadMetrics(&stats) + } + T.log.Info(stats.String()) + stats.Clear() + case <-T.closed: + return + } + } +} + +func (T *App) Start() error { + T.closed = make(chan struct{}) + if T.StatLogPeriod != 0 { + go T.statLogLoop() + } + + for _, server := range T.servers { + if err := server.Start(); err != nil { + return err + } + } + + return nil +} + +func (T *App) Stop() error { + close(T.closed) + + for _, server := range T.servers { + if err := server.Stop(); err != nil { + return err + } + } + + return nil +} + +var _ caddy.Module = (*App)(nil) +var _ caddy.Provisioner = (*App)(nil) +var _ caddy.App = (*App)(nil) diff --git a/lib/gat/endpoint.go b/lib/gat/endpoint.go deleted file mode 100644 index 269f4a8af118222c03efbde114bedf4aaaecae5e..0000000000000000000000000000000000000000 --- a/lib/gat/endpoint.go +++ /dev/null @@ -1,12 +0,0 @@ -package gat - -import "gfx.cafe/gfx/pggat/lib/bouncer/frontends/v0" - -type FrontendAcceptOptions = frontends.AcceptOptions - -type Endpoint struct { - Network string - Address string - - AcceptOptions FrontendAcceptOptions -} diff --git a/lib/gat/gatcaddyfile/README.md b/lib/gat/gatcaddyfile/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4a2141f8dd7c9acc6b39ec238d4f22f2304272be --- /dev/null +++ b/lib/gat/gatcaddyfile/README.md @@ -0,0 +1,9 @@ +## Server Blocks +Server blocks will be matched in order by their keys. Different SSL configurations may not be used on the same host port pair +(due to technical limitations). + +## Directives +| Directive | Description | +|-----------|-----------------------------------| +| ssl | ssl configuration for this server | +| {handler} | each handler will be run in order | diff --git a/lib/gat/gatcaddyfile/discoverer.go b/lib/gat/gatcaddyfile/discoverer.go new file mode 100644 index 0000000000000000000000000000000000000000..f580483395ffea33442d8b359dfe911e8ceff7d1 --- /dev/null +++ b/lib/gat/gatcaddyfile/discoverer.go @@ -0,0 +1,65 @@ +package gatcaddyfile + +import ( + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + + "gfx.cafe/gfx/pggat/lib/gat/handlers/discovery/discoverers/digitalocean" + "gfx.cafe/gfx/pggat/lib/gat/handlers/discovery/discoverers/google_cloud_sql" + "gfx.cafe/gfx/pggat/lib/gat/handlers/discovery/discoverers/zalando_operator" +) + +func init() { + RegisterDirective(Discoverer, "digitalocean", func(d *caddyfile.Dispenser, warnings *[]caddyconfig.Warning) (caddy.Module, error) { + if !d.NextArg() { + return nil, d.ArgErr() + } + + apiKey := d.Val() + + return &digitalocean.Discoverer{ + Config: digitalocean.Config{ + APIKey: apiKey, + }, + }, nil + }) + RegisterDirective(Discoverer, "google_cloud_sql", func(d *caddyfile.Dispenser, warnings *[]caddyconfig.Warning) (caddy.Module, error) { + module := google_cloud_sql.Discoverer{ + Config: google_cloud_sql.Config{ + IpAddressType: "PRIMARY", + }, + } + + if !d.NextArg() { + return nil, d.ArgErr() + } + module.Project = d.Val() + + if !d.NextArg() { + return nil, d.ArgErr() + } + module.AuthUser = d.Val() + + if !d.NextArg() { + return nil, d.ArgErr() + } + module.AuthPassword = d.Val() + + return &module, nil + }) + RegisterDirective(Discoverer, "zalando_operator", func(d *caddyfile.Dispenser, warnings *[]caddyconfig.Warning) (caddy.Module, error) { + module := zalando_operator.Discoverer{ + Config: zalando_operator.Config{ + Namespace: "default", + }, + } + + if !d.NextArg() { + return nil, d.ArgErr() + } + module.OperatorConfigurationObject = d.Val() + + return &module, nil + }) +} diff --git a/lib/gat/gatcaddyfile/gattype.go b/lib/gat/gatcaddyfile/gattype.go new file mode 100644 index 0000000000000000000000000000000000000000..6800adc05823b9ec542606ab6d411210ff8f3f1b --- /dev/null +++ b/lib/gat/gatcaddyfile/gattype.go @@ -0,0 +1,253 @@ +package gatcaddyfile + +import ( + "encoding/json" + "strings" + "time" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + + "gfx.cafe/gfx/pggat/lib/gat" + "gfx.cafe/gfx/pggat/lib/gat/matchers" + "gfx.cafe/gfx/pggat/lib/gat/ssl/servers/self_signed" + "gfx.cafe/gfx/pggat/lib/util/dur" +) + +func init() { + caddyconfig.RegisterAdapter("caddyfile", caddyfile.Adapter{ServerType: ServerType{}}) +} + +type ServerType struct{} + +func (ServerType) Setup(blocks []caddyfile.ServerBlock, m map[string]any) (*caddy.Config, []caddyconfig.Warning, error) { + var config caddy.Config + var warnings []caddyconfig.Warning + + app := gat.App{ + Config: gat.Config{ + StatLogPeriod: dur.Duration(1 * time.Minute), + }, + } + + for i, block := range blocks { + if i == 0 && len(block.Keys) == 0 { + // global options + for _, segment := range block.Segments { + d := caddyfile.NewDispenser(segment) + if !d.Next() { + continue + } + directive := d.Val() + switch { + case directive == "stat_log_period": + if !d.NextArg() { + return nil, nil, d.ArgErr() + } + + period, err := time.ParseDuration(d.Val()) + if err != nil { + return nil, nil, d.WrapErr(err) + } + + app.StatLogPeriod = dur.Duration(period) + default: + return nil, nil, d.SyntaxErr("global options") + } + + if d.CountRemainingArgs() > 0 { + return nil, nil, d.ArgErr() + } + } + + continue + } + + var server gat.ServerConfig + + server.Listen = make([]gat.ListenerConfig, 0, len(block.Keys)) + for _, key := range block.Keys { + listen := gat.ListenerConfig{ + Address: key, + } + server.Listen = append(server.Listen, listen) + } + + var namedMatchers map[string]json.RawMessage + + for _, segment := range block.Segments { + d := caddyfile.NewDispenser(segment) + if !d.Next() { + continue + } + directive := d.Val() + switch { + case directive == "ssl": + var val json.RawMessage + if !d.NextArg() { + // self signed ssl + val = caddyconfig.JSONModuleObject( + self_signed.Server{}, + "provider", + "self_signed", + &warnings, + ) + } else { + var err error + val, err = UnmarshalDirectiveJSONModuleObject( + d, + SSLServer, + "provider", + &warnings, + ) + if err != nil { + return nil, nil, err + } + } + + // set + for i := range server.Listen { + if server.Listen[i].SSL != nil { + return nil, nil, d.Err("duplicate ssl directive") + } + server.Listen[i].SSL = val + } + + if d.CountRemainingArgs() > 0 { + return nil, nil, d.ArgErr() + } + case strings.HasPrefix(directive, "@"): + name := strings.TrimPrefix(directive, "@") + if _, ok := namedMatchers[name]; ok { + return nil, nil, d.Errf(`duplicate named matcher "%s"`, name) + } + + var matcher json.RawMessage + + // read named matcher + if d.NextArg() { + // inline + var err error + matcher, err = UnmarshalDirectiveJSONModuleObject( + d, + Matcher, + "matcher", + &warnings, + ) + if err != nil { + return nil, nil, err + } + } else { + // block + if !d.NextBlock(0) { + return nil, nil, d.ArgErr() + } + + var and matchers.And + + for { + if d.Val() == "}" { + break + } + + cond, err := UnmarshalDirectiveJSONModuleObject( + d, + Matcher, + "matcher", + &warnings, + ) + if err != nil { + return nil, nil, err + } + and.And = append(and.And, cond) + + if !d.NextLine() { + return nil, nil, d.EOFErr() + } + } + + if len(and.And) == 0 { + matcher = nil + } else if len(and.And) == 1 { + matcher = and.And[0] + } else { + matcher = caddyconfig.JSONModuleObject( + and, + Matcher, + "matcher", + &warnings, + ) + } + } + + if d.CountRemainingArgs() > 0 { + return nil, nil, d.ArgErr() + } + + if namedMatchers == nil { + namedMatchers = make(map[string]json.RawMessage) + } + namedMatchers[name] = matcher + default: + unmarshaller, ok := LookupDirective(Handler, d.Val()) + if !ok { + return nil, nil, d.Errf(`unknown handler "%s"`, d.Val()) + } + + var route gat.RouteConfig + + // try to read matcher + if d.NextArg() { + matcher := d.Val() + switch { + case strings.HasPrefix(matcher, "@"): // named matcher + route.Match, ok = namedMatchers[strings.TrimPrefix(matcher, "@")] + if !ok { + return nil, nil, d.Errf(`unknown named matcher "%s"`, matcher) + } + case strings.HasPrefix(matcher, "/"): // database + route.Match = caddyconfig.JSONModuleObject( + matchers.Database{ + Database: strings.TrimPrefix(matcher, "/"), + }, + "matcher", + "database", + &warnings, + ) + case matcher == "*": + route.Match = nil // wildcard + default: + d.Prev() + } + } + + var err error + route.Handle, err = unmarshaller.JSONModuleObject( + d, + Handler, + "handler", + &warnings, + ) + if err != nil { + return nil, nil, err + } + + if d.CountRemainingArgs() > 0 { + return nil, nil, d.ArgErr() + } + + server.Routes = append(server.Routes, route) + } + } + + app.Servers = append(app.Servers, server) + } + + if config.AppsRaw == nil { + config.AppsRaw = make(caddy.ModuleMap) + } + config.AppsRaw[string(app.CaddyModule().ID)] = caddyconfig.JSON(app, &warnings) + + return &config, warnings, nil +} diff --git a/lib/gat/gatcaddyfile/handler.go b/lib/gat/gatcaddyfile/handler.go new file mode 100644 index 0000000000000000000000000000000000000000..758478728e635faee8bf397e87390a27334d2cbf --- /dev/null +++ b/lib/gat/gatcaddyfile/handler.go @@ -0,0 +1,518 @@ +package gatcaddyfile + +import ( + "strconv" + "strings" + "time" + + error_handler "gfx.cafe/gfx/pggat/lib/gat/handlers/error" + "gfx.cafe/gfx/pggat/lib/gat/handlers/pgbouncer_spilo" + pool_handler "gfx.cafe/gfx/pggat/lib/gat/handlers/pool" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + + "gfx.cafe/gfx/pggat/lib/bouncer" + "gfx.cafe/gfx/pggat/lib/gat/handlers/allowed_startup_parameters" + "gfx.cafe/gfx/pggat/lib/gat/handlers/discovery" + "gfx.cafe/gfx/pggat/lib/gat/handlers/pgbouncer" + "gfx.cafe/gfx/pggat/lib/gat/handlers/require_ssl" + "gfx.cafe/gfx/pggat/lib/gat/handlers/rewrite_database" + "gfx.cafe/gfx/pggat/lib/gat/handlers/rewrite_parameter" + "gfx.cafe/gfx/pggat/lib/gat/handlers/rewrite_password" + "gfx.cafe/gfx/pggat/lib/gat/handlers/rewrite_user" + "gfx.cafe/gfx/pggat/lib/gat/poolers/transaction" + "gfx.cafe/gfx/pggat/lib/gat/ssl/clients/insecure_skip_verify" + "gfx.cafe/gfx/pggat/lib/util/dur" + "gfx.cafe/gfx/pggat/lib/util/strutil" +) + +func init() { + RegisterDirective(Handler, "require_ssl", func(d *caddyfile.Dispenser, _ *[]caddyconfig.Warning) (caddy.Module, error) { + var ssl = true + if d.NextArg() { + switch d.Val() { + case "true": + ssl = true + case "false": + ssl = false + default: + return nil, d.SyntaxErr("boolean") + } + } + return &require_ssl.Module{ + SSL: ssl, + }, nil + }) + RegisterDirective(Handler, "allowed_startup_parameters", func(d *caddyfile.Dispenser, _ *[]caddyconfig.Warning) (caddy.Module, error) { + if !d.NextBlock(d.Nesting()) { + return nil, d.ArgErr() + } + + var module allowed_startup_parameters.Module + + for { + if d.Val() == "}" { + break + } + + module.Parameters = append(module.Parameters, strutil.MakeCIString(d.Val())) + + if !d.NextLine() { + return nil, d.EOFErr() + } + } + + return &module, nil + }) + RegisterDirective(Handler, "user", func(d *caddyfile.Dispenser, _ *[]caddyconfig.Warning) (caddy.Module, error) { + if !d.NextArg() { + return nil, d.ArgErr() + } + + return &rewrite_user.Module{ + User: d.Val(), + }, nil + }) + RegisterDirective(Handler, "password", func(d *caddyfile.Dispenser, _ *[]caddyconfig.Warning) (caddy.Module, error) { + if !d.NextArg() { + return nil, d.ArgErr() + } + + return &rewrite_password.Module{ + Password: d.Val(), + }, nil + }) + RegisterDirective(Handler, "database", func(d *caddyfile.Dispenser, _ *[]caddyconfig.Warning) (caddy.Module, error) { + if !d.NextArg() { + return nil, d.ArgErr() + } + + return &rewrite_database.Module{ + Database: d.Val(), + }, nil + }) + RegisterDirective(Handler, "parameter", func(d *caddyfile.Dispenser, _ *[]caddyconfig.Warning) (caddy.Module, error) { + if !d.NextArg() { + return nil, d.ArgErr() + } + + keyValue := d.Val() + key, value, ok := strings.Cut(keyValue, "=") + if !ok { + return nil, d.SyntaxErr("key=value") + } + + return &rewrite_parameter.Module{ + Key: strutil.MakeCIString(key), + Value: value, + }, nil + }) + RegisterDirective(Handler, "error", func(d *caddyfile.Dispenser, _ *[]caddyconfig.Warning) (caddy.Module, error) { + if !d.NextArg() { + return nil, d.ArgErr() + } + + message := d.Val() + + return &error_handler.Module{ + Message: message, + }, nil + }) + RegisterDirective(Handler, "pgbouncer", func(d *caddyfile.Dispenser, _ *[]caddyconfig.Warning) (caddy.Module, error) { + var config = "pgbouncer.ini" + if d.NextArg() { + config = d.Val() + } + return &pgbouncer.Module{ + ConfigFile: config, + }, nil + }) + RegisterDirective(Handler, "discovery", func(d *caddyfile.Dispenser, warnings *[]caddyconfig.Warning) (caddy.Module, error) { + module := discovery.Module{ + Config: discovery.Config{ + ReconcilePeriod: dur.Duration(5 * time.Minute), + Pooler: JSONModuleObject( + &transaction.Module{ + ManagementConfig: defaultPoolManagementConfig, + }, + Pooler, + "pooler", + warnings, + ), + ServerSSLMode: bouncer.SSLModePrefer, + ServerSSL: JSONModuleObject( + &insecure_skip_verify.Client{}, + SSLClient, + "provider", + warnings, + ), + }, + } + + if d.NextArg() { + // discoverer + var err error + module.Discoverer, err = UnmarshalDirectiveJSONModuleObject( + d, + Discoverer, + "discoverer", + warnings, + ) + + if err != nil { + return nil, err + } + } else { + if !d.NextBlock(d.Nesting()) { + return nil, d.ArgErr() + } + + for { + if d.Val() == "}" { + break + } + + directive := d.Val() + switch directive { + case "reconcile_period": + if !d.NextArg() { + return nil, d.ArgErr() + } + + val, err := time.ParseDuration(d.Val()) + if err != nil { + return nil, d.WrapErr(err) + } + module.ReconcilePeriod = dur.Duration(val) + case "discoverer": + if !d.NextArg() { + return nil, d.ArgErr() + } + + var err error + module.Discoverer, err = UnmarshalDirectiveJSONModuleObject( + d, + Discoverer, + "discoverer", + warnings, + ) + if err != nil { + return nil, err + } + case "pooler": + if !d.NextArg() { + return nil, d.ArgErr() + } + + var err error + module.Pooler, err = UnmarshalDirectiveJSONModuleObject( + d, + Pooler, + "pooler", + warnings, + ) + if err != nil { + return nil, err + } + case "ssl": + if !d.NextArg() { + return nil, d.ArgErr() + } + + module.ServerSSLMode = bouncer.SSLMode(d.Val()) + + if !d.NextArg() { + return nil, d.ArgErr() + } + + var err error + module.ServerSSL, err = UnmarshalDirectiveJSONModuleObject( + d, + SSLClient, + "provider", + warnings, + ) + if err != nil { + return nil, err + } + case "parameter": + if !d.NextArg() { + return nil, d.ArgErr() + } + + keyValue := d.Val() + key, value, ok := strings.Cut(keyValue, "=") + if !ok { + return nil, d.SyntaxErr("key=value") + } + if module.ServerStartupParameters == nil { + module.ServerStartupParameters = make(map[string]string) + } + module.ServerStartupParameters[key] = value + default: + return nil, d.ArgErr() + } + + if !d.NextLine() { + return nil, d.EOFErr() + } + } + } + + return &module, nil + }) + RegisterDirective(Handler, "pgbouncer_spilo", func(d *caddyfile.Dispenser, warnings *[]caddyconfig.Warning) (caddy.Module, error) { + module := pgbouncer_spilo.Module{} + + if !d.NextBlock(d.Nesting()) { + return nil, d.ArgErr() + } + + for { + if d.Val() == "}" { + break + } + + directive := d.Val() + switch directive { + case "host": + if !d.NextArg() { + return nil, d.ArgErr() + } + + module.Host = d.Val() + case "port": + if !d.NextArg() { + return nil, d.ArgErr() + } + + var err error + module.Port, err = strconv.Atoi(d.Val()) + if err != nil { + return nil, d.WrapErr(err) + } + case "user": + if !d.NextArg() { + return nil, d.ArgErr() + } + + module.User = d.Val() + case "schema": + if !d.NextArg() { + return nil, d.ArgErr() + } + + module.Schema = d.Val() + case "password": + if !d.NextArg() { + return nil, d.ArgErr() + } + + module.Password = d.Val() + case "mode": + if !d.NextArg() { + return nil, d.ArgErr() + } + + module.Mode = d.Val() + case "default_size": + if !d.NextArg() { + return nil, d.ArgErr() + } + + var err error + module.DefaultSize, err = strconv.Atoi(d.Val()) + if err != nil { + return nil, d.WrapErr(err) + } + case "min_size": + if !d.NextArg() { + return nil, d.ArgErr() + } + + var err error + module.MinSize, err = strconv.Atoi(d.Val()) + if err != nil { + return nil, d.WrapErr(err) + } + case "reserve_size": + if !d.NextArg() { + return nil, d.ArgErr() + } + + var err error + module.ReserveSize, err = strconv.Atoi(d.Val()) + if err != nil { + return nil, d.WrapErr(err) + } + case "max_client_conn": + if !d.NextArg() { + return nil, d.ArgErr() + } + + var err error + module.MaxClientConn, err = strconv.Atoi(d.Val()) + if err != nil { + return nil, d.WrapErr(err) + } + case "max_db_conn": + if !d.NextArg() { + return nil, d.ArgErr() + } + + var err error + module.MaxDBConn, err = strconv.Atoi(d.Val()) + if err != nil { + return nil, d.WrapErr(err) + } + default: + return nil, d.ArgErr() + } + + if !d.NextLine() { + return nil, d.EOFErr() + } + } + + return &module, nil + }) + RegisterDirective(Handler, "pool", func(d *caddyfile.Dispenser, warnings *[]caddyconfig.Warning) (caddy.Module, error) { + module := pool_handler.Module{ + Config: pool_handler.Config{ + Pooler: JSONModuleObject( + &transaction.Module{ + ManagementConfig: defaultPoolManagementConfig, + }, + Pooler, + "pooler", + warnings, + ), + + ServerSSLMode: bouncer.SSLModePrefer, + ServerSSL: JSONModuleObject( + &insecure_skip_verify.Client{}, + SSLClient, + "provider", + warnings, + ), + }, + } + + if d.NextArg() { + module.ServerAddress = d.Val() + + if !d.NextArg() { + return nil, d.ArgErr() + } + module.ServerDatabase = d.Val() + + if !d.NextArg() { + return nil, d.ArgErr() + } + module.ServerUsername = d.Val() + + if !d.NextArg() { + return nil, d.ArgErr() + } + module.ServerPassword = d.Val() + } else { + if !d.NextBlock(d.Nesting()) { + return nil, d.ArgErr() + } + + for { + if d.Val() == "}" { + break + } + + directive := d.Val() + switch directive { + case "pooler": + if !d.NextArg() { + return nil, d.ArgErr() + } + + var err error + module.Pooler, err = UnmarshalDirectiveJSONModuleObject( + d, + Pooler, + "pooler", + warnings, + ) + if err != nil { + return nil, err + } + case "address": + if !d.NextArg() { + return nil, d.ArgErr() + } + + module.ServerAddress = d.Val() + case "ssl": + if !d.NextArg() { + return nil, d.ArgErr() + } + + module.ServerSSLMode = bouncer.SSLMode(d.Val()) + + if !d.NextArg() { + return nil, d.ArgErr() + } + + var err error + module.ServerSSL, err = UnmarshalDirectiveJSONModuleObject( + d, + SSLClient, + "provider", + warnings, + ) + if err != nil { + return nil, err + } + case "username": + if !d.NextArg() { + return nil, d.ArgErr() + } + + module.ServerUsername = d.Val() + case "password": + if !d.NextArg() { + return nil, d.ArgErr() + } + + module.ServerPassword = d.Val() + case "database": + if !d.NextArg() { + return nil, d.ArgErr() + } + + module.ServerDatabase = d.Val() + case "parameter": + if !d.NextArg() { + return nil, d.ArgErr() + } + + keyValue := d.Val() + key, value, ok := strings.Cut(keyValue, "=") + if !ok { + return nil, d.SyntaxErr("key=value") + } + if module.ServerStartupParameters == nil { + module.ServerStartupParameters = make(map[string]string) + } + module.ServerStartupParameters[key] = value + default: + return nil, d.ArgErr() + } + + if !d.NextLine() { + return nil, d.EOFErr() + } + } + } + + return &module, nil + }) +} diff --git a/lib/gat/gatcaddyfile/matcher.go b/lib/gat/gatcaddyfile/matcher.go new file mode 100644 index 0000000000000000000000000000000000000000..07e6822f6b2f350fb92f0ac5bb0cf420e49302a1 --- /dev/null +++ b/lib/gat/gatcaddyfile/matcher.go @@ -0,0 +1,194 @@ +package gatcaddyfile + +import ( + "encoding/json" + "strings" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + + "gfx.cafe/gfx/pggat/lib/gat/matchers" + "gfx.cafe/gfx/pggat/lib/util/strutil" +) + +func init() { + RegisterDirective(Matcher, "user", func(d *caddyfile.Dispenser, _ *[]caddyconfig.Warning) (caddy.Module, error) { + if !d.NextArg() { + return nil, d.ArgErr() + } + user := d.Val() + return &matchers.User{ + User: user, + }, nil + }) + RegisterDirective(Matcher, "database", func(d *caddyfile.Dispenser, _ *[]caddyconfig.Warning) (caddy.Module, error) { + if !d.NextArg() { + return nil, d.ArgErr() + } + database := d.Val() + return &matchers.Database{ + Database: database, + }, nil + }) + RegisterDirective(Matcher, "local_address", func(d *caddyfile.Dispenser, _ *[]caddyconfig.Warning) (caddy.Module, error) { + if !d.NextArg() { + return nil, d.ArgErr() + } + address := d.Val() + var network string + if strings.HasPrefix(address, "/") { + network = "unix" + } else { + network = "tcp" + } + return &matchers.LocalAddress{ + Network: network, + Address: address, + }, nil + }) + RegisterDirective(Matcher, "parameter", func(d *caddyfile.Dispenser, _ *[]caddyconfig.Warning) (caddy.Module, error) { + if !d.NextArg() { + return nil, d.ArgErr() + } + keyValue := d.Val() + key, value, ok := strings.Cut(keyValue, "=") + if !ok { + return nil, d.SyntaxErr("key=value") + } + return &matchers.StartupParameter{ + Key: strutil.MakeCIString(key), + Value: value, + }, nil + }) + RegisterDirective(Matcher, "ssl", func(d *caddyfile.Dispenser, _ *[]caddyconfig.Warning) (caddy.Module, error) { + var ssl = true + if d.NextArg() { + val := d.Val() + switch val { + case "true": + ssl = true + case "false": + ssl = false + default: + return nil, d.SyntaxErr("boolean") + } + } + + return &matchers.SSL{ + SSL: ssl, + }, nil + }) + RegisterDirective(Matcher, "not", func(d *caddyfile.Dispenser, warnings *[]caddyconfig.Warning) (caddy.Module, error) { + if !d.NextArg() { + return nil, d.SyntaxErr("matcher directive") + } + + matcher, err := UnmarshalDirectiveJSONModuleObject( + d, + Matcher, + "matcher", + warnings, + ) + if err != nil { + return nil, err + } + + return &matchers.Not{ + Not: matcher, + }, nil + }) + RegisterDirective(Matcher, "and", func(d *caddyfile.Dispenser, warnings *[]caddyconfig.Warning) (caddy.Module, error) { + if !d.NextBlock(d.Nesting()) { + return nil, d.ArgErr() + } + + var and []caddy.Module + + for { + if d.Val() == "}" { + break + } + + unmarshaller, ok := LookupDirective(Matcher, d.Val()) + if !ok { + return nil, d.Errf(`unknown matcher "%s"`, d.Val()) + } + + val, err := unmarshaller(d, warnings) + if err != nil { + return nil, err + } + and = append(and, val) + + if !d.NextLine() { + return nil, d.EOFErr() + } + } + + if len(and) == 0 { + return nil, nil + } + if len(and) == 1 { + return and[0], nil + } + + var raw = make([]json.RawMessage, 0, len(and)) + for _, val := range and { + raw = append(raw, JSONModuleObject( + val, + Matcher, + "matcher", + warnings, + )) + } + return &matchers.And{ + And: raw, + }, nil + }) + RegisterDirective(Matcher, "or", func(d *caddyfile.Dispenser, warnings *[]caddyconfig.Warning) (caddy.Module, error) { + if !d.NextBlock(d.Nesting()) { + return nil, d.ArgErr() + } + + var or []caddy.Module + + for { + if d.Val() == "}" { + break + } + + unmarshaller, ok := LookupDirective(Matcher, d.Val()) + if !ok { + return nil, d.Errf(`unknown matcher "%s"`, d.Val()) + } + + val, err := unmarshaller(d, warnings) + if err != nil { + return nil, err + } + or = append(or, val) + + if !d.NextLine() { + return nil, d.EOFErr() + } + } + + if len(or) == 1 { + return or[0], nil + } + + var raw = make([]json.RawMessage, 0, len(or)) + for _, val := range or { + raw = append(raw, JSONModuleObject( + val, + Matcher, + "matcher", + warnings, + )) + } + return &matchers.Or{ + Or: raw, + }, nil + }) +} diff --git a/lib/gat/gatcaddyfile/pooler.go b/lib/gat/gatcaddyfile/pooler.go new file mode 100644 index 0000000000000000000000000000000000000000..e5e950968c1387b6a3e81cae49d4a3527294893b --- /dev/null +++ b/lib/gat/gatcaddyfile/pooler.go @@ -0,0 +1,115 @@ +package gatcaddyfile + +import ( + "time" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + + "gfx.cafe/gfx/pggat/lib/gat/pool" + "gfx.cafe/gfx/pggat/lib/gat/poolers/session" + "gfx.cafe/gfx/pggat/lib/gat/poolers/transaction" + "gfx.cafe/gfx/pggat/lib/util/dur" + "gfx.cafe/gfx/pggat/lib/util/strutil" +) + +var defaultPoolManagementConfig = pool.ManagementConfig{ + ServerIdleTimeout: dur.Duration(5 * time.Minute), + ServerReconnectInitialTime: dur.Duration(5 * time.Second), + ServerReconnectMaxTime: dur.Duration(1 * time.Minute), + TrackedParameters: []strutil.CIString{ + strutil.MakeCIString("client_encoding"), + strutil.MakeCIString("datestyle"), + strutil.MakeCIString("timezone"), + strutil.MakeCIString("standard_conforming_strings"), + strutil.MakeCIString("application_name"), + }, +} + +func unmarshalPoolConfig(d *caddyfile.Dispenser) (pool.ManagementConfig, error) { + var config = defaultPoolManagementConfig + + if !d.NextBlock(d.Nesting()) { + return config, nil + } + + config.TrackedParameters = nil + + for { + if d.Val() == "}" { + break + } + + directive := d.Val() + switch directive { + case "idle_timeout": + if !d.NextArg() { + return config, d.ArgErr() + } + + val, err := time.ParseDuration(d.Val()) + if err != nil { + return config, d.WrapErr(err) + } + config.ServerIdleTimeout = dur.Duration(val) + case "reconnect": + if !d.NextArg() { + return config, d.ArgErr() + } + + initialTime, err := time.ParseDuration(d.Val()) + if err != nil { + return config, d.WrapErr(err) + } + + maxTime := initialTime + if d.NextArg() { + maxTime, err = time.ParseDuration(d.Val()) + if err != nil { + return config, d.WrapErr(err) + } + } + + config.ServerReconnectInitialTime = dur.Duration(initialTime) + config.ServerReconnectMaxTime = dur.Duration(maxTime) + case "track": + if !d.NextArg() { + return config, d.ArgErr() + } + + config.TrackedParameters = append(config.TrackedParameters, strutil.MakeCIString(d.Val())) + default: + return config, d.ArgErr() + } + + if !d.NextLine() { + return config, d.EOFErr() + } + } + + return config, nil +} + +func init() { + RegisterDirective(Pooler, "transaction", func(d *caddyfile.Dispenser, warnings *[]caddyconfig.Warning) (caddy.Module, error) { + config, err := unmarshalPoolConfig(d) + if err != nil { + return nil, err + } + + return &transaction.Module{ + ManagementConfig: config, + }, nil + }) + RegisterDirective(Pooler, "session", func(d *caddyfile.Dispenser, warnings *[]caddyconfig.Warning) (caddy.Module, error) { + config, err := unmarshalPoolConfig(d) + if err != nil { + return nil, err + } + + return &session.Module{ + ManagementConfig: config, + }, nil + }) +} diff --git a/lib/gat/gatcaddyfile/ssl.go b/lib/gat/gatcaddyfile/ssl.go new file mode 100644 index 0000000000000000000000000000000000000000..a4ae1063ea5c9ad340e212b9dd22972d23629dc5 --- /dev/null +++ b/lib/gat/gatcaddyfile/ssl.go @@ -0,0 +1,36 @@ +package gatcaddyfile + +import ( + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + + "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/gatcaddyfile/unmarshaller.go b/lib/gat/gatcaddyfile/unmarshaller.go new file mode 100644 index 0000000000000000000000000000000000000000..e75b67241eabbb60cd0afda77097bcc87492e540 --- /dev/null +++ b/lib/gat/gatcaddyfile/unmarshaller.go @@ -0,0 +1,58 @@ +package gatcaddyfile + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +type Unmarshaller func(*caddyfile.Dispenser, *[]caddyconfig.Warning) (caddy.Module, error) + +func (T Unmarshaller) JSONModuleObject( + d *caddyfile.Dispenser, + namespace string, + inlineKey string, + warnings *[]caddyconfig.Warning, +) (json.RawMessage, error) { + module, err := T(d, warnings) + if err != nil { + return nil, err + } + + return JSONModuleObject( + module, + namespace, + inlineKey, + warnings, + ), nil +} + +func JSONModuleObject( + module caddy.Module, + namespace string, + inlineKey string, + warnings *[]caddyconfig.Warning, +) json.RawMessage { + rawModuleID := string(module.CaddyModule().ID) + dotModuleID := strings.TrimPrefix(rawModuleID, namespace) + moduleID := strings.TrimPrefix(dotModuleID, ".") + if rawModuleID == dotModuleID || dotModuleID == moduleID { + if warnings != nil { + *warnings = append(*warnings, caddyconfig.Warning{ + Message: fmt.Sprintf(`expected item in namespace "%s" but got "%s"`, namespace, rawModuleID), + }) + } + return nil + } + + return caddyconfig.JSONModuleObject( + module, + inlineKey, + moduleID, + warnings, + ) +} diff --git a/lib/gat/gatcaddyfile/unmarshallers.go b/lib/gat/gatcaddyfile/unmarshallers.go new file mode 100644 index 0000000000000000000000000000000000000000..0d1f0cd75e45ff374721313958d0c3f431122d36 --- /dev/null +++ b/lib/gat/gatcaddyfile/unmarshallers.go @@ -0,0 +1,52 @@ +package gatcaddyfile + +import ( + "encoding/json" + "fmt" + + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + + "gfx.cafe/gfx/pggat/lib/util/maps" +) + +const ( + Discoverer = "pggat.handlers.discovery.discoverers" + Handler = "pggat.handlers" + Matcher = "pggat.matchers" + Pooler = "pggat.poolers" + SSLServer = "pggat.ssl.servers" + SSLClient = "pggat.ssl.clients" +) + +var unmarshallers maps.TwoKey[string, string, Unmarshaller] + +func RegisterDirective(namespace, directive string, unmarshaller Unmarshaller) { + if _, ok := unmarshallers.Load(namespace, directive); ok { + panic(fmt.Sprintf(`directive "%s" already exists`, directive)) + } + unmarshallers.Store(namespace, directive, unmarshaller) +} + +func LookupDirective(namespace, directive string) (Unmarshaller, bool) { + return unmarshallers.Load(namespace, directive) +} + +func UnmarshalDirectiveJSONModuleObject( + d *caddyfile.Dispenser, + namespace string, + inlineKey string, + warnings *[]caddyconfig.Warning, +) (json.RawMessage, error) { + unmarshaller, ok := LookupDirective(namespace, d.Val()) + if !ok { + return nil, d.Errf(`unknown directive in %s: "%s"`, namespace, d.Val()) + } + + return unmarshaller.JSONModuleObject( + d, + namespace, + inlineKey, + warnings, + ) +} diff --git a/lib/gat/handler.go b/lib/gat/handler.go new file mode 100644 index 0000000000000000000000000000000000000000..6026c1e6778260a9f25d5a2ce66e5b14677c051f --- /dev/null +++ b/lib/gat/handler.go @@ -0,0 +1,25 @@ +package gat + +import ( + "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/gat/metrics" +) + +// Handler handles the Conn +type Handler interface { + // Handle will attempt to handle the Conn. Return io.EOF for normal disconnection or nil to continue to the next + // handle. The error will be relayed to the client so there is no need to send it yourself. + Handle(conn *fed.Conn) error +} + +type CancellableHandler interface { + Handler + + Cancel(key [8]byte) +} + +type MetricsHandler interface { + Handler + + ReadMetrics(metrics *metrics.Handler) +} diff --git a/lib/gat/handlers/allowed_startup_parameters/module.go b/lib/gat/handlers/allowed_startup_parameters/module.go new file mode 100644 index 0000000000000000000000000000000000000000..f23ab20e767f24769ecd06b515263126b18c929b --- /dev/null +++ b/lib/gat/handlers/allowed_startup_parameters/module.go @@ -0,0 +1,47 @@ +package allowed_startup_parameters + +import ( + "fmt" + + "github.com/caddyserver/caddy/v2" + + "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/gat" + "gfx.cafe/gfx/pggat/lib/perror" + "gfx.cafe/gfx/pggat/lib/util/slices" + "gfx.cafe/gfx/pggat/lib/util/strutil" +) + +func init() { + caddy.RegisterModule((*Module)(nil)) +} + +type Module struct { + Parameters []strutil.CIString `json:"parameters"` +} + +func (T *Module) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.handlers.allowed_startup_parameters", + New: func() caddy.Module { + return new(Module) + }, + } +} + +func (T *Module) Handle(conn *fed.Conn) error { + for parameter := range conn.InitialParameters { + if !slices.Contains(T.Parameters, parameter) { + return perror.New( + perror.FATAL, + perror.FeatureNotSupported, + fmt.Sprintf(`Startup parameter "%s" is not supported`, parameter.String()), + ) + } + } + + return nil +} + +var _ gat.Handler = (*Module)(nil) +var _ caddy.Module = (*Module)(nil) diff --git a/lib/gat/modules/discovery/cluster.go b/lib/gat/handlers/discovery/cluster.go similarity index 100% rename from lib/gat/modules/discovery/cluster.go rename to lib/gat/handlers/discovery/cluster.go diff --git a/lib/gat/handlers/discovery/config.go b/lib/gat/handlers/discovery/config.go new file mode 100644 index 0000000000000000000000000000000000000000..b2712561ddabae7281b277222057eb05be92581d --- /dev/null +++ b/lib/gat/handlers/discovery/config.go @@ -0,0 +1,22 @@ +package discovery + +import ( + "encoding/json" + + "gfx.cafe/gfx/pggat/lib/bouncer" + "gfx.cafe/gfx/pggat/lib/util/dur" +) + +type Config struct { + // ReconcilePeriod is how often the module should check for changes. 0 = disable + ReconcilePeriod dur.Duration `json:"reconcile_period"` + + Discoverer json.RawMessage `json:"discoverer" caddy:"namespace=pggat.handlers.discovery.discoverers inline_key=discoverer"` + + Pooler json.RawMessage `json:"pooler" caddy:"namespace=pggat.poolers inline_key=pooler"` + + ServerSSLMode bouncer.SSLMode `json:"server_ssl_mode,omitempty"` + ServerSSL json.RawMessage `json:"server_ssl,omitempty" caddy:"namespace=pggat.ssl.clients inline_key=provider"` + + ServerStartupParameters map[string]string `json:"server_startup_parameters,omitempty"` +} diff --git a/lib/gat/modules/discovery/discoverer.go b/lib/gat/handlers/discovery/discoverer.go similarity index 60% rename from lib/gat/modules/discovery/discoverer.go rename to lib/gat/handlers/discovery/discoverer.go index 6ef7581783e1776fe3fd5f32c493d0aaa32b72f7..6fb8373387ae7640390735ecca3a28ba55312d54 100644 --- a/lib/gat/modules/discovery/discoverer.go +++ b/lib/gat/handlers/discovery/discoverer.go @@ -1,11 +1,10 @@ package discovery -// Discoverer looks up and returns the servers. It must implement either Clusters or Added, Updated, and Removed. -// Both can be implemented for extra robustness. +// Discoverer looks up and returns the servers. It must implement Clusters. Optionally, it can implement Added +// and Removed for faster updating. For updates, just send to Added. type Discoverer interface { Clusters() ([]Cluster, error) Added() <-chan Cluster - Updated() <-chan Cluster Removed() <-chan string } diff --git a/lib/gat/handlers/discovery/discoverers/digitalocean/config.go b/lib/gat/handlers/discovery/discoverers/digitalocean/config.go new file mode 100644 index 0000000000000000000000000000000000000000..ed367390677db01a922ca33afb0017fcf3b1ade5 --- /dev/null +++ b/lib/gat/handlers/discovery/discoverers/digitalocean/config.go @@ -0,0 +1,6 @@ +package digitalocean + +type Config struct { + APIKey string `json:"api_key"` + Private bool `json:"private"` +} diff --git a/lib/gat/modules/digitalocean_discovery/discoverer.go b/lib/gat/handlers/discovery/discoverers/digitalocean/discoverer.go similarity index 70% rename from lib/gat/modules/digitalocean_discovery/discoverer.go rename to lib/gat/handlers/discovery/discoverers/digitalocean/discoverer.go index 1e4009b013cd3fee56c2c4868ed6815722598c3b..bcafe2c4286f1a7ecfb6dca60fc0d05449c593f1 100644 --- a/lib/gat/modules/digitalocean_discovery/discoverer.go +++ b/lib/gat/handlers/discovery/discoverers/digitalocean/discoverer.go @@ -1,29 +1,41 @@ -package digitalocean_discovery +package digitalocean import ( "context" "net" "strconv" + "github.com/caddyserver/caddy/v2" "github.com/digitalocean/godo" - "gfx.cafe/gfx/pggat/lib/gat/modules/discovery" + "gfx.cafe/gfx/pggat/lib/gat/handlers/discovery" ) +func init() { + caddy.RegisterModule((*Discoverer)(nil)) +} + type Discoverer struct { - config Config + Config do *godo.Client } -func NewDiscoverer(config Config) (*Discoverer, error) { - return &Discoverer{ - config: config, - do: godo.NewFromToken(config.APIKey), - }, nil +func (T *Discoverer) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.handlers.discovery.discoverers.digitalocean", + New: func() caddy.Module { + return new(Discoverer) + }, + } } -func (T Discoverer) Clusters() ([]discovery.Cluster, error) { +func (T *Discoverer) Provision(ctx caddy.Context) error { + T.do = godo.NewFromToken(T.APIKey) + return nil +} + +func (T *Discoverer) Clusters() ([]discovery.Cluster, error) { clusters, _, err := T.do.Databases.List(context.Background(), nil) if err != nil { return nil, err @@ -36,7 +48,7 @@ func (T Discoverer) Clusters() ([]discovery.Cluster, error) { } var primaryAddr string - if T.config.Private { + if T.Private { primaryAddr = net.JoinHostPort(cluster.PrivateConnection.Host, strconv.Itoa(cluster.PrivateConnection.Port)) } else { primaryAddr = net.JoinHostPort(cluster.Connection.Host, strconv.Itoa(cluster.Connection.Port)) @@ -67,7 +79,7 @@ func (T Discoverer) Clusters() ([]discovery.Cluster, error) { c.Replicas = make(map[string]discovery.Endpoint, len(replicas)) for _, replica := range replicas { var replicaAddr string - if T.config.Private { + if T.Private { replicaAddr = net.JoinHostPort(replica.PrivateConnection.Host, strconv.Itoa(replica.PrivateConnection.Port)) } else { replicaAddr = net.JoinHostPort(replica.Connection.Host, strconv.Itoa(replica.Connection.Port)) @@ -84,16 +96,13 @@ func (T Discoverer) Clusters() ([]discovery.Cluster, error) { return res, nil } -func (T Discoverer) Added() <-chan discovery.Cluster { - return nil -} - -func (T Discoverer) Updated() <-chan discovery.Cluster { +func (T *Discoverer) Added() <-chan discovery.Cluster { return nil } -func (T Discoverer) Removed() <-chan string { +func (T *Discoverer) Removed() <-chan string { return nil } var _ discovery.Discoverer = (*Discoverer)(nil) +var _ caddy.Module = (*Discoverer)(nil) diff --git a/lib/gat/handlers/discovery/discoverers/google_cloud_sql/config.go b/lib/gat/handlers/discovery/discoverers/google_cloud_sql/config.go new file mode 100644 index 0000000000000000000000000000000000000000..00c664fc4c5fa412a4adb874e616e3372e0861ed --- /dev/null +++ b/lib/gat/handlers/discovery/discoverers/google_cloud_sql/config.go @@ -0,0 +1,8 @@ +package google_cloud_sql + +type Config struct { + Project string `json:"project"` + IpAddressType string `json:"ip_address_type"` + AuthUser string `json:"auth_user"` + AuthPassword string `json:"auth_password"` +} diff --git a/lib/gat/modules/cloud_sql_discovery/discoverer.go b/lib/gat/handlers/discovery/discoverers/google_cloud_sql/discoverer.go similarity index 71% rename from lib/gat/modules/cloud_sql_discovery/discoverer.go rename to lib/gat/handlers/discovery/discoverers/google_cloud_sql/discoverer.go index da7b6b44a9de0ad41a26f7d0723893fb09bbad2f..6bb9351b023cd1c14533778c68fe9c1a575b239d 100644 --- a/lib/gat/modules/cloud_sql_discovery/discoverer.go +++ b/lib/gat/handlers/discovery/discoverers/google_cloud_sql/discoverer.go @@ -1,19 +1,21 @@ -package cloud_sql_discovery +package google_cloud_sql import ( - "context" "crypto/tls" "net" "strings" + "gfx.cafe/gfx/pggat/lib/gat/pool/recipe" + + "github.com/caddyserver/caddy/v2" sqladmin "google.golang.org/api/sqladmin/v1beta4" + "gfx.cafe/gfx/pggat/lib/gat/handlers/discovery" + "gfx.cafe/gfx/pggat/lib/auth/credentials" "gfx.cafe/gfx/pggat/lib/bouncer" - "gfx.cafe/gfx/pggat/lib/bouncer/backends/v0" "gfx.cafe/gfx/pggat/lib/bouncer/bouncers/v2" "gfx.cafe/gfx/pggat/lib/fed" - "gfx.cafe/gfx/pggat/lib/gat/modules/discovery" "gfx.cafe/gfx/pggat/lib/gsql" ) @@ -22,28 +24,39 @@ type authQueryResult struct { Password string `sql:"1"` } +func init() { + caddy.RegisterModule((*Discoverer)(nil)) +} + type Discoverer struct { - config Config + Config google *sqladmin.Service } -func NewDiscoverer(config Config) (*Discoverer, error) { - google, err := sqladmin.NewService(context.Background()) +func (T *Discoverer) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.handlers.discovery.discoverers.google_cloud_sql", + New: func() caddy.Module { + return new(Discoverer) + }, + } +} + +func (T *Discoverer) Provision(ctx caddy.Context) error { + var err error + T.google, err = sqladmin.NewService(ctx) if err != nil { - return nil, err + return err } - return &Discoverer{ - config: config, - google: google, - }, nil + return nil } func (T *Discoverer) instanceToCluster(primary *sqladmin.DatabaseInstance, replicas ...*sqladmin.DatabaseInstance) (discovery.Cluster, error) { var primaryAddress string for _, ip := range primary.IpAddresses { - if ip.Type != T.config.IpAddressType { + if ip.Type != T.IpAddressType { continue } primaryAddress = net.JoinHostPort(ip.IpAddress, "5432") @@ -61,7 +74,7 @@ func (T *Discoverer) instanceToCluster(primary *sqladmin.DatabaseInstance, repli for _, replica := range replicas { var replicaAddress string for _, ip := range primary.IpAddresses { - if ip.Type != T.config.IpAddressType { + if ip.Type != T.IpAddressType { continue } replicaAddress = net.JoinHostPort(ip.IpAddress, "5432") @@ -72,7 +85,7 @@ func (T *Discoverer) instanceToCluster(primary *sqladmin.DatabaseInstance, repli } } - databases, err := T.google.Databases.List(T.config.Project, primary.Name).Do() + databases, err := T.google.Databases.List(T.Project, primary.Name).Do() if err != nil { return discovery.Cluster{}, err } @@ -85,42 +98,36 @@ func (T *Discoverer) instanceToCluster(primary *sqladmin.DatabaseInstance, repli return c, nil } - var admin fed.Conn + var admin *fed.Conn defer func() { if admin != nil { _ = admin.Close() } }() - users, err := T.google.Users.List(T.config.Project, primary.Name).Do() + users, err := T.google.Users.List(T.Project, primary.Name).Do() if err != nil { return discovery.Cluster{}, err } c.Users = make([]discovery.User, 0, len(users.Items)) for _, user := range users.Items { var password string - if user.Name == T.config.AuthUser { - password = T.config.AuthPassword + if user.Name == T.AuthUser { + password = T.AuthPassword } else { // dial admin connection if admin == nil { - raw, err := net.Dial("tcp", primaryAddress) - if err != nil { - return discovery.Cluster{}, err - } - admin = fed.WrapNetConn(raw) - _, err = backends.Accept(&backends.AcceptContext{ - Conn: admin, - Options: backends.AcceptOptions{ - SSLMode: bouncer.SSLModePrefer, - SSLConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - Username: T.config.AuthUser, - Credentials: credentials.FromString(T.config.AuthUser, T.config.AuthPassword), - Database: c.Databases[0], + admin, err = recipe.Dialer{ + Network: "tcp", + Address: primaryAddress, + SSLMode: bouncer.SSLModePrefer, + SSLConfig: &tls.Config{ + InsecureSkipVerify: true, }, - }) + Username: T.AuthUser, + Credentials: credentials.FromString(T.AuthUser, T.AuthPassword), + Database: c.Databases[0], + }.Dial() if err != nil { return discovery.Cluster{}, err } @@ -141,7 +148,7 @@ func (T *Discoverer) instanceToCluster(primary *sqladmin.DatabaseInstance, repli if err != nil { return discovery.Cluster{}, err } - _, err, err2 := bouncers.Bounce(client, admin, initialPacket) + _, err, err2 := bouncers.Bounce(fed.NewConn(client), admin, initialPacket) if err != nil { return discovery.Cluster{}, err } @@ -162,7 +169,7 @@ func (T *Discoverer) instanceToCluster(primary *sqladmin.DatabaseInstance, repli } func (T *Discoverer) Clusters() ([]discovery.Cluster, error) { - clusters, err := T.google.Instances.List(T.config.Project).Do() + clusters, err := T.google.Instances.List(T.Project).Do() if err != nil { return nil, err } @@ -201,12 +208,9 @@ func (T *Discoverer) Added() <-chan discovery.Cluster { return nil } -func (T *Discoverer) Updated() <-chan discovery.Cluster { - return nil -} - func (T *Discoverer) Removed() <-chan string { return nil } var _ discovery.Discoverer = (*Discoverer)(nil) +var _ caddy.Module = (*Discoverer)(nil) diff --git a/lib/gat/handlers/discovery/discoverers/zalando_operator/config.go b/lib/gat/handlers/discovery/discoverers/zalando_operator/config.go new file mode 100644 index 0000000000000000000000000000000000000000..fab4714a95597959ad66cdb00e77eb93f87d72e0 --- /dev/null +++ b/lib/gat/handlers/discovery/discoverers/zalando_operator/config.go @@ -0,0 +1,7 @@ +package zalando_operator + +type Config struct { + Namespace string `json:"namespace"` + ConfigMapName string `json:"config_map_name"` + OperatorConfigurationObject string `json:"operator_configuration_object"` +} diff --git a/lib/gat/modules/zalando_operator_discovery/discoverer.go b/lib/gat/handlers/discovery/discoverers/zalando_operator/discoverer.go similarity index 69% rename from lib/gat/modules/zalando_operator_discovery/discoverer.go rename to lib/gat/handlers/discovery/discoverers/zalando_operator/discoverer.go index 3e2adf38d51f46f9318b542f0036c1e738fe932d..62f547c46a6cdb20ff6cf5995499188b96c84b3f 100644 --- a/lib/gat/modules/zalando_operator_discovery/discoverer.go +++ b/lib/gat/handlers/discovery/discoverers/zalando_operator/discoverer.go @@ -1,11 +1,11 @@ -package zalando_operator_discovery +package zalando_operator import ( "context" "fmt" "strings" - "time" + "github.com/caddyserver/caddy/v2" acidzalando "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do" acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1" acidv1informer "github.com/zalando/postgres-operator/pkg/generated/informers/externalversions/acid.zalan.do/v1" @@ -14,13 +14,20 @@ import ( "github.com/zalando/postgres-operator/pkg/util/constants" "github.com/zalando/postgres-operator/pkg/util/k8sutil" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/rest" "k8s.io/client-go/tools/cache" - "gfx.cafe/gfx/pggat/lib/gat/modules/discovery" + "gfx.cafe/gfx/pggat/lib/gat/handlers/discovery" ) +func init() { + caddy.RegisterModule((*Discoverer)(nil)) +} + type Discoverer struct { - config Config + Config + + rest *rest.Config op *config.Config @@ -29,36 +36,41 @@ type Discoverer struct { k8s k8sutil.KubernetesClient added chan discovery.Cluster - updated chan discovery.Cluster removed chan string + + done chan struct{} } -func NewDiscoverer(conf Config) (*Discoverer, error) { - d := &Discoverer{ - config: conf, - } - if err := d.init(); err != nil { - return nil, err +func (T *Discoverer) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.handlers.discovery.discoverers.zalando_operator", + New: func() caddy.Module { + return new(Discoverer) + }, } - return d, nil } -func (T *Discoverer) init() error { +func (T *Discoverer) Provision(ctx caddy.Context) error { var err error - T.k8s, err = k8sutil.NewFromConfig(T.config.Rest) + T.rest, err = rest.InClusterConfig() if err != nil { return err } - if T.config.ConfigMapName != "" { - operatorConfig, err := T.k8s.ConfigMaps(T.config.Namespace).Get(context.Background(), T.config.ConfigMapName, metav1.GetOptions{}) + T.k8s, err = k8sutil.NewFromConfig(T.rest) + if err != nil { + return err + } + + if T.ConfigMapName != "" { + operatorConfig, err := T.k8s.ConfigMaps(T.Namespace).Get(ctx, T.ConfigMapName, metav1.GetOptions{}) if err != nil { return nil } T.op = config.NewFromMap(operatorConfig.Data) - } else if T.config.OperatorConfigurationObject != "" { - operatorConfig, err := T.k8s.OperatorConfigurations(T.config.Namespace).Get(context.Background(), T.config.OperatorConfigurationObject, metav1.GetOptions{}) + } else if T.OperatorConfigurationObject != "" { + operatorConfig, err := T.k8s.OperatorConfigurations(T.Namespace).Get(ctx, T.OperatorConfigurationObject, metav1.GetOptions{}) if err != nil { return err } @@ -88,13 +100,12 @@ func (T *Discoverer) init() error { T.informer = acidv1informer.NewPostgresqlInformer( T.k8s.AcidV1ClientSet, - T.config.Namespace, - 5*time.Minute, + T.Namespace, + 0, cache.Indexers{}, ) T.added = make(chan discovery.Cluster) - T.updated = make(chan discovery.Cluster) T.removed = make(chan string) _, err = T.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -118,7 +129,7 @@ func (T *Discoverer) init() error { if err != nil { return } - T.updated <- cluster + T.added <- cluster }, DeleteFunc: func(obj interface{}) { psql, ok := obj.(*acidv1.Postgresql) @@ -132,17 +143,23 @@ func (T *Discoverer) init() error { return err } - go T.informer.Run(nil) + T.done = make(chan struct{}) + go T.informer.Run(T.done) return nil } +func (T *Discoverer) Cleanup() error { + close(T.done) + return nil +} + func (T *Discoverer) postgresqlToCluster(cluster acidv1.Postgresql) (discovery.Cluster, error) { c := discovery.Cluster{ ID: string(cluster.UID), Primary: discovery.Endpoint{ Network: "tcp", - Address: fmt.Sprintf("%s.%s.svc.%s:5432", cluster.Name, T.config.Namespace, T.op.ClusterDomain), + Address: fmt.Sprintf("%s.%s.svc.%s:5432", cluster.Name, T.Namespace, T.op.ClusterDomain), }, Databases: make([]string, 0, len(cluster.Spec.Databases)), Users: make([]discovery.User, 0, len(cluster.Spec.Users)), @@ -151,7 +168,7 @@ func (T *Discoverer) postgresqlToCluster(cluster acidv1.Postgresql) (discovery.C c.Replicas = make(map[string]discovery.Endpoint, 1) c.Replicas["repl"] = discovery.Endpoint{ Network: "tcp", - Address: fmt.Sprintf("%s-repl.%s.svc.%s:5432", cluster.Name, T.config.Namespace, T.op.ClusterDomain), + Address: fmt.Sprintf("%s-repl.%s.svc.%s:5432", cluster.Name, T.Namespace, T.op.ClusterDomain), } } @@ -164,7 +181,7 @@ func (T *Discoverer) postgresqlToCluster(cluster acidv1.Postgresql) (discovery.C ) // get secret - secret, err := T.k8s.Secrets(T.config.Namespace).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err := T.k8s.Secrets(T.Namespace).Get(context.Background(), secretName, metav1.GetOptions{}) if err != nil { return discovery.Cluster{}, err } @@ -188,19 +205,32 @@ func (T *Discoverer) postgresqlToCluster(cluster acidv1.Postgresql) (discovery.C } func (T *Discoverer) Clusters() ([]discovery.Cluster, error) { - return nil, nil + clusters, err := T.k8s.Postgresqls(T.Namespace).List(context.Background(), metav1.ListOptions{}) + if err != nil { + return nil, err + } + + res := make([]discovery.Cluster, 0, len(clusters.Items)) + for _, cluster := range clusters.Items { + r, err := T.postgresqlToCluster(cluster) + if err != nil { + return nil, err + } + res = append(res, r) + } + + return res, nil } func (T *Discoverer) Added() <-chan discovery.Cluster { return T.added } -func (T *Discoverer) Updated() <-chan discovery.Cluster { - return T.updated -} - func (T *Discoverer) Removed() <-chan string { return T.removed } var _ discovery.Discoverer = (*Discoverer)(nil) +var _ caddy.Module = (*Discoverer)(nil) +var _ caddy.Provisioner = (*Discoverer)(nil) +var _ caddy.CleanerUpper = (*Discoverer)(nil) diff --git a/lib/gat/modules/discovery/module.go b/lib/gat/handlers/discovery/module.go similarity index 54% rename from lib/gat/modules/discovery/module.go rename to lib/gat/handlers/discovery/module.go index 8962fa8896fa9bd629411d1ae5e0cc7252cece95..bd6614d2e3f9350789e84dd0cb2637f060d85bbd 100644 --- a/lib/gat/modules/discovery/module.go +++ b/lib/gat/handlers/discovery/module.go @@ -1,42 +1,119 @@ package discovery import ( + "crypto/tls" + "fmt" "sync" "time" - "tuxpa.in/a/zlog/log" + "github.com/caddyserver/caddy/v2" + "go.uber.org/zap" "gfx.cafe/gfx/pggat/lib/auth" "gfx.cafe/gfx/pggat/lib/auth/credentials" + "gfx.cafe/gfx/pggat/lib/bouncer/frontends/v0" + "gfx.cafe/gfx/pggat/lib/fed" "gfx.cafe/gfx/pggat/lib/gat" "gfx.cafe/gfx/pggat/lib/gat/metrics" "gfx.cafe/gfx/pggat/lib/gat/pool" - "gfx.cafe/gfx/pggat/lib/gat/pool/pools/session" - "gfx.cafe/gfx/pggat/lib/gat/pool/pools/transaction" "gfx.cafe/gfx/pggat/lib/gat/pool/recipe" "gfx.cafe/gfx/pggat/lib/util/maps" "gfx.cafe/gfx/pggat/lib/util/slices" + "gfx.cafe/gfx/pggat/lib/util/strutil" ) +func init() { + caddy.RegisterModule((*Module)(nil)) +} + type Module struct { - config Config + Config + + discoverer Discoverer + + pooler gat.Pooler + sslConfig *tls.Config + + serverStartupParameters map[strutil.CIString]string + + closed chan struct{} // this is fine to have no locking because it is only accessed by discoverLoop clusters map[string]Cluster - pools maps.TwoKey[string, string, *pool.Pool] + pools maps.TwoKey[string, string, pool.WithCredentials] mu sync.RWMutex + + log *zap.Logger +} + +func (*Module) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.handlers.discovery", + New: func() caddy.Module { + return new(Module) + }, + } } -func NewModule(config Config) (*Module, error) { - m := &Module{ - config: config, +func (T *Module) Provision(ctx caddy.Context) error { + T.log = ctx.Logger() + + if T.Discoverer != nil { + val, err := ctx.LoadModule(T, "Discoverer") + if err != nil { + return fmt.Errorf("loading discoverer module: %v", err) + } + T.discoverer = val.(Discoverer) + } + if T.Pooler != nil { + val, err := ctx.LoadModule(T, "Pooler") + if err != nil { + return fmt.Errorf("loading pooler module: %v", err) + } + T.pooler = val.(gat.Pooler) + } + if T.ServerSSL != nil { + val, err := ctx.LoadModule(T, "ServerSSL") + if err != nil { + return fmt.Errorf("loading ssl module: %v", err) + } + ssl := val.(gat.SSLClient) + T.sslConfig = ssl.ClientTLSConfig() + } + T.serverStartupParameters = make(map[strutil.CIString]string, len(T.ServerStartupParameters)) + for key, value := range T.ServerStartupParameters { + T.serverStartupParameters[strutil.MakeCIString(key)] = value } - if err := m.reconcile(); err != nil { - return nil, err + + if T.closed != nil { + return nil } - go m.discoverLoop() - return m, nil + T.closed = make(chan struct{}) + + if err := T.reconcile(); err != nil { + return err + } + go T.discoverLoop() + + return nil +} + +func (T *Module) Cleanup() error { + if T.closed == nil { + return nil + } + close(T.closed) + T.closed = nil + + T.mu.Lock() + defer T.mu.Unlock() + T.pools.Range(func(user string, database string, p pool.WithCredentials) bool { + p.Close() + T.pools.Delete(user, database) + return true + }) + return nil } func (T *Module) replicaUsername(username string) string { @@ -49,40 +126,11 @@ func (T *Module) creds(user User) (primary, replica auth.Credentials) { return } -func (T *Module) backendAcceptOptions(username string, creds auth.Credentials, database string) recipe.BackendAcceptOptions { - return recipe.BackendAcceptOptions{ - SSLMode: T.config.ServerSSLMode, - SSLConfig: T.config.ServerSSLConfig, - Username: username, - Credentials: creds, - Database: database, - StartupParameters: T.config.ServerStartupParameters, - } -} - -func (T *Module) poolOptions(creds auth.Credentials) pool.Options { - options := pool.Options{ - Credentials: creds, - ServerReconnectInitialTime: T.config.ServerReconnectInitialTime, - ServerReconnectMaxTime: T.config.ServerReconnectMaxTime, - ServerIdleTimeout: T.config.ServerIdleTimeout, - TrackedParameters: T.config.TrackedParameters, - ServerResetQuery: T.config.ServerResetQuery, - } - - switch T.config.PoolMode { - case "session": - options = session.Apply(options) - case "transaction": - options = transaction.Apply(options) - default: - log.Printf("unknown pool mode: %s", T.config.PoolMode) - } - - return options -} - func (T *Module) added(cluster Cluster) { + if prev, ok := T.clusters[cluster.ID]; ok { + T.updated(prev, cluster) + return + } if T.clusters == nil { T.clusters = make(map[string]Cluster) } @@ -175,21 +223,24 @@ func (T *Module) replacePrimary(users []User, databases []string, endpoint Endpo for _, user := range users { primaryCreds, _ := T.creds(user) for _, database := range databases { - acceptOptions := T.backendAcceptOptions(user.Username, primaryCreds, database) - primary := recipe.Dialer{ - Network: endpoint.Network, - Address: endpoint.Address, - AcceptOptions: acceptOptions, + Network: endpoint.Network, + Address: endpoint.Address, + Username: user.Username, + Credentials: primaryCreds, + Database: database, + SSLMode: T.ServerSSLMode, + SSLConfig: T.sslConfig, + StartupParameters: T.serverStartupParameters, } - p := T.Lookup(user.Username, database) - if p == nil { + p, ok := T.lookup(user.Username, database) + if !ok { continue } p.RemoveRecipe("primary") - p.AddRecipe("primary", recipe.NewRecipe(recipe.Options{ + p.AddRecipe("primary", recipe.NewRecipe(recipe.Config{ Dialer: primary, })) } @@ -201,18 +252,23 @@ func (T *Module) addReplicas(replicas map[string]Endpoint, users []User, databas replicaUsername := T.replicaUsername(user.Username) primaryCreds, replicaCreds := T.creds(user) for _, database := range databases { - acceptOptions := T.backendAcceptOptions(user.Username, primaryCreds, database) - - replicaPoolOptions := T.poolOptions(replicaCreds) - replicaPool := pool.NewPool(replicaPoolOptions) + replicaPool := pool.WithCredentials{ + Pool: T.pooler.NewPool(), + Credentials: replicaCreds, + } for id, r := range replicas { replica := recipe.Dialer{ - Network: r.Network, - Address: r.Address, - AcceptOptions: acceptOptions, + Network: r.Network, + Address: r.Address, + Username: user.Username, + Credentials: primaryCreds, + Database: database, + SSLMode: T.ServerSSLMode, + SSLConfig: T.sslConfig, + StartupParameters: T.serverStartupParameters, } - replicaPool.AddRecipe(id, recipe.NewRecipe(recipe.Options{ + replicaPool.AddRecipe(id, recipe.NewRecipe(recipe.Config{ Dialer: replica, })) } @@ -236,19 +292,22 @@ func (T *Module) addReplica(users []User, databases []string, id string, endpoin replicaUsername := T.replicaUsername(user.Username) primaryCreds, _ := T.creds(user) for _, database := range databases { - acceptOptions := T.backendAcceptOptions(user.Username, primaryCreds, database) - - p := T.Lookup(replicaUsername, database) - if p == nil { + p, ok := T.lookup(replicaUsername, database) + if !ok { continue } replica := recipe.Dialer{ - Network: endpoint.Network, - Address: endpoint.Address, - AcceptOptions: acceptOptions, + Network: endpoint.Network, + Address: endpoint.Address, + Username: user.Username, + Credentials: primaryCreds, + Database: database, + SSLMode: T.ServerSSLMode, + SSLConfig: T.sslConfig, + StartupParameters: T.serverStartupParameters, } - p.AddRecipe(id, recipe.NewRecipe(recipe.Options{ + p.AddRecipe(id, recipe.NewRecipe(recipe.Config{ Dialer: replica, })) } @@ -259,8 +318,8 @@ func (T *Module) removeReplica(users []User, databases []string, id string) { for _, user := range users { username := T.replicaUsername(user.Username) for _, database := range databases { - p := T.Lookup(username, database) - if p == nil { + p, ok := T.lookup(username, database) + if !ok { continue } p.RemoveRecipe(id) @@ -272,34 +331,39 @@ func (T *Module) addUser(primaryEndpoint Endpoint, replicas map[string]Endpoint, replicaUsername := T.replicaUsername(user.Username) primaryCreds, replicaCreds := T.creds(user) for _, database := range databases { - acceptOptions := T.backendAcceptOptions(user.Username, primaryCreds, database) - - primary := recipe.Dialer{ - Network: primaryEndpoint.Network, - Address: primaryEndpoint.Address, - AcceptOptions: acceptOptions, + base := recipe.Dialer{ + Username: user.Username, + Credentials: primaryCreds, + Database: database, + SSLMode: T.ServerSSLMode, + SSLConfig: T.sslConfig, + StartupParameters: T.serverStartupParameters, } - primaryPoolOptions := T.poolOptions(primaryCreds) + primary := base + primary.Network = primaryEndpoint.Network + primary.Address = primaryEndpoint.Address - primaryPool := pool.NewPool(primaryPoolOptions) - primaryPool.AddRecipe("primary", recipe.NewRecipe(recipe.Options{ + primaryPool := pool.WithCredentials{ + Pool: T.pooler.NewPool(), + Credentials: primaryCreds, + } + primaryPool.AddRecipe("primary", recipe.NewRecipe(recipe.Config{ Dialer: primary, })) T.addPool(user.Username, database, primaryPool) if len(replicas) > 0 { - replicaPoolOptions := T.poolOptions(replicaCreds) - - replicaPool := pool.NewPool(replicaPoolOptions) + replicaPool := pool.WithCredentials{ + Pool: T.pooler.NewPool(), + Credentials: replicaCreds, + } for id, r := range replicas { - replica := recipe.Dialer{ - Network: r.Network, - Address: r.Address, - AcceptOptions: acceptOptions, - } - replicaPool.AddRecipe(id, recipe.NewRecipe(recipe.Options{ + replica := base + replica.Network = r.Network + replica.Address = r.Address + replicaPool.AddRecipe(id, recipe.NewRecipe(recipe.Config{ Dialer: replica, })) } @@ -326,34 +390,39 @@ func (T *Module) addDatabase(primaryEndpoint Endpoint, replicas map[string]Endpo replicaUsername := T.replicaUsername(user.Username) primaryCreds, replicaCreds := T.creds(user) - acceptOptions := T.backendAcceptOptions(user.Username, primaryCreds, database) - - primary := recipe.Dialer{ - Network: primaryEndpoint.Network, - Address: primaryEndpoint.Address, - AcceptOptions: acceptOptions, + base := recipe.Dialer{ + Username: user.Username, + Credentials: primaryCreds, + Database: database, + SSLMode: T.ServerSSLMode, + SSLConfig: T.sslConfig, + StartupParameters: T.serverStartupParameters, } - primaryPoolOptions := T.poolOptions(primaryCreds) + primary := base + primary.Network = primaryEndpoint.Network + primary.Address = primaryEndpoint.Address - primaryPool := pool.NewPool(primaryPoolOptions) - primaryPool.AddRecipe("primary", recipe.NewRecipe(recipe.Options{ + primaryPool := pool.WithCredentials{ + Pool: T.pooler.NewPool(), + Credentials: primaryCreds, + } + primaryPool.AddRecipe("primary", recipe.NewRecipe(recipe.Config{ Dialer: primary, })) T.addPool(user.Username, database, primaryPool) if len(replicas) > 0 { - replicaPoolOptions := T.poolOptions(replicaCreds) - - replicaPool := pool.NewPool(replicaPoolOptions) + replicaPool := pool.WithCredentials{ + Pool: T.pooler.NewPool(), + Credentials: replicaCreds, + } for id, r := range replicas { - replica := recipe.Dialer{ - Network: r.Network, - Address: r.Address, - AcceptOptions: acceptOptions, - } - replicaPool.AddRecipe(id, recipe.NewRecipe(recipe.Options{ + replica := base + replica.Network = r.Network + replica.Address = r.Address + replicaPool.AddRecipe(id, recipe.NewRecipe(recipe.Config{ Dialer: replica, })) } @@ -385,7 +454,7 @@ func (T *Module) removed(id string) { } func (T *Module) reconcile() error { - clusters, err := T.config.Discoverer.Clusters() + clusters, err := T.discoverer.Clusters() if err != nil { return err } @@ -415,33 +484,31 @@ outer: func (T *Module) discoverLoop() { var reconcile <-chan time.Time - if T.config.ReconcilePeriod != 0 { - r := time.NewTicker(T.config.ReconcilePeriod) + if T.ReconcilePeriod != 0 { + r := time.NewTicker(T.ReconcilePeriod.Duration()) defer r.Stop() reconcile = r.C } for { select { - case cluster := <-T.config.Discoverer.Added(): + case cluster := <-T.discoverer.Added(): T.added(cluster) - case id := <-T.config.Discoverer.Removed(): + case id := <-T.discoverer.Removed(): T.removed(id) - case next := <-T.config.Discoverer.Updated(): - T.updated(T.clusters[next.ID], next) case <-reconcile: err := T.reconcile() if err != nil { - log.Printf("failed to reconcile: %v", err) + T.log.Warn("failed to reconcile", zap.Error(err)) } } } } -func (T *Module) addPool(user, database string, p *pool.Pool) { +func (T *Module) addPool(user, database string, p pool.WithCredentials) { T.mu.Lock() defer T.mu.Unlock() - log.Printf("added pool user=%s database=%s", user, database) + T.log.Info("added pool", zap.String("user", user), zap.String("database", database)) if old, ok := T.pools.Load(user, database); ok { // shouldn't normally get here old.Close() @@ -457,27 +524,50 @@ func (T *Module) removePool(user, database string) { return } p.Close() - log.Printf("removed pool user=%s database=%s", user, database) + T.log.Info("removed pool", zap.String("user", user), zap.String("database", database)) T.pools.Delete(user, database) } -func (T *Module) GatModule() {} - -func (T *Module) ReadMetrics(metrics *metrics.Pools) { +func (T *Module) ReadMetrics(metrics *metrics.Handler) { T.mu.RLock() defer T.mu.RUnlock() - T.pools.Range(func(_ string, _ string, p *pool.Pool) bool { + T.pools.Range(func(_ string, _ string, p pool.WithCredentials) bool { p.ReadMetrics(&metrics.Pool) return true }) } -func (T *Module) Lookup(user, database string) *gat.Pool { +func (T *Module) lookup(user, database string) (pool.WithCredentials, bool) { T.mu.RLock() defer T.mu.RUnlock() - p, _ := T.pools.Load(user, database) - return p + return T.pools.Load(user, database) +} + +func (T *Module) Handle(conn *fed.Conn) error { + p, ok := T.lookup(conn.User, conn.Database) + if !ok { + return nil + } + + if err := frontends.Authenticate(conn, p.Credentials); err != nil { + return err + } + + return p.Serve(conn) +} + +func (T *Module) Cancel(key [8]byte) { + T.mu.RLock() + defer T.mu.RUnlock() + T.pools.Range(func(_ string, _ string, p pool.WithCredentials) bool { + p.Cancel(key) + return true + }) } -var _ gat.Module = (*Module)(nil) -var _ gat.Provider = (*Module)(nil) +var _ gat.Handler = (*Module)(nil) +var _ gat.MetricsHandler = (*Module)(nil) +var _ gat.CancellableHandler = (*Module)(nil) +var _ caddy.Module = (*Module)(nil) +var _ caddy.Provisioner = (*Module)(nil) +var _ caddy.CleanerUpper = (*Module)(nil) diff --git a/lib/gat/handlers/error/module.go b/lib/gat/handlers/error/module.go new file mode 100644 index 0000000000000000000000000000000000000000..b8eedbf704d40f63e2308636c063aa3a68ba0a7f --- /dev/null +++ b/lib/gat/handlers/error/module.go @@ -0,0 +1,36 @@ +package error_handler + +import ( + "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/gat" + "gfx.cafe/gfx/pggat/lib/perror" + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule((*Module)(nil)) +} + +type Module struct { + Message string `json:"message"` +} + +func (T *Module) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.handlers.error", + New: func() caddy.Module { + return new(Module) + }, + } +} + +func (T *Module) Handle(_ *fed.Conn) error { + return perror.New( + perror.FATAL, + perror.InternalError, + T.Message, + ) +} + +var _ gat.Handler = (*Module)(nil) +var _ caddy.Module = (*Module)(nil) diff --git a/lib/gat/modules/pgbouncer/authfile.go b/lib/gat/handlers/pgbouncer/authfile.go similarity index 100% rename from lib/gat/modules/pgbouncer/authfile.go rename to lib/gat/handlers/pgbouncer/authfile.go diff --git a/lib/gat/modules/pgbouncer/config.go b/lib/gat/handlers/pgbouncer/config.go similarity index 89% rename from lib/gat/modules/pgbouncer/config.go rename to lib/gat/handlers/pgbouncer/config.go index 1ce4b8edbeb6d07b7ba87273878d57714d514f04..6f3b179adbe9adf4901e9b04d18dc68f4f5cffcf 100644 --- a/lib/gat/modules/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 new file mode 100644 index 0000000000000000000000000000000000000000..a5a8f025fcdc70e1c491edd94bd21d854f08dc38 --- /dev/null +++ b/lib/gat/handlers/pgbouncer/module.go @@ -0,0 +1,363 @@ +package pgbouncer + +import ( + "crypto/tls" + "errors" + "fmt" + "io" + "net" + "strconv" + "strings" + "sync" + "time" + + "github.com/caddyserver/caddy/v2" + "go.uber.org/zap" + + "gfx.cafe/gfx/pggat/lib/bouncer/frontends/v0" + "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/gat/poolers/session" + "gfx.cafe/gfx/pggat/lib/gat/poolers/transaction" + "gfx.cafe/gfx/pggat/lib/perror" + "gfx.cafe/gfx/pggat/lib/util/dur" + "gfx.cafe/gfx/pggat/lib/util/slices" + + "gfx.cafe/gfx/pggat/lib/auth/credentials" + "gfx.cafe/gfx/pggat/lib/gat" + "gfx.cafe/gfx/pggat/lib/gat/metrics" + "gfx.cafe/gfx/pggat/lib/gat/pool" + "gfx.cafe/gfx/pggat/lib/gat/pool/recipe" + "gfx.cafe/gfx/pggat/lib/gsql" + "gfx.cafe/gfx/pggat/lib/util/maps" + "gfx.cafe/gfx/pggat/lib/util/strutil" +) + +type authQueryResult struct { + Username string `sql:"0"` + Password string `sql:"1"` +} + +func init() { + caddy.RegisterModule((*Module)(nil)) +} + +type Module struct { + ConfigFile string `json:"config"` + Config Config `json:"-"` + + pools maps.TwoKey[string, string, pool.WithCredentials] + mu sync.RWMutex + + log *zap.Logger +} + +func (*Module) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.handlers.pgbouncer", + New: func() caddy.Module { + return new(Module) + }, + } +} + +func (T *Module) Provision(ctx caddy.Context) error { + T.log = ctx.Logger() + + if T.ConfigFile != "" { + var err error + T.Config, err = Load(T.ConfigFile) + return err + } + return nil +} + +func (T *Module) Cleanup() error { + T.mu.Lock() + defer T.mu.Unlock() + + T.pools.Range(func(user string, database string, p pool.WithCredentials) bool { + p.Close() + T.pools.Delete(user, database) + return true + }) + + return nil +} + +func (T *Module) getPassword(user, database string) (string, bool) { + // try to get password + password, ok := T.Config.PgBouncer.AuthFile[user] + if !ok { + // try to run auth query + if T.Config.PgBouncer.AuthQuery == "" { + return "", false + } + + authUser := T.Config.Databases[database].AuthUser + if authUser == "" { + authUser = T.Config.PgBouncer.AuthUser + if authUser == "" { + return "", false + } + } + + authPool, ok := T.lookup(authUser, database) + if !ok { + return "", false + } + + var result authQueryResult + client := new(gsql.Client) + err := gsql.ExtendedQuery(client, &result, T.Config.PgBouncer.AuthQuery, user) + if err != nil { + T.log.Warn("auth query failed", zap.Error(err)) + return "", false + } + err = client.Close() + if err != nil { + T.log.Warn("auth query failed", zap.Error(err)) + return "", false + } + err = authPool.ServeBot(client) + if err != nil && !errors.Is(err, io.EOF) { + T.log.Warn("auth query failed", zap.Error(err)) + return "", false + } + + if result.Username != user { + // user not found + return "", false + } + + password = result.Password + } + + return password, true +} + +func (T *Module) tryCreate(user, database string) (pool.WithCredentials, bool) { + db, ok := T.Config.Databases[database] + if !ok { + // try wildcard + db, ok = T.Config.Databases["*"] + if !ok { + return pool.WithCredentials{}, false + } + } + + // try to get password + password, ok := T.getPassword(user, database) + if !ok { + return pool.WithCredentials{}, false + } + + creds := credentials.FromString(user, password) + + serverDatabase := db.DBName + if serverDatabase == "" { + serverDatabase = database + } + + configUser := T.Config.Users[user] + + poolMode := db.PoolMode + if poolMode == "" { + poolMode = configUser.PoolMode + if poolMode == "" { + poolMode = T.Config.PgBouncer.PoolMode + } + } + + trackedParameters := append([]strutil.CIString{ + strutil.MakeCIString("client_encoding"), + strutil.MakeCIString("datestyle"), + strutil.MakeCIString("timezone"), + strutil.MakeCIString("standard_conforming_strings"), + strutil.MakeCIString("application_name"), + }, T.Config.PgBouncer.TrackExtraParameters...) + + serverLoginRetry := dur.Duration(T.Config.PgBouncer.ServerLoginRetry * float64(time.Second)) + + poolOptions := pool.Config{ + ManagementConfig: pool.ManagementConfig{ + TrackedParameters: trackedParameters, + ServerResetQuery: T.Config.PgBouncer.ServerResetQuery, + ServerIdleTimeout: dur.Duration(T.Config.PgBouncer.ServerIdleTimeout * float64(time.Second)), + ServerReconnectInitialTime: serverLoginRetry, + }, + Logger: T.log, + } + + switch poolMode { + case PoolModeSession: + poolOptions.PoolingConfig = session.PoolingOptions + case PoolModeTransaction: + if T.Config.PgBouncer.ServerResetQueryAlways == 0 { + poolOptions.ServerResetQuery = "" + } + poolOptions.PoolingConfig = transaction.PoolingOptions + default: + return pool.WithCredentials{}, false + } + p := pool.WithCredentials{ + Pool: pool.NewPool(poolOptions), + Credentials: creds, + } + + T.mu.Lock() + defer T.mu.Unlock() + T.pools.Store(user, database, p) + + serverCreds := creds + if db.Password != "" { + // lookup password + serverCreds = credentials.FromString(user, db.Password) + } + + dialer := recipe.Dialer{ + SSLMode: T.Config.PgBouncer.ServerTLSSSLMode, + SSLConfig: &tls.Config{ + InsecureSkipVerify: true, // TODO(garet) + }, + Username: user, + Credentials: serverCreds, + Database: serverDatabase, + StartupParameters: db.StartupParameters, + } + + if db.Host == "" || strings.HasPrefix(db.Host, "/") { + // connect over unix socket + dir := db.Host + port := db.Port + if !strings.HasPrefix(dir, "/") { + dir = dir + "/" + } + + if port == 0 { + port = 5432 + } + + dir = dir + ".s.PGSQL." + strconv.Itoa(port) + + dialer.Network = "unix" + dialer.Address = dir + } else { + var address string + if db.Port == 0 { + address = net.JoinHostPort(db.Host, "5432") + } else { + address = net.JoinHostPort(db.Host, strconv.Itoa(db.Port)) + } + + // connect over tcp + dialer.Network = "tcp" + dialer.Address = address + } + + recipeOptions := recipe.Config{ + Dialer: dialer, + MinConnections: db.MinPoolSize, + MaxConnections: db.MaxDBConnections, + } + if recipeOptions.MinConnections == 0 { + recipeOptions.MinConnections = T.Config.PgBouncer.MinPoolSize + } + if recipeOptions.MaxConnections == 0 { + recipeOptions.MaxConnections = T.Config.PgBouncer.MaxDBConnections + } + r := recipe.NewRecipe(recipeOptions) + + p.AddRecipe("pgbouncer", r) + + return p, true +} + +func (T *Module) lookup(user, database string) (pool.WithCredentials, bool) { + p, ok := T.pools.Load(user, database) + if ok { + return p, true + } + + // try to create pool + return T.tryCreate(user, database) +} + +func (T *Module) Handle(conn *fed.Conn) error { + // check ssl + if T.Config.PgBouncer.ClientTLSSSLMode.IsRequired() { + var ssl bool + netConn, ok := conn.ReadWriteCloser.(*fed.NetConn) + if ok { + ssl = netConn.SSL() + } + + if !ssl { + return perror.New( + perror.FATAL, + perror.InvalidPassword, + "SSL is required", + ) + } + } + + // check startup parameters + for key := range conn.InitialParameters { + if slices.Contains([]strutil.CIString{ + strutil.MakeCIString("client_encoding"), + strutil.MakeCIString("datestyle"), + strutil.MakeCIString("timezone"), + strutil.MakeCIString("standard_conforming_strings"), + strutil.MakeCIString("application_name"), + }, key) { + continue + } + if slices.Contains(T.Config.PgBouncer.TrackExtraParameters, key) { + continue + } + if slices.Contains(T.Config.PgBouncer.IgnoreStartupParameters, key) { + continue + } + + return perror.New( + perror.FATAL, + perror.FeatureNotSupported, + fmt.Sprintf(`Startup parameter "%s" is not supported`, key.String()), + ) + } + + p, ok := T.lookup(conn.User, conn.Database) + if !ok { + return nil + } + + if err := frontends.Authenticate(conn, p.Credentials); err != nil { + return err + } + + return p.Serve(conn) +} + +func (T *Module) ReadMetrics(metrics *metrics.Handler) { + T.mu.RLock() + defer T.mu.RUnlock() + T.pools.Range(func(_ string, _ string, p pool.WithCredentials) bool { + p.ReadMetrics(&metrics.Pool) + return true + }) +} + +func (T *Module) Cancel(key [8]byte) { + T.mu.RLock() + defer T.mu.RUnlock() + T.pools.Range(func(_ string, _ string, p pool.WithCredentials) bool { + p.Cancel(key) + return true + }) +} + +var _ gat.Handler = (*Module)(nil) +var _ gat.MetricsHandler = (*Module)(nil) +var _ gat.CancellableHandler = (*Module)(nil) +var _ caddy.Module = (*Module)(nil) +var _ caddy.Provisioner = (*Module)(nil) +var _ caddy.CleanerUpper = (*Module)(nil) diff --git a/lib/gat/handlers/pgbouncer_spilo/config.go b/lib/gat/handlers/pgbouncer_spilo/config.go new file mode 100644 index 0000000000000000000000000000000000000000..f303c6eb99b7091d81e6ded8b1ddfabf6e399a53 --- /dev/null +++ b/lib/gat/handlers/pgbouncer_spilo/config.go @@ -0,0 +1,15 @@ +package pgbouncer_spilo + +type Config struct { + Host string `json:"host"` + Port int `json:"port"` + User string `json:"user"` + Schema string `json:"schema"` + Password string `json:"password"` + Mode string `json:"mode"` + DefaultSize int `json:"default_size"` + MinSize int `json:"min_size"` + ReserveSize int `json:"reserve_size"` + MaxClientConn int `json:"max_client_conn"` + MaxDBConn int `json:"max_db_conn"` +} diff --git a/lib/gat/modules/zalando/module.go b/lib/gat/handlers/pgbouncer_spilo/module.go similarity index 53% rename from lib/gat/modules/zalando/module.go rename to lib/gat/handlers/pgbouncer_spilo/module.go index 061e3ea25ec0e36b9afb3437c3dc4df3e53e7219..dc41720b5b91196e3dd19d2f6d636d5dc5412f23 100644 --- a/lib/gat/modules/zalando/module.go +++ b/lib/gat/handlers/pgbouncer_spilo/module.go @@ -1,32 +1,54 @@ -package zalando +package pgbouncer_spilo import ( "fmt" + "github.com/caddyserver/caddy/v2" + + "gfx.cafe/gfx/pggat/lib/gat/handlers/pgbouncer" + "gfx.cafe/gfx/pggat/lib/bouncer" - "gfx.cafe/gfx/pggat/lib/gat/modules/pgbouncer" + "gfx.cafe/gfx/pggat/lib/gat" + "gfx.cafe/gfx/pggat/lib/util/strutil" ) -func NewModule(config Config) (*pgbouncer.Module, error) { +func init() { + caddy.RegisterModule((*Module)(nil)) +} + +type Module struct { + Config + + pgbouncer.Module `json:"-"` +} + +func (*Module) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.handlers.pgbouncer_spilo", + New: func() caddy.Module { + return new(Module) + }, + } +} + +func (T *Module) Provision(ctx caddy.Context) error { pgb := pgbouncer.Default if pgb.Databases == nil { pgb.Databases = make(map[string]pgbouncer.Database) } pgb.Databases["*"] = pgbouncer.Database{ - Host: config.PGHost, - Port: config.PGPort, - AuthUser: config.PGUser, + Host: T.Host, + Port: T.Port, + AuthUser: T.User, } - pgb.PgBouncer.PoolMode = pgbouncer.PoolMode(config.PoolerMode) - pgb.PgBouncer.ListenPort = config.PoolerPort - pgb.PgBouncer.ListenAddr = "*" + pgb.PgBouncer.PoolMode = pgbouncer.PoolMode(T.Mode) pgb.PgBouncer.AuthType = "md5" pgb.PgBouncer.AuthFile = pgbouncer.AuthFile{ - config.PGUser: config.PGPassword, + T.User: T.Password, } - pgb.PgBouncer.AdminUsers = []string{config.PGUser} - pgb.PgBouncer.AuthQuery = fmt.Sprintf("SELECT * FROM %s.user_lookup($1)", config.PGSchema) + pgb.PgBouncer.AdminUsers = []string{T.User} + pgb.PgBouncer.AuthQuery = fmt.Sprintf("SELECT * FROM %s.user_lookup($1)", T.Schema) pgb.PgBouncer.LogFile = "/var/log/pgbouncer/pgbouncer.log" pgb.PgBouncer.PidFile = "/var/run/pgbouncer/pgbouncer.pid" @@ -42,10 +64,10 @@ func NewModule(config Config) (*pgbouncer.Module, error) { pgb.PgBouncer.LogConnections = 0 pgb.PgBouncer.LogDisconnections = 0 - pgb.PgBouncer.DefaultPoolSize = config.PoolerDefaultSize - pgb.PgBouncer.ReservePoolSize = config.PoolerReserveSize - pgb.PgBouncer.MaxClientConn = config.PoolerMaxClientConn - pgb.PgBouncer.MaxDBConnections = config.PoolerMaxDBConn + pgb.PgBouncer.DefaultPoolSize = T.DefaultSize + pgb.PgBouncer.ReservePoolSize = T.ReserveSize + pgb.PgBouncer.MaxClientConn = T.MaxClientConn + pgb.PgBouncer.MaxDBConnections = T.MaxDBConn pgb.PgBouncer.IdleTransactionTimeout = 600 pgb.PgBouncer.ServerLoginRetry = 5 @@ -54,5 +76,13 @@ func NewModule(config Config) (*pgbouncer.Module, error) { strutil.MakeCIString("options"), } - return pgbouncer.NewModule(pgb) + T.Module = pgbouncer.Module{ + Config: pgb, + } + + return nil } + +var _ gat.Handler = (*Module)(nil) +var _ caddy.Module = (*Module)(nil) +var _ caddy.Provisioner = (*Module)(nil) diff --git a/lib/gat/handlers/pool/config.go b/lib/gat/handlers/pool/config.go new file mode 100644 index 0000000000000000000000000000000000000000..5a2fc336fd46a6aee54c26118aa067f7cdb855c4 --- /dev/null +++ b/lib/gat/handlers/pool/config.go @@ -0,0 +1,22 @@ +package pool_handler + +import ( + "encoding/json" + + "gfx.cafe/gfx/pggat/lib/bouncer" +) + +type Config struct { + Pooler json.RawMessage `json:"pooler" caddy:"namespace=pggat.poolers inline_key=pooler"` + + // Server connect options + ServerAddress string `jsonn:"server_address"` + ServerSSLMode bouncer.SSLMode `json:"server_ssl_mode,omitempty"` + ServerSSL json.RawMessage `json:"server_ssl,omitempty" caddy:"namespace=pggat.ssl.clients inline_key=provider"` + + // Server routing options + ServerUsername string `json:"server_username"` + ServerPassword string `json:"server_password"` + ServerDatabase string `json:"server_database"` + ServerStartupParameters map[string]string `json:"server_startup_parameters"` +} diff --git a/lib/gat/handlers/pool/module.go b/lib/gat/handlers/pool/module.go new file mode 100644 index 0000000000000000000000000000000000000000..61b0085a7abd477aac62cd69d0d83d37df226374 --- /dev/null +++ b/lib/gat/handlers/pool/module.go @@ -0,0 +1,113 @@ +package pool_handler + +import ( + "crypto/tls" + "fmt" + "strings" + + "github.com/caddyserver/caddy/v2" + + "gfx.cafe/gfx/pggat/lib/auth/credentials" + "gfx.cafe/gfx/pggat/lib/bouncer/frontends/v0" + "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/gat" + "gfx.cafe/gfx/pggat/lib/gat/metrics" + "gfx.cafe/gfx/pggat/lib/gat/pool/recipe" + "gfx.cafe/gfx/pggat/lib/util/strutil" +) + +func init() { + caddy.RegisterModule((*Module)(nil)) +} + +type Module struct { + Config + + pool *gat.Pool +} + +func (*Module) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.handlers.pool", + New: func() caddy.Module { + return new(Module) + }, + } +} + +func (T *Module) Provision(ctx caddy.Context) error { + val, err := ctx.LoadModule(T, "Pooler") + if err != nil { + return fmt.Errorf("loading pooler module: %v", err) + } + pooler := val.(gat.Pooler) + + var sslConfig *tls.Config + if T.ServerSSL != nil { + val, err = ctx.LoadModule(T, "ServerSSL") + if err != nil { + return fmt.Errorf("loading ssl module: %v", err) + } + ssl := val.(gat.SSLClient) + sslConfig = ssl.ClientTLSConfig() + } + + creds := credentials.FromString(T.ServerUsername, T.ServerPassword) + startupParameters := make(map[strutil.CIString]string, len(T.ServerStartupParameters)) + for key, value := range T.ServerStartupParameters { + startupParameters[strutil.MakeCIString(key)] = value + } + + var network string + if strings.HasPrefix(T.ServerAddress, "/") { + network = "unix" + } else { + network = "tcp" + } + + d := recipe.Dialer{ + Network: network, + Address: T.ServerAddress, + SSLMode: T.ServerSSLMode, + SSLConfig: sslConfig, + Username: T.ServerUsername, + Credentials: creds, + Database: T.ServerDatabase, + StartupParameters: startupParameters, + } + + T.pool = pooler.NewPool() + T.pool.AddRecipe("pool", recipe.NewRecipe(recipe.Config{ + Dialer: d, + })) + + return nil +} + +func (T *Module) Cleanup() error { + T.pool.Close() + return nil +} + +func (T *Module) Handle(conn *fed.Conn) error { + if err := frontends.Authenticate(conn, nil); err != nil { + return err + } + + return T.pool.Serve(conn) +} + +func (T *Module) Cancel(key [8]byte) { + T.pool.Cancel(key) +} + +func (T *Module) ReadMetrics(metrics *metrics.Handler) { + T.pool.ReadMetrics(&metrics.Pool) +} + +var _ gat.Handler = (*Module)(nil) +var _ gat.MetricsHandler = (*Module)(nil) +var _ gat.CancellableHandler = (*Module)(nil) +var _ caddy.Module = (*Module)(nil) +var _ caddy.Provisioner = (*Module)(nil) +var _ caddy.CleanerUpper = (*Module)(nil) diff --git a/lib/gat/handlers/require_ssl/module.go b/lib/gat/handlers/require_ssl/module.go new file mode 100644 index 0000000000000000000000000000000000000000..47f7cb8e63bb3f6ce31fd810505154aa1817a941 --- /dev/null +++ b/lib/gat/handlers/require_ssl/module.go @@ -0,0 +1,58 @@ +package require_ssl + +import ( + "github.com/caddyserver/caddy/v2" + + "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/gat" + "gfx.cafe/gfx/pggat/lib/perror" +) + +func init() { + caddy.RegisterModule((*Module)(nil)) +} + +type Module struct { + SSL bool `json:"ssl"` +} + +func (T *Module) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.handlers.require_ssl", + New: func() caddy.Module { + return new(Module) + }, + } +} + +func (T *Module) Handle(conn *fed.Conn) error { + var ssl bool + + sslConn, ok := conn.ReadWriteCloser.(fed.SSL) + if ok { + ssl = sslConn.SSL() + } + + if T.SSL { + if !ssl { + return perror.New( + perror.FATAL, + perror.InvalidPassword, + "SSL is required", + ) + } + return nil + } + + if ssl { + return perror.New( + perror.FATAL, + perror.InvalidPassword, + "SSL is not allowed", + ) + } + return nil +} + +var _ gat.Handler = (*Module)(nil) +var _ caddy.Module = (*Module)(nil) diff --git a/lib/gat/handlers/rewrite_database/module.go b/lib/gat/handlers/rewrite_database/module.go new file mode 100644 index 0000000000000000000000000000000000000000..bd471975c538e5c1512de4750cc897da1df4cdb0 --- /dev/null +++ b/lib/gat/handlers/rewrite_database/module.go @@ -0,0 +1,34 @@ +package rewrite_database + +import ( + "github.com/caddyserver/caddy/v2" + + "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/gat" +) + +func init() { + caddy.RegisterModule((*Module)(nil)) +} + +type Module struct { + Database string `json:"database"` +} + +func (T *Module) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.handlers.rewrite_database", + New: func() caddy.Module { + return new(Module) + }, + } +} + +func (T *Module) Handle(conn *fed.Conn) error { + conn.Database = T.Database + + return nil +} + +var _ gat.Handler = (*Module)(nil) +var _ caddy.Module = (*Module)(nil) diff --git a/lib/gat/handlers/rewrite_parameter/module.go b/lib/gat/handlers/rewrite_parameter/module.go new file mode 100644 index 0000000000000000000000000000000000000000..89b056fb57c4ef8248bcbcfc454e17270dc207aa --- /dev/null +++ b/lib/gat/handlers/rewrite_parameter/module.go @@ -0,0 +1,39 @@ +package rewrite_parameter + +import ( + "github.com/caddyserver/caddy/v2" + + "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/gat" + "gfx.cafe/gfx/pggat/lib/util/strutil" +) + +func init() { + caddy.RegisterModule((*Module)(nil)) +} + +type Module struct { + Key strutil.CIString `json:"key"` + Value string `json:"value"` +} + +func (T *Module) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.handlers.rewrite_parameter", + New: func() caddy.Module { + return new(Module) + }, + } +} + +func (T *Module) Handle(conn *fed.Conn) error { + if conn.InitialParameters == nil { + conn.InitialParameters = make(map[strutil.CIString]string) + } + conn.InitialParameters[T.Key] = T.Value + + return nil +} + +var _ gat.Handler = (*Module)(nil) +var _ caddy.Module = (*Module)(nil) diff --git a/lib/gat/handlers/rewrite_password/module.go b/lib/gat/handlers/rewrite_password/module.go new file mode 100644 index 0000000000000000000000000000000000000000..0cec7729092223be5d0599c5e1f8d5b2ee68bc47 --- /dev/null +++ b/lib/gat/handlers/rewrite_password/module.go @@ -0,0 +1,37 @@ +package rewrite_password + +import ( + "github.com/caddyserver/caddy/v2" + + "gfx.cafe/gfx/pggat/lib/auth/credentials" + "gfx.cafe/gfx/pggat/lib/bouncer/frontends/v0" + "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/gat" +) + +func init() { + caddy.RegisterModule((*Module)(nil)) +} + +type Module struct { + Password string `json:"password"` +} + +func (T *Module) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.handlers.rewrite_password", + New: func() caddy.Module { + return new(Module) + }, + } +} + +func (T *Module) Handle(conn *fed.Conn) error { + return frontends.Authenticate( + conn, + credentials.FromString(conn.User, T.Password), + ) +} + +var _ gat.Handler = (*Module)(nil) +var _ caddy.Module = (*Module)(nil) diff --git a/lib/gat/handlers/rewrite_user/module.go b/lib/gat/handlers/rewrite_user/module.go new file mode 100644 index 0000000000000000000000000000000000000000..a4a301cd6b14f0b789caf81b27e0ee9401508363 --- /dev/null +++ b/lib/gat/handlers/rewrite_user/module.go @@ -0,0 +1,34 @@ +package rewrite_user + +import ( + "github.com/caddyserver/caddy/v2" + + "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/gat" +) + +func init() { + caddy.RegisterModule((*Module)(nil)) +} + +type Module struct { + User string `json:"user"` +} + +func (T *Module) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.handlers.rewrite_user", + New: func() caddy.Module { + return new(Module) + }, + } +} + +func (T *Module) Handle(conn *fed.Conn) error { + conn.User = T.User + + return nil +} + +var _ gat.Handler = (*Module)(nil) +var _ caddy.Module = (*Module)(nil) diff --git a/lib/gat/listen.go b/lib/gat/listen.go new file mode 100644 index 0000000000000000000000000000000000000000..baa1090cef04d4ea7c474b0e98d3807af28d2c0f --- /dev/null +++ b/lib/gat/listen.go @@ -0,0 +1,101 @@ +package gat + +import ( + "context" + "encoding/json" + "fmt" + "net" + "strconv" + "strings" + + "github.com/caddyserver/caddy/v2" + "go.uber.org/zap" + + "gfx.cafe/gfx/pggat/lib/fed" +) + +type ListenerConfig struct { + Address string `json:"address"` + SSL json.RawMessage `json:"ssl,omitempty" caddy:"namespace=pggat.ssl.servers inline_key=provider"` +} + +type Listener struct { + ListenerConfig + + networkAddress caddy.NetworkAddress + ssl SSLServer + + listener net.Listener + + log *zap.Logger +} + +func (T *Listener) accept() (*fed.Conn, error) { + raw, err := T.listener.Accept() + if err != nil { + return nil, err + } + return fed.NewConn( + fed.NewNetConn(raw), + ), nil +} + +func (T *Listener) Provision(ctx caddy.Context) error { + T.log = ctx.Logger() + + if strings.HasPrefix(T.Address, "/") { + // unix address + T.networkAddress = caddy.NetworkAddress{ + Network: "unix", + Host: T.Address, + } + } else { + // tcp address + host, rawPort, ok := strings.Cut(T.Address, ":") + + var port = 5432 + if ok { + var err error + port, err = strconv.Atoi(rawPort) + if err != nil { + return fmt.Errorf("parsing port: %v", err) + } + } + + T.networkAddress = caddy.NetworkAddress{ + Network: "tcp", + Host: host, + StartPort: uint(port), + EndPort: uint(port), + } + } + + if T.SSL != nil { + val, err := ctx.LoadModule(T, "SSL") + if err != nil { + return fmt.Errorf("loading ssl module: %v", err) + } + T.ssl = val.(SSLServer) + } + + return nil +} + +func (T *Listener) Start() error { + listener, err := T.networkAddress.Listen(context.Background(), 0, net.ListenConfig{}) + if err != nil { + return err + } + T.listener = listener.(net.Listener) + + T.log.Info("listening", zap.String("address", T.listener.Addr().String())) + + return nil +} + +func (T *Listener) Stop() error { + return T.listener.Close() +} + +var _ caddy.App = (*Listener)(nil) +var _ caddy.Provisioner = (*Listener)(nil) diff --git a/lib/gat/listener.go b/lib/gat/listener.go deleted file mode 100644 index 71893dbab6362b65b1fce73af27ccd8384fd7370..0000000000000000000000000000000000000000 --- a/lib/gat/listener.go +++ /dev/null @@ -1,7 +0,0 @@ -package gat - -type Listener interface { - Module - - Endpoints() []Endpoint -} diff --git a/lib/gat/matcher.go b/lib/gat/matcher.go new file mode 100644 index 0000000000000000000000000000000000000000..6152f4e455f0b398a69340df1799f27fd3723682 --- /dev/null +++ b/lib/gat/matcher.go @@ -0,0 +1,7 @@ +package gat + +import "gfx.cafe/gfx/pggat/lib/fed" + +type Matcher interface { + Matches(conn *fed.Conn) bool +} diff --git a/lib/gat/matchers/and.go b/lib/gat/matchers/and.go new file mode 100644 index 0000000000000000000000000000000000000000..e4106ed9846f357d87333f9688afd08735183e89 --- /dev/null +++ b/lib/gat/matchers/and.go @@ -0,0 +1,59 @@ +package matchers + +import ( + "encoding/json" + "fmt" + + "github.com/caddyserver/caddy/v2" + + "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/gat" +) + +func init() { + caddy.RegisterModule((*And)(nil)) +} + +type And struct { + And []json.RawMessage `json:"and" caddy:"namespace=pggat.matchers inline_key=matcher"` + + and []gat.Matcher +} + +func (T *And) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.matchers.and", + New: func() caddy.Module { + return new(And) + }, + } +} + +func (T *And) Provision(ctx caddy.Context) error { + T.and = make([]gat.Matcher, 0, len(T.And)) + if T.And != nil { + val, err := ctx.LoadModule(T, "And") + if err != nil { + return fmt.Errorf("loading matcher module: %v", err) + } + + for _, vv := range val.([]any) { + T.and = append(T.and, vv.(gat.Matcher)) + } + } + + return nil +} + +func (T *And) Matches(conn *fed.Conn) bool { + for _, matcher := range T.and { + if !matcher.Matches(conn) { + return false + } + } + return true +} + +var _ gat.Matcher = (*And)(nil) +var _ caddy.Module = (*And)(nil) +var _ caddy.Provisioner = (*And)(nil) diff --git a/lib/gat/matchers/database.go b/lib/gat/matchers/database.go new file mode 100644 index 0000000000000000000000000000000000000000..d75731ac17771fb0caa23f6b0e918f5e5dae53b3 --- /dev/null +++ b/lib/gat/matchers/database.go @@ -0,0 +1,32 @@ +package matchers + +import ( + "github.com/caddyserver/caddy/v2" + + "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/gat" +) + +func init() { + caddy.RegisterModule((*Database)(nil)) +} + +type Database struct { + Database string `json:"database"` +} + +func (T *Database) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.matchers.database", + New: func() caddy.Module { + return new(Database) + }, + } +} + +func (T *Database) Matches(conn *fed.Conn) bool { + return conn.Database == T.Database +} + +var _ gat.Matcher = (*Database)(nil) +var _ caddy.Module = (*Database)(nil) diff --git a/lib/gat/matchers/localaddress.go b/lib/gat/matchers/localaddress.go new file mode 100644 index 0000000000000000000000000000000000000000..c9ac3093f475bf7f3b6ddd3a9c6f128ff68f3933 --- /dev/null +++ b/lib/gat/matchers/localaddress.go @@ -0,0 +1,87 @@ +package matchers + +import ( + "fmt" + "net" + + "github.com/caddyserver/caddy/v2" + + "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/gat" +) + +func init() { + caddy.RegisterModule((*LocalAddress)(nil)) +} + +type LocalAddress struct { + Network string `json:"network"` + Address string `json:"address"` + + addr net.Addr +} + +func (T *LocalAddress) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.matchers.local_address", + New: func() caddy.Module { + return new(LocalAddress) + }, + } +} + +func (T *LocalAddress) Provision(ctx caddy.Context) error { + var err error + switch T.Network { + case "tcp", "tcp4", "tcp6": + T.addr, err = net.ResolveTCPAddr(T.Network, T.Address) + case "udp", "udp4", "udp6": + T.addr, err = net.ResolveUDPAddr(T.Network, T.Address) + case "ip", "ip4", "ip6": + T.addr, err = net.ResolveIPAddr(T.Network, T.Address) + case "unix", "unixgram", "unixpacket": + T.addr, err = net.ResolveUnixAddr(T.Network, T.Address) + default: + err = fmt.Errorf("unknown network: %s", T.Network) + } + return err +} + +func (T *LocalAddress) Matches(conn *fed.Conn) bool { + netConn, ok := conn.ReadWriteCloser.(*fed.NetConn) + if !ok { + return false + } + switch addr := netConn.LocalAddr().(type) { + case *net.TCPAddr: + expected, ok := T.addr.(*net.TCPAddr) + if !ok { + return false + } + return addr.Port == expected.Port && addr.Zone == expected.Zone && (expected.IP == nil || addr.IP.Equal(expected.IP)) + case *net.IPAddr: + expected, ok := T.addr.(*net.IPAddr) + if !ok { + return false + } + return addr.Zone == expected.Zone && (expected.IP == nil || addr.IP.Equal(expected.IP)) + case *net.UDPAddr: + expected, ok := T.addr.(*net.UDPAddr) + if !ok { + return false + } + return addr.Port == expected.Port && addr.Zone == expected.Zone && (expected.IP == nil || addr.IP.Equal(expected.IP)) + case *net.UnixAddr: + expected, ok := T.addr.(*net.UnixAddr) + if !ok { + return false + } + return addr.Name == expected.Name && addr.Net == expected.Net + default: + return false + } +} + +var _ gat.Matcher = (*LocalAddress)(nil) +var _ caddy.Module = (*LocalAddress)(nil) +var _ caddy.Provisioner = (*LocalAddress)(nil) diff --git a/lib/gat/matchers/not.go b/lib/gat/matchers/not.go new file mode 100644 index 0000000000000000000000000000000000000000..22e7facd65365abc35524ef2f09b436fec1044e1 --- /dev/null +++ b/lib/gat/matchers/not.go @@ -0,0 +1,51 @@ +package matchers + +import ( + "encoding/json" + "fmt" + + "github.com/caddyserver/caddy/v2" + + "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/gat" +) + +func init() { + caddy.RegisterModule((*Not)(nil)) +} + +type Not struct { + Not json.RawMessage `json:"not" caddy:"namespace=pggat.matchers inline_key=matcher"` + + not gat.Matcher +} + +func (T *Not) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.matchers.not", + New: func() caddy.Module { + return new(Not) + }, + } +} + +func (T *Not) Provision(ctx caddy.Context) error { + if T.Not != nil { + val, err := ctx.LoadModule(T, "Not") + if err != nil { + return fmt.Errorf("loading matcher module: %v", err) + } + + T.not = val.(gat.Matcher) + } + + return nil +} + +func (T *Not) Matches(conn *fed.Conn) bool { + return !T.not.Matches(conn) +} + +var _ gat.Matcher = (*Not)(nil) +var _ caddy.Module = (*Not)(nil) +var _ caddy.Provisioner = (*Not)(nil) diff --git a/lib/gat/matchers/or.go b/lib/gat/matchers/or.go new file mode 100644 index 0000000000000000000000000000000000000000..baf7e89f75d4669a52c2c924eb99ccac76f08059 --- /dev/null +++ b/lib/gat/matchers/or.go @@ -0,0 +1,59 @@ +package matchers + +import ( + "encoding/json" + "fmt" + + "github.com/caddyserver/caddy/v2" + + "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/gat" +) + +func init() { + caddy.RegisterModule((*Or)(nil)) +} + +type Or struct { + Or []json.RawMessage `json:"or" caddy:"namespace=pggat.matchers inline_key=matcher"` + + or []gat.Matcher +} + +func (T *Or) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.matchers.or", + New: func() caddy.Module { + return new(Or) + }, + } +} + +func (T *Or) Provision(ctx caddy.Context) error { + T.or = make([]gat.Matcher, 0, len(T.Or)) + if T.Or != nil { + val, err := ctx.LoadModule(T, "Or") + if err != nil { + return fmt.Errorf("loading matcher module: %v", err) + } + + for _, vv := range val.([]any) { + T.or = append(T.or, vv.(gat.Matcher)) + } + } + + return nil +} + +func (T *Or) Matches(conn *fed.Conn) bool { + for _, matcher := range T.or { + if matcher.Matches(conn) { + return true + } + } + return false +} + +var _ gat.Matcher = (*Or)(nil) +var _ caddy.Module = (*Or)(nil) +var _ caddy.Provisioner = (*Or)(nil) diff --git a/lib/gat/matchers/ssl.go b/lib/gat/matchers/ssl.go new file mode 100644 index 0000000000000000000000000000000000000000..57ae1a0acb907587d5c30070051af0a5a30d4017 --- /dev/null +++ b/lib/gat/matchers/ssl.go @@ -0,0 +1,36 @@ +package matchers + +import ( + "github.com/caddyserver/caddy/v2" + + "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/gat" +) + +func init() { + caddy.RegisterModule((*SSL)(nil)) +} + +type SSL struct { + SSL bool `json:"ssl"` +} + +func (T *SSL) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.matchers.ssl", + New: func() caddy.Module { + return new(SSL) + }, + } +} + +func (T *SSL) Matches(conn *fed.Conn) bool { + sslConn, ok := conn.ReadWriteCloser.(fed.SSL) + if !ok { + return T.SSL == false + } + return sslConn.SSL() == T.SSL +} + +var _ gat.Matcher = (*SSL)(nil) +var _ caddy.Module = (*SSL)(nil) diff --git a/lib/gat/matchers/startupparameter.go b/lib/gat/matchers/startupparameter.go new file mode 100644 index 0000000000000000000000000000000000000000..2cbacf2425e96c8f32ae5f53c8ba0418ba717471 --- /dev/null +++ b/lib/gat/matchers/startupparameter.go @@ -0,0 +1,34 @@ +package matchers + +import ( + "github.com/caddyserver/caddy/v2" + + "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/gat" + "gfx.cafe/gfx/pggat/lib/util/strutil" +) + +func init() { + caddy.RegisterModule((*StartupParameter)(nil)) +} + +type StartupParameter struct { + Key strutil.CIString `json:"key"` + Value string `json:"value"` +} + +func (T *StartupParameter) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.matchers.startup_parameter", + New: func() caddy.Module { + return new(StartupParameter) + }, + } +} + +func (T *StartupParameter) Matches(conn *fed.Conn) bool { + return conn.InitialParameters[T.Key] == T.Value +} + +var _ gat.Matcher = (*StartupParameter)(nil) +var _ caddy.Module = (*StartupParameter)(nil) diff --git a/lib/gat/matchers/user.go b/lib/gat/matchers/user.go new file mode 100644 index 0000000000000000000000000000000000000000..33f94cf09fd6cdfe626432452a9c4cf7bf231744 --- /dev/null +++ b/lib/gat/matchers/user.go @@ -0,0 +1,32 @@ +package matchers + +import ( + "github.com/caddyserver/caddy/v2" + + "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/gat" +) + +func init() { + caddy.RegisterModule((*User)(nil)) +} + +type User struct { + User string `json:"user"` +} + +func (T *User) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.matchers.user", + New: func() caddy.Module { + return new(User) + }, + } +} + +func (T *User) Matches(conn *fed.Conn) bool { + return conn.User == T.User +} + +var _ gat.Matcher = (*User)(nil) +var _ caddy.Module = (*User)(nil) diff --git a/lib/gat/metrics/pools.go b/lib/gat/metrics/pools.go index 3c929867ebb26ee150ecd8c64d11e96d52632e3a..bfd4f05c8d713efe03e4b89830ecac21a511c6bb 100644 --- a/lib/gat/metrics/pools.go +++ b/lib/gat/metrics/pools.go @@ -1,6 +1,6 @@ package metrics -type Pools struct { +type Handler struct { // TODO(garet) Pool } diff --git a/lib/gat/metrics/server.go b/lib/gat/metrics/server.go index 0c6ed7170b830d1cff7abe0c774787e8514adabc..5abb26d33a8d82dbb5e4443ddc3ce3e0c3b5b5a7 100644 --- a/lib/gat/metrics/server.go +++ b/lib/gat/metrics/server.go @@ -2,5 +2,5 @@ package metrics type Server struct { // TODO(garet) - Pools + Handler } diff --git a/lib/gat/module.go b/lib/gat/module.go deleted file mode 100644 index 0ec77c1684af9d0f78fd46c73493fa1ebbf33306..0000000000000000000000000000000000000000 --- a/lib/gat/module.go +++ /dev/null @@ -1,5 +0,0 @@ -package gat - -type Module interface { - GatModule() -} diff --git a/lib/gat/modules/cloud_sql_discovery/config.go b/lib/gat/modules/cloud_sql_discovery/config.go deleted file mode 100644 index c6f84166304f6e0cc05941b9475bc4a52053575d..0000000000000000000000000000000000000000 --- a/lib/gat/modules/cloud_sql_discovery/config.go +++ /dev/null @@ -1,23 +0,0 @@ -package cloud_sql_discovery - -import ( - "errors" - - "gfx.cafe/util/go/gun" -) - -type Config struct { - Project string `env:"PGGAT_GC_PROJECT"` - IpAddressType string `env:"PGGAT_GC_IP_ADDR_TYPE" default:"PRIMARY"` - AuthUser string `env:"PGGAT_GC_AUTH_USER" default:"pggat"` - AuthPassword string `env:"PGGAT_GC_AUTH_PASSWORD"` -} - -func Load() (Config, error) { - var conf Config - gun.Load(&conf) - if conf.Project == "" { - return Config{}, errors.New("expected PGGAT_GC_PROJECT") - } - return conf, nil -} diff --git a/lib/gat/modules/cloud_sql_discovery/module.go b/lib/gat/modules/cloud_sql_discovery/module.go deleted file mode 100644 index 3cee645f095d1a240925b7da024fa3767904a6b5..0000000000000000000000000000000000000000 --- a/lib/gat/modules/cloud_sql_discovery/module.go +++ /dev/null @@ -1,37 +0,0 @@ -package cloud_sql_discovery - -import ( - "crypto/tls" - "time" - - "gfx.cafe/gfx/pggat/lib/bouncer" - "gfx.cafe/gfx/pggat/lib/gat/modules/discovery" - "gfx.cafe/gfx/pggat/lib/util/strutil" -) - -func NewModule(config Config) (*discovery.Module, error) { - d, err := NewDiscoverer(config) - if err != nil { - return nil, err - } - - return discovery.NewModule(discovery.Config{ - ReconcilePeriod: 5 * time.Minute, - Discoverer: d, - ServerSSLMode: bouncer.SSLModePrefer, - ServerSSLConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - ServerReconnectInitialTime: 5 * time.Second, - ServerReconnectMaxTime: 5 * time.Second, - ServerIdleTimeout: 5 * time.Minute, - TrackedParameters: []strutil.CIString{ - strutil.MakeCIString("client_encoding"), - strutil.MakeCIString("datestyle"), - strutil.MakeCIString("timezone"), - strutil.MakeCIString("standard_conforming_strings"), - strutil.MakeCIString("application_name"), - }, - PoolMode: "transaction", // TODO(garet) - }) -} diff --git a/lib/gat/modules/digitalocean_discovery/config.go b/lib/gat/modules/digitalocean_discovery/config.go deleted file mode 100644 index d228a3fff2a08b53da3e2c6b9c7b6baa058a8e16..0000000000000000000000000000000000000000 --- a/lib/gat/modules/digitalocean_discovery/config.go +++ /dev/null @@ -1,23 +0,0 @@ -package digitalocean_discovery - -import ( - "errors" - - "gfx.cafe/util/go/gun" -) - -type Config struct { - APIKey string `env:"PGGAT_DO_API_KEY"` - Private bool `env:"PGGAT_DO_PRIVATE"` - PoolMode string `env:"PGGAT_POOL_MODE"` -} - -func Load() (Config, error) { - var conf Config - gun.Load(&conf) - if conf.APIKey == "" { - return Config{}, errors.New("expected auth token") - } - - return conf, nil -} diff --git a/lib/gat/modules/digitalocean_discovery/module.go b/lib/gat/modules/digitalocean_discovery/module.go deleted file mode 100644 index 24dc545c2337b33f84642704dba93514af4cedff..0000000000000000000000000000000000000000 --- a/lib/gat/modules/digitalocean_discovery/module.go +++ /dev/null @@ -1,37 +0,0 @@ -package digitalocean_discovery - -import ( - "crypto/tls" - "time" - - "gfx.cafe/gfx/pggat/lib/bouncer" - "gfx.cafe/gfx/pggat/lib/gat/modules/discovery" - "gfx.cafe/gfx/pggat/lib/util/strutil" -) - -func NewModule(config Config) (*discovery.Module, error) { - d, err := NewDiscoverer(config) - if err != nil { - return nil, err - } - - return discovery.NewModule(discovery.Config{ - ReconcilePeriod: 5 * time.Minute, - Discoverer: d, - ServerSSLMode: bouncer.SSLModeRequire, - ServerSSLConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - ServerReconnectInitialTime: 5 * time.Second, - ServerReconnectMaxTime: 5 * time.Second, - ServerIdleTimeout: 5 * time.Minute, - TrackedParameters: []strutil.CIString{ - strutil.MakeCIString("client_encoding"), - strutil.MakeCIString("datestyle"), - strutil.MakeCIString("timezone"), - strutil.MakeCIString("standard_conforming_strings"), - strutil.MakeCIString("application_name"), - }, - PoolMode: "transaction", // TODO(garet) - }) -} diff --git a/lib/gat/modules/discovery/config.go b/lib/gat/modules/discovery/config.go deleted file mode 100644 index b2633ddb98d84c088df8161bf9a61f6c811ca9a5..0000000000000000000000000000000000000000 --- a/lib/gat/modules/discovery/config.go +++ /dev/null @@ -1,28 +0,0 @@ -package discovery - -import ( - "crypto/tls" - "time" - - "gfx.cafe/gfx/pggat/lib/bouncer" - "gfx.cafe/gfx/pggat/lib/util/strutil" -) - -type Config struct { - // ReconcilePeriod is how often the module should check for changes. 0 = disable - ReconcilePeriod time.Duration - - Discoverer Discoverer - - ServerSSLMode bouncer.SSLMode - ServerSSLConfig *tls.Config - ServerStartupParameters map[strutil.CIString]string - ServerReconnectInitialTime time.Duration - ServerReconnectMaxTime time.Duration - ServerIdleTimeout time.Duration - ServerResetQuery string - - TrackedParameters []strutil.CIString - - PoolMode string -} diff --git a/lib/gat/modules/pgbouncer/module.go b/lib/gat/modules/pgbouncer/module.go deleted file mode 100644 index 1acb9c836fc2a8bf801f773fc3165cf39572faf3..0000000000000000000000000000000000000000 --- a/lib/gat/modules/pgbouncer/module.go +++ /dev/null @@ -1,320 +0,0 @@ -package pgbouncer - -import ( - "crypto/tls" - "errors" - "io" - "net" - "strconv" - "strings" - "time" - - "tuxpa.in/a/zlog/log" - - "gfx.cafe/gfx/pggat/lib/auth/credentials" - "gfx.cafe/gfx/pggat/lib/bouncer/backends/v0" - "gfx.cafe/gfx/pggat/lib/bouncer/frontends/v0" - "gfx.cafe/gfx/pggat/lib/gat" - "gfx.cafe/gfx/pggat/lib/gat/metrics" - "gfx.cafe/gfx/pggat/lib/gat/pool" - "gfx.cafe/gfx/pggat/lib/gat/pool/pools/session" - "gfx.cafe/gfx/pggat/lib/gat/pool/pools/transaction" - "gfx.cafe/gfx/pggat/lib/gat/pool/recipe" - "gfx.cafe/gfx/pggat/lib/gsql" - "gfx.cafe/gfx/pggat/lib/util/maps" - "gfx.cafe/gfx/pggat/lib/util/strutil" -) - -type authQueryResult struct { - Username string `sql:"0"` - Password string `sql:"1"` -} - -type Module struct { - config Config - - pools maps.TwoKey[string, string, *gat.Pool] -} - -func NewModule(config Config) (*Module, error) { - return &Module{ - config: config, - }, nil -} - -func (T *Module) getPassword(user, database string) (string, bool) { - // try to get password - password, ok := T.config.PgBouncer.AuthFile[user] - if !ok { - // try to run auth query - if T.config.PgBouncer.AuthQuery == "" { - return "", false - } - - authUser := T.config.Databases[database].AuthUser - if authUser == "" { - authUser = T.config.PgBouncer.AuthUser - if authUser == "" { - return "", false - } - } - - authPool := T.Lookup(authUser, database) - if authPool == nil { - return "", false - } - - var result authQueryResult - client := new(gsql.Client) - err := gsql.ExtendedQuery(client, &result, T.config.PgBouncer.AuthQuery, user) - if err != nil { - log.Println("auth query failed:", err) - return "", false - } - err = client.Close() - if err != nil { - log.Println("auth query failed:", err) - return "", false - } - err = authPool.ServeBot(client) - if err != nil && !errors.Is(err, io.EOF) { - log.Println("auth query failed:", err) - return "", false - } - - if result.Username != user { - // user not found - return "", false - } - - password = result.Password - } - - return password, true -} - -func (T *Module) tryCreate(user, database string) *gat.Pool { - db, ok := T.config.Databases[database] - if !ok { - // try wildcard - db, ok = T.config.Databases["*"] - if !ok { - return nil - } - } - - // try to get password - password, ok := T.getPassword(user, database) - if !ok { - return nil - } - - creds := credentials.FromString(user, password) - - serverDatabase := db.DBName - if serverDatabase == "" { - serverDatabase = database - } - - configUser := T.config.Users[user] - - poolMode := db.PoolMode - if poolMode == "" { - poolMode = configUser.PoolMode - if poolMode == "" { - poolMode = T.config.PgBouncer.PoolMode - } - } - - trackedParameters := append([]strutil.CIString{ - strutil.MakeCIString("client_encoding"), - strutil.MakeCIString("datestyle"), - strutil.MakeCIString("timezone"), - strutil.MakeCIString("standard_conforming_strings"), - strutil.MakeCIString("application_name"), - }, T.config.PgBouncer.TrackExtraParameters...) - - serverLoginRetry := time.Duration(T.config.PgBouncer.ServerLoginRetry * float64(time.Second)) - - poolOptions := pool.Options{ - Credentials: creds, - TrackedParameters: trackedParameters, - ServerResetQuery: T.config.PgBouncer.ServerResetQuery, - ServerIdleTimeout: time.Duration(T.config.PgBouncer.ServerIdleTimeout * float64(time.Second)), - ServerReconnectInitialTime: serverLoginRetry, - } - - switch poolMode { - case PoolModeSession: - poolOptions = session.Apply(poolOptions) - case PoolModeTransaction: - if T.config.PgBouncer.ServerResetQueryAlways == 0 { - poolOptions.ServerResetQuery = "" - } - poolOptions = transaction.Apply(poolOptions) - default: - return nil - } - p := pool.NewPool(poolOptions) - - T.pools.Store(user, database, p) - - var d recipe.Dialer - - serverCreds := creds - if db.Password != "" { - // lookup password - serverCreds = credentials.FromString(user, db.Password) - } - - acceptOptions := backends.AcceptOptions{ - SSLMode: T.config.PgBouncer.ServerTLSSSLMode, - SSLConfig: &tls.Config{ - InsecureSkipVerify: true, // TODO(garet) - }, - Username: user, - Credentials: serverCreds, - Database: serverDatabase, - StartupParameters: db.StartupParameters, - } - - if db.Host == "" || strings.HasPrefix(db.Host, "/") { - // connect over unix socket - dir := db.Host - port := db.Port - if !strings.HasPrefix(dir, "/") { - dir = dir + "/" - } - - if port == 0 { - port = 5432 - } - - dir = dir + ".s.PGSQL." + strconv.Itoa(port) - - d = recipe.Dialer{ - Network: "unix", - Address: dir, - AcceptOptions: acceptOptions, - } - } else { - var address string - if db.Port == 0 { - address = net.JoinHostPort(db.Host, "5432") - } else { - address = net.JoinHostPort(db.Host, strconv.Itoa(db.Port)) - } - - // connect over tcp - d = recipe.Dialer{ - Network: "tcp", - Address: address, - AcceptOptions: acceptOptions, - } - } - - recipeOptions := recipe.Options{ - Dialer: d, - MinConnections: db.MinPoolSize, - MaxConnections: db.MaxDBConnections, - } - if recipeOptions.MinConnections == 0 { - recipeOptions.MinConnections = T.config.PgBouncer.MinPoolSize - } - if recipeOptions.MaxConnections == 0 { - recipeOptions.MaxConnections = T.config.PgBouncer.MaxDBConnections - } - r := recipe.NewRecipe(recipeOptions) - - p.AddRecipe("pgbouncer", r) - - return p -} - -func (T *Module) Lookup(user, database string) *gat.Pool { - p, _ := T.pools.Load(user, database) - if p != nil { - return p - } - - // try to create pool - return T.tryCreate(user, database) -} - -func (T *Module) ReadMetrics(metrics *metrics.Pools) { - T.pools.Range(func(_ string, _ string, p *gat.Pool) bool { - p.ReadMetrics(&metrics.Pool) - return true - }) -} - -func (T *Module) Endpoints() []gat.Endpoint { - trackedParameters := append([]strutil.CIString{ - strutil.MakeCIString("client_encoding"), - strutil.MakeCIString("datestyle"), - strutil.MakeCIString("timezone"), - strutil.MakeCIString("standard_conforming_strings"), - strutil.MakeCIString("application_name"), - }, T.config.PgBouncer.TrackExtraParameters...) - - allowedStartupParameters := append(trackedParameters, T.config.PgBouncer.IgnoreStartupParameters...) - var sslConfig *tls.Config - if T.config.PgBouncer.ClientTLSCertFile != "" && T.config.PgBouncer.ClientTLSKeyFile != "" { - certificate, err := tls.LoadX509KeyPair(T.config.PgBouncer.ClientTLSCertFile, T.config.PgBouncer.ClientTLSKeyFile) - if err != nil { - log.Printf("error loading X509 keypair: %v", err) - } else { - sslConfig = &tls.Config{ - Certificates: []tls.Certificate{ - certificate, - }, - } - } - } - - acceptOptions := frontends.AcceptOptions{ - SSLRequired: T.config.PgBouncer.ClientTLSSSLMode.IsRequired(), - SSLConfig: sslConfig, - AllowedStartupOptions: allowedStartupParameters, - } - - var endpoints []gat.Endpoint - - if T.config.PgBouncer.ListenAddr != "" { - listenAddr := T.config.PgBouncer.ListenAddr - if listenAddr == "*" { - listenAddr = "" - } - - listen := net.JoinHostPort(listenAddr, strconv.Itoa(T.config.PgBouncer.ListenPort)) - - endpoints = append(endpoints, gat.Endpoint{ - Network: "tcp", - Address: listen, - AcceptOptions: acceptOptions, - }) - } - - // listen on unix socket - dir := T.config.PgBouncer.UnixSocketDir - port := T.config.PgBouncer.ListenPort - - if !strings.HasSuffix(dir, "/") { - dir = dir + "/" - } - dir = dir + ".s.PGSQL." + strconv.Itoa(port) - - endpoints = append(endpoints, gat.Endpoint{ - Network: "unix", - Address: dir, - AcceptOptions: acceptOptions, - }) - - return endpoints -} - -func (T *Module) GatModule() {} - -var _ gat.Module = (*Module)(nil) -var _ gat.Provider = (*Module)(nil) -var _ gat.Listener = (*Module)(nil) diff --git a/lib/gat/modules/raw_pools/module.go b/lib/gat/modules/raw_pools/module.go deleted file mode 100644 index 14ab7262f5316a602d0f5aa602491f324b1d8309..0000000000000000000000000000000000000000 --- a/lib/gat/modules/raw_pools/module.go +++ /dev/null @@ -1,56 +0,0 @@ -package raw_pools - -import ( - "sync" - - "gfx.cafe/gfx/pggat/lib/gat" - "gfx.cafe/gfx/pggat/lib/gat/metrics" - "gfx.cafe/gfx/pggat/lib/gat/pool" - "gfx.cafe/gfx/pggat/lib/util/maps" -) - -type Module struct { - pools maps.TwoKey[string, string, *pool.Pool] - mu sync.RWMutex -} - -func NewModule() (*Module, error) { - return &Module{}, nil -} - -func (T *Module) GatModule() {} - -func (T *Module) Add(user, database string, p *pool.Pool) { - T.mu.Lock() - defer T.mu.Unlock() - - T.pools.Store(user, database, p) -} - -func (T *Module) Remove(user, database string) { - T.mu.Lock() - defer T.mu.Unlock() - - T.pools.Delete(user, database) -} - -func (T *Module) Lookup(user, database string) *gat.Pool { - T.mu.RLock() - defer T.mu.RUnlock() - - p, _ := T.pools.Load(user, database) - return p -} - -func (T *Module) ReadMetrics(metrics *metrics.Pools) { - T.mu.RLock() - defer T.mu.RUnlock() - - T.pools.Range(func(_ string, _ string, p *pool.Pool) bool { - p.ReadMetrics(&metrics.Pool) - return true - }) -} - -var _ gat.Module = (*Module)(nil) -var _ gat.Provider = (*Module)(nil) diff --git a/lib/gat/modules/zalando/config.go b/lib/gat/modules/zalando/config.go deleted file mode 100644 index ad174dee0e9ead966fda8caf83c5f211929fa3d3..0000000000000000000000000000000000000000 --- a/lib/gat/modules/zalando/config.go +++ /dev/null @@ -1,32 +0,0 @@ -package zalando - -import ( - "errors" - - "gfx.cafe/util/go/gun" -) - -type Config struct { - PGHost string `env:"PGHOST"` - PGPort int `env:"PGPORT"` - PGUser string `env:"PGUSER"` - PGSchema string `env:"PGSCHEMA"` - PGPassword string `env:"PGPASSWORD"` - PoolerPort int `env:"CONNECTION_POOLER_PORT"` - PoolerMode string `env:"CONNECTION_POOLER_MODE"` - PoolerDefaultSize int `env:"CONNECTION_POOLER_DEFAULT_SIZE"` - PoolerMinSize int `env:"CONNECTION_POOLER_MIN_SIZE"` - PoolerReserveSize int `env:"CONNECTION_POOLER_RESERVE_SIZE"` - PoolerMaxClientConn int `env:"CONNECTION_POOLER_MAX_CLIENT_CONN"` - PoolerMaxDBConn int `env:"CONNECTION_POOLER_MAX_DB_CONN"` -} - -func Load() (Config, error) { - var conf Config - gun.Load(&conf) - if conf.PoolerMode == "" { - return Config{}, errors.New("expected pooler mode") - } - - return conf, nil -} diff --git a/lib/gat/modules/zalando_operator_discovery/config.go b/lib/gat/modules/zalando_operator_discovery/config.go deleted file mode 100644 index c4a4945147b5a87353830c0b42ca0d3adfaff80b..0000000000000000000000000000000000000000 --- a/lib/gat/modules/zalando_operator_discovery/config.go +++ /dev/null @@ -1,26 +0,0 @@ -package zalando_operator_discovery - -import ( - "gfx.cafe/util/go/gun" - "k8s.io/client-go/rest" -) - -type Config struct { - Namespace string `env:"PGGAT_NAMESPACE" default:"default"` - ConfigMapName string `env:"CONFIG_MAP_NAME"` - OperatorConfigurationObject string `env:"POSTGRES_OPERATOR_CONFIGURATION_OBJECT"` - - Rest *rest.Config -} - -func Load() (Config, error) { - var config Config - gun.Load(&config) - - var err error - config.Rest, err = rest.InClusterConfig() - if err != nil { - return Config{}, err - } - return config, nil -} diff --git a/lib/gat/modules/zalando_operator_discovery/module.go b/lib/gat/modules/zalando_operator_discovery/module.go deleted file mode 100644 index e3702a15ba0422a879135cbee8d7e115edc97f23..0000000000000000000000000000000000000000 --- a/lib/gat/modules/zalando_operator_discovery/module.go +++ /dev/null @@ -1,40 +0,0 @@ -package zalando_operator_discovery - -import ( - "crypto/tls" - "time" - - "gfx.cafe/gfx/pggat/lib/bouncer" - "gfx.cafe/gfx/pggat/lib/gat/modules/discovery" - "gfx.cafe/gfx/pggat/lib/util/strutil" -) - -func NewModule(config Config) (*discovery.Module, error) { - d, err := NewDiscoverer(config) - if err != nil { - return nil, err - } - m, err := discovery.NewModule(discovery.Config{ - Discoverer: d, - ServerSSLMode: bouncer.SSLModePrefer, - ServerSSLConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - ServerReconnectInitialTime: 5 * time.Second, - ServerReconnectMaxTime: 5 * time.Second, - ServerIdleTimeout: 5 * time.Minute, - // ServerResetQuery: "discard all", - TrackedParameters: []strutil.CIString{ - strutil.MakeCIString("client_encoding"), - strutil.MakeCIString("datestyle"), - strutil.MakeCIString("timezone"), - strutil.MakeCIString("standard_conforming_strings"), - strutil.MakeCIString("application_name"), - }, - PoolMode: "transaction", // TODO(garet) pool mode from operator config - }) - if err != nil { - return nil, err - } - return m, nil -} diff --git a/lib/gat/pool/client.go b/lib/gat/pool/client.go index 2d687e10229b09be35905ad882daf27ac27eee44..c6d3c76e45367c7843c3ad651a9bf694c538f3c8 100644 --- a/lib/gat/pool/client.go +++ b/lib/gat/pool/client.go @@ -2,12 +2,9 @@ package pool import ( "gfx.cafe/gfx/pggat/lib/fed" - "gfx.cafe/gfx/pggat/lib/middleware" - "gfx.cafe/gfx/pggat/lib/middleware/interceptor" - "gfx.cafe/gfx/pggat/lib/middleware/middlewares/eqp" - "gfx.cafe/gfx/pggat/lib/middleware/middlewares/ps" - "gfx.cafe/gfx/pggat/lib/middleware/middlewares/unterminate" - "gfx.cafe/gfx/pggat/lib/util/strutil" + "gfx.cafe/gfx/pggat/lib/fed/middlewares/eqp" + "gfx.cafe/gfx/pggat/lib/fed/middlewares/ps" + "gfx.cafe/gfx/pggat/lib/fed/middlewares/unterminate" ) type pooledClient struct { @@ -18,39 +15,31 @@ type pooledClient struct { } func newClient( - options Options, - conn fed.Conn, - initialParameters map[strutil.CIString]string, - backendKey [8]byte, + options Config, + conn *fed.Conn, ) *pooledClient { - middlewares := []middleware.Middleware{ + conn.Middleware = append( + conn.Middleware, unterminate.Unterminate, - } + ) var psClient *ps.Client if options.ParameterStatusSync == ParameterStatusSyncDynamic { // add ps middleware - psClient = ps.NewClient(initialParameters) - middlewares = append(middlewares, psClient) + psClient = ps.NewClient(conn.InitialParameters) + conn.Middleware = append(conn.Middleware, psClient) } var eqpClient *eqp.Client if options.ExtendedQuerySync { // add eqp middleware eqpClient = eqp.NewClient() - middlewares = append(middlewares, eqpClient) + conn.Middleware = append(conn.Middleware, eqpClient) } - conn = interceptor.NewInterceptor( - conn, - middlewares..., - ) - return &pooledClient{ pooledConn: makeConn( conn, - initialParameters, - backendKey, ), ps: psClient, eqp: eqpClient, diff --git a/lib/gat/pool/options.go b/lib/gat/pool/config.go similarity index 73% rename from lib/gat/pool/options.go rename to lib/gat/pool/config.go index a682419ba82aedc1fae8414bcc6933453609a891..36604526c2916b47a33c13b5e2dff5a9a81ae8d8 100644 --- a/lib/gat/pool/options.go +++ b/lib/gat/pool/config.go @@ -1,9 +1,9 @@ package pool import ( - "time" + "go.uber.org/zap" - "gfx.cafe/gfx/pggat/lib/auth" + "gfx.cafe/gfx/pggat/lib/util/dur" "gfx.cafe/gfx/pggat/lib/util/strutil" ) @@ -20,33 +20,42 @@ const ( ParameterStatusSyncDynamic ) -type Options struct { - Credentials auth.Credentials - +type PoolingConfig struct { NewPooler func() Pooler // ReleaseAfterTransaction toggles whether servers should be released and re acquired after each transaction. // Use false for lower latency // Use true for better balancing ReleaseAfterTransaction bool - ServerResetQuery string + // ParameterStatusSync is the parameter syncing mode + ParameterStatusSync ParameterStatusSync + + // ExtendedQuerySync controls whether prepared statements and portals should be tracked and synced before use. + // Use false for lower latency + // Use true for transaction pooling + ExtendedQuerySync bool +} + +type ManagementConfig struct { + ServerResetQuery string `json:"server_reset_query,omitempty"` // ServerIdleTimeout defines how long a server may be idle before it is disconnected - ServerIdleTimeout time.Duration + ServerIdleTimeout dur.Duration `json:"server_idle_timeout,omitempty"` // ServerReconnectInitialTime defines how long to wait initially before attempting a server reconnect // 0 = disable, don't retry - ServerReconnectInitialTime time.Duration + ServerReconnectInitialTime dur.Duration `json:"server_reconnect_initial_time,omitempty"` // ServerReconnectMaxTime defines the max amount of time to wait before attempting a server reconnect // 0 = disable, back off infinitely - ServerReconnectMaxTime time.Duration + ServerReconnectMaxTime dur.Duration `json:"server_reconnect_max_time,omitempty"` - // ParameterStatusSync is the parameter syncing mode - ParameterStatusSync ParameterStatusSync // TrackedParameters are parameters which should be synced by updating the server, not the client. - TrackedParameters []strutil.CIString + TrackedParameters []strutil.CIString `json:"tracked_parameters,omitempty"` +} - // ExtendedQuerySync controls whether prepared statements and portals should be tracked and synced before use. - // Use false for lower latency - // Use true for transaction pooling - ExtendedQuerySync bool +type Config struct { + PoolingConfig + + ManagementConfig + + Logger *zap.Logger } diff --git a/lib/gat/pool/conn.go b/lib/gat/pool/conn.go index 8e321311f40d1b32ef67b61e04ee4751be5c6004..065a645895951e6693e756a2054e03f45f89b031 100644 --- a/lib/gat/pool/conn.go +++ b/lib/gat/pool/conn.go @@ -15,12 +15,7 @@ import ( type pooledConn struct { id uuid.UUID - conn fed.Conn - // please someone fix runtime.convI2I - rw fed.ReadWriter - - initialParameters map[strutil.CIString]string - backendKey [8]byte + conn *fed.Conn // metrics @@ -38,16 +33,11 @@ type pooledConn struct { } func makeConn( - conn fed.Conn, - initialParameters map[strutil.CIString]string, - backendKey [8]byte, + conn *fed.Conn, ) pooledConn { return pooledConn{ - id: uuid.New(), - conn: conn, - rw: conn, - initialParameters: initialParameters, - backendKey: backendKey, + id: uuid.New(), + conn: conn, since: time.Now(), } @@ -57,21 +47,16 @@ func (T *pooledConn) GetID() uuid.UUID { return T.id } -func (T *pooledConn) GetConn() fed.Conn { +func (T *pooledConn) GetConn() *fed.Conn { return T.conn } -// GetReadWriter is the exact same as GetConn but bypasses the runtime.convI2I -func (T *pooledConn) GetReadWriter() fed.ReadWriter { - return T.rw -} - func (T *pooledConn) GetInitialParameters() map[strutil.CIString]string { - return T.initialParameters + return T.conn.InitialParameters } func (T *pooledConn) GetBackendKey() [8]byte { - return T.backendKey + return T.conn.BackendKey } func (T *pooledConn) TransactionComplete() { diff --git a/lib/gat/pool/errors.go b/lib/gat/pool/errors.go new file mode 100644 index 0000000000000000000000000000000000000000..b0a18cecdeae5d460c0c6df65417ac908aaf926b --- /dev/null +++ b/lib/gat/pool/errors.go @@ -0,0 +1,5 @@ +package pool + +import "errors" + +var ErrClosed = errors.New("pool closed") diff --git a/lib/gat/pool/flow.go b/lib/gat/pool/flow.go index a186ea7ec9779863a089a1e6419d9e78cfb0bfb9..e12636210e444859639229cf308bb4e48c2b6cec 100644 --- a/lib/gat/pool/flow.go +++ b/lib/gat/pool/flow.go @@ -3,14 +3,14 @@ package pool import ( "gfx.cafe/gfx/pggat/lib/bouncer/backends/v0" "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/fed/middlewares/eqp" + "gfx.cafe/gfx/pggat/lib/fed/middlewares/ps" packets "gfx.cafe/gfx/pggat/lib/fed/packets/v3.0" "gfx.cafe/gfx/pggat/lib/gat/metrics" - "gfx.cafe/gfx/pggat/lib/middleware/middlewares/eqp" - "gfx.cafe/gfx/pggat/lib/middleware/middlewares/ps" "gfx.cafe/gfx/pggat/lib/util/slices" ) -func pair(options Options, client *pooledClient, server *pooledServer) (clientErr, serverErr error) { +func pair(options Config, client *pooledClient, server *pooledServer) (clientErr, serverErr error) { defer func() { client.SetState(metrics.ConnStateActive, server.GetID()) server.SetState(metrics.ConnStateActive, client.GetID()) @@ -23,7 +23,7 @@ func pair(options Options, client *pooledClient, server *pooledServer) (clientEr switch options.ParameterStatusSync { case ParameterStatusSyncDynamic: - clientErr, serverErr = ps.Sync(options.TrackedParameters, client.GetReadWriter(), client.GetPS(), server.GetReadWriter(), server.GetPS()) + clientErr, serverErr = ps.Sync(options.TrackedParameters, client.GetConn(), client.GetPS(), server.GetConn(), server.GetPS()) case ParameterStatusSyncInitial: clientErr, serverErr = syncInitialParameters(options, client, server) } @@ -33,13 +33,13 @@ func pair(options Options, client *pooledClient, server *pooledServer) (clientEr } if options.ExtendedQuerySync { - serverErr = eqp.Sync(client.GetEQP(), server.GetReadWriter(), server.GetEQP()) + serverErr = eqp.Sync(client.GetEQP(), server.GetConn(), server.GetEQP()) } return } -func syncInitialParameters(options Options, client *pooledClient, server *pooledServer) (clientErr, serverErr error) { +func syncInitialParameters(options Config, client *pooledClient, server *pooledServer) (clientErr, serverErr error) { clientParams := client.GetInitialParameters() serverParams := server.GetInitialParameters() @@ -80,15 +80,10 @@ func syncInitialParameters(options Options, client *pooledClient, server *pooled continue } - ctx := backends.Context{ - Packet: packet, - Server: server.GetReadWriter(), - } - serverErr = backends.SetParameter(&ctx, key, value) + serverErr, _, packet = backends.SetParameter(server.GetConn(), nil, packet, key, value) if serverErr != nil { return } - packet = ctx.Packet } for key, value := range serverParams { diff --git a/lib/gat/pool/pool.go b/lib/gat/pool/pool.go index 3ad6b328f89e9307cf135e82a2d6dc48fad57795..8399e39b80bb5ba6e913de1415d8a4dbafc9b7c1 100644 --- a/lib/gat/pool/pool.go +++ b/lib/gat/pool/pool.go @@ -7,21 +7,19 @@ import ( "time" "github.com/google/uuid" - "tuxpa.in/a/zlog/log" + "go.uber.org/zap" - "gfx.cafe/gfx/pggat/lib/auth" "gfx.cafe/gfx/pggat/lib/bouncer/backends/v0" "gfx.cafe/gfx/pggat/lib/bouncer/bouncers/v2" "gfx.cafe/gfx/pggat/lib/fed" packets "gfx.cafe/gfx/pggat/lib/fed/packets/v3.0" "gfx.cafe/gfx/pggat/lib/gat/metrics" "gfx.cafe/gfx/pggat/lib/util/slices" - "gfx.cafe/gfx/pggat/lib/util/strutil" ) type Pool struct { - options Options - pooler Pooler + config Config + pooler Pooler closed chan struct{} @@ -37,18 +35,18 @@ type Pool struct { mu sync.RWMutex } -func NewPool(options Options) *Pool { - if options.NewPooler == nil { +func NewPool(config Config) *Pool { + if config.NewPooler == nil { panic("expected new pooler func") } - pooler := options.NewPooler() + pooler := config.NewPooler() if pooler == nil { panic("expected pooler") } p := &Pool{ - options: options, - pooler: pooler, + config: config, + pooler: pooler, closed: make(chan struct{}), pending: make(chan struct{}, 1), @@ -79,10 +77,6 @@ func (T *Pool) idlest() (server *pooledServer, at time.Time) { return } -func (T *Pool) GetCredentials() auth.Credentials { - return T.options.Credentials -} - func (T *Pool) AddRecipe(name string, r *Recipe) { func() { T.mu.Lock() @@ -104,7 +98,7 @@ func (T *Pool) AddRecipe(name string, r *Recipe) { count := r.AllocateInitial() for i := 0; i < count; i++ { if err := T.scaleUpL1(name, r); err != nil { - log.Printf("failed to dial server: %v", err) + T.config.Logger.Warn("failed to dial server", zap.Error(err)) for j := i; j < count; j++ { r.Free() } @@ -153,7 +147,7 @@ func (T *Pool) scaleUpL0() (string, *Recipe) { } func (T *Pool) scaleUpL1(name string, r *Recipe) error { - conn, params, err := r.Dial() + conn, err := r.Dial() if err != nil { // failed to dial r.Free() @@ -170,11 +164,9 @@ func (T *Pool) scaleUpL1(name string, r *Recipe) error { } server := newServer( - T.options, + T.config, name, conn, - params.InitialParameters, - params.BackendKey, ) if T.servers == nil { @@ -209,7 +201,7 @@ func (T *Pool) scaleUp() bool { err := T.scaleUpL1(name, r) if err != nil { - log.Printf("failed to dial server: %v", err) + T.config.Logger.Warn("failed to dial server", zap.Error(err)) return false } @@ -231,9 +223,12 @@ func (T *Pool) removeServerL1(server *pooledServer) { name := server.GetRecipe() T.serversByRecipe[name] = slices.Delete(T.serversByRecipe[name], server) // update order - T.recipeScaleOrder.Update(slices.Index(T.recipeScaleOrder, name), func(n string) int { - return len(T.serversByRecipe[n]) - }) + index := slices.Index(T.recipeScaleOrder, name) + if index != -1 { + T.recipeScaleOrder.Update(index, func(n string) int { + return len(T.serversByRecipe[n]) + }) + } } } @@ -250,6 +245,9 @@ func (T *Pool) acquireServer(client *pooledClient) *pooledServer { } serverID = T.pooler.Acquire(client.GetID(), SyncModeBlocking) T.pendingCount.Add(-1) + if serverID == uuid.Nil { + return nil + } } T.mu.RLock() @@ -264,13 +262,10 @@ func (T *Pool) acquireServer(client *pooledClient) *pooledServer { } func (T *Pool) releaseServer(server *pooledServer) { - if T.options.ServerResetQuery != "" { + if T.config.ServerResetQuery != "" { server.SetState(metrics.ConnStateRunningResetQuery, uuid.Nil) - ctx := backends.Context{ - Server: server.GetReadWriter(), - } - err := backends.QueryString(&ctx, T.options.ServerResetQuery) + err, _, _ := backends.QueryString(server.GetConn(), nil, nil, T.config.ServerResetQuery) if err != nil { T.removeServer(server) return @@ -283,19 +278,15 @@ func (T *Pool) releaseServer(server *pooledServer) { } func (T *Pool) Serve( - conn fed.Conn, - initialParameters map[strutil.CIString]string, - backendKey [8]byte, + conn *fed.Conn, ) error { defer func() { _ = conn.Close() }() client := newClient( - T.options, + T.config, conn, - initialParameters, - backendKey, ) return T.serve(client, false) @@ -304,17 +295,17 @@ func (T *Pool) Serve( // ServeBot is for clients that don't need initial parameters, cancelling queries, and are ready now. Use Serve for // real clients func (T *Pool) ServeBot( - conn fed.Conn, + conn fed.ReadWriteCloser, ) error { defer func() { _ = conn.Close() }() client := newClient( - T.options, - conn, - nil, - [8]byte{}, + T.config, + &fed.Conn{ + ReadWriteCloser: conn, + }, ) return T.serve(client, true) @@ -343,8 +334,11 @@ func (T *Pool) serve(client *pooledClient, initialized bool) error { if !initialized { server = T.acquireServer(client) + if server == nil { + return ErrClosed + } - err, serverErr = pair(T.options, client, server) + err, serverErr = pair(T.config, client, server) if serverErr != nil { return serverErr } @@ -361,7 +355,7 @@ func (T *Pool) serve(client *pooledClient, initialized bool) error { } for { - if server != nil && T.options.ReleaseAfterTransaction { + if server != nil && T.config.ReleaseAfterTransaction { client.SetState(metrics.ConnStateIdle, uuid.Nil) T.releaseServer(server) server = nil @@ -374,11 +368,14 @@ func (T *Pool) serve(client *pooledClient, initialized bool) error { if server == nil { server = T.acquireServer(client) + if server == nil { + return ErrClosed + } - err, serverErr = pair(T.options, client, server) + err, serverErr = pair(T.config, client, server) } if err == nil && serverErr == nil { - packet, err, serverErr = bouncers.Bounce(client.GetReadWriter(), server.GetReadWriter(), packet) + packet, err, serverErr = bouncers.Bounce(client.GetConn(), server.GetConn(), packet) } if serverErr != nil { return serverErr @@ -421,23 +418,23 @@ func (T *Pool) removeClientL1(client *pooledClient) { delete(T.clientsByKey, client.GetBackendKey()) } -func (T *Pool) Cancel(key [8]byte) error { +func (T *Pool) Cancel(key [8]byte) { T.mu.RLock() defer T.mu.RUnlock() client, ok := T.clientsByKey[key] if !ok { - return nil + return } state, peer, _ := client.GetState() if state != metrics.ConnStateActive { - return nil + return } server, ok := T.servers[peer] if !ok { - return nil + return } // prevent state from changing by RLocking the server @@ -446,15 +443,15 @@ func (T *Pool) Cancel(key [8]byte) error { // make sure peer is still set if server.peer != peer { - return nil + return } r, ok := T.recipes[server.recipe] if !ok { - return nil + return } - return r.Cancel(server.backendKey) + r.Cancel(server.GetBackendKey()) } func (T *Pool) ReadMetrics(m *metrics.Pool) { @@ -483,6 +480,7 @@ func (T *Pool) ReadMetrics(m *metrics.Pool) { func (T *Pool) Close() { close(T.closed) + T.pooler.Close() T.mu.Lock() defer T.mu.Unlock() diff --git a/lib/gat/pool/pooler.go b/lib/gat/pool/pooler.go index 2133ce11ba5f39a43d55c00d0910067c574bed9d..06bba5a46ba99f4e34c4c69e8d196f02617799f8 100644 --- a/lib/gat/pool/pooler.go +++ b/lib/gat/pool/pooler.go @@ -18,4 +18,6 @@ type Pooler interface { Acquire(client uuid.UUID, sync SyncMode) (server uuid.UUID) Release(server uuid.UUID) + + Close() } diff --git a/lib/gat/pool/pools/session/apply.go b/lib/gat/pool/pools/session/apply.go deleted file mode 100644 index 2c9f8157626ee3d098ddf1635878a2eed1ffc7ee..0000000000000000000000000000000000000000 --- a/lib/gat/pool/pools/session/apply.go +++ /dev/null @@ -1,12 +0,0 @@ -package session - -import ( - "gfx.cafe/gfx/pggat/lib/gat/pool" -) - -func Apply(options pool.Options) pool.Options { - options.NewPooler = NewPooler - options.ParameterStatusSync = pool.ParameterStatusSyncInitial - options.ExtendedQuerySync = false - return options -} diff --git a/lib/gat/pool/pools/transaction/apply.go b/lib/gat/pool/pools/transaction/apply.go deleted file mode 100644 index 088a11b35ec19d05b9bcc2336c3417ceef9ef419..0000000000000000000000000000000000000000 --- a/lib/gat/pool/pools/transaction/apply.go +++ /dev/null @@ -1,11 +0,0 @@ -package transaction - -import "gfx.cafe/gfx/pggat/lib/gat/pool" - -func Apply(options pool.Options) pool.Options { - options.NewPooler = NewPooler - options.ParameterStatusSync = pool.ParameterStatusSyncDynamic - options.ExtendedQuerySync = true - options.ReleaseAfterTransaction = true - return options -} diff --git a/lib/gat/pool/recipe/options.go b/lib/gat/pool/recipe/config.go similarity index 88% rename from lib/gat/pool/recipe/options.go rename to lib/gat/pool/recipe/config.go index 6edc926c8f5e65fc58e3f36061bc81a97c05fb81..37ae8d6602e4cde1eed617841d36d3909cf1caf3 100644 --- a/lib/gat/pool/recipe/options.go +++ b/lib/gat/pool/recipe/config.go @@ -1,6 +1,6 @@ package recipe -type Options struct { +type Config struct { Dialer Dialer MinConnections int diff --git a/lib/gat/pool/recipe/dialer.go b/lib/gat/pool/recipe/dialer.go index 622cc32898ce8c514b838c55e95c0485779dc786..8202c2b9a6b1633fb835e7c092d9e8a84757ee69 100644 --- a/lib/gat/pool/recipe/dialer.go +++ b/lib/gat/pool/recipe/dialer.go @@ -1,57 +1,68 @@ package recipe import ( - "errors" - "io" + "crypto/tls" "net" + "gfx.cafe/gfx/pggat/lib/auth" + "gfx.cafe/gfx/pggat/lib/bouncer" "gfx.cafe/gfx/pggat/lib/bouncer/backends/v0" "gfx.cafe/gfx/pggat/lib/fed" + "gfx.cafe/gfx/pggat/lib/util/strutil" ) -type BackendAcceptOptions = backends.AcceptOptions - type Dialer struct { Network string Address string - AcceptOptions BackendAcceptOptions + SSLMode bouncer.SSLMode + SSLConfig *tls.Config + Username string + Credentials auth.Credentials + Database string + StartupParameters map[strutil.CIString]string } -func (T Dialer) Dial() (fed.Conn, backends.AcceptParams, error) { +func (T Dialer) Dial() (*fed.Conn, error) { c, err := net.Dial(T.Network, T.Address) if err != nil { - return nil, backends.AcceptParams{}, err - } - conn := fed.WrapNetConn(c) - ctx := backends.AcceptContext{ - Conn: conn, - Options: T.AcceptOptions, + return nil, err } - params, err := backends.Accept(&ctx) + conn := fed.NewConn( + fed.NewNetConn(c), + ) + conn.User = T.Username + conn.Database = T.Database + err = backends.Accept( + conn, + T.SSLMode, + T.SSLConfig, + T.Username, + T.Credentials, + T.Database, + T.StartupParameters, + ) if err != nil { - return nil, backends.AcceptParams{}, err + return nil, err } - return conn, params, nil + return conn, nil } -func (T Dialer) Cancel(key [8]byte) error { +func (T Dialer) Cancel(key [8]byte) { c, err := net.Dial(T.Network, T.Address) if err != nil { - return err + return } - conn := fed.WrapNetConn(c) + conn := fed.NewConn( + fed.NewNetConn(c), + ) defer func() { _ = conn.Close() }() if err = backends.Cancel(conn, key); err != nil { - return err + return } // wait for server to close the connection, this means that the server received it ok - _, err = conn.ReadPacket(true, nil) - if err != nil && !errors.Is(err, io.EOF) { - return err - } - return nil + _, _ = conn.ReadPacket(true, nil) } diff --git a/lib/gat/pool/recipe/recipe.go b/lib/gat/pool/recipe/recipe.go index b535ab40085da5e982857cb78e2c3d13d299c46a..aeb25e76eb3a4a81b6c4061df7a615274ffe8b7e 100644 --- a/lib/gat/pool/recipe/recipe.go +++ b/lib/gat/pool/recipe/recipe.go @@ -3,20 +3,19 @@ package recipe import ( "sync" - "gfx.cafe/gfx/pggat/lib/bouncer/backends/v0" "gfx.cafe/gfx/pggat/lib/fed" ) type Recipe struct { - options Options + config Config count int mu sync.Mutex } -func NewRecipe(options Options) *Recipe { +func NewRecipe(config Config) *Recipe { return &Recipe{ - options: options, + config: config, } } @@ -24,12 +23,12 @@ func (T *Recipe) AllocateInitial() int { T.mu.Lock() defer T.mu.Unlock() - if T.count >= T.options.MinConnections { + if T.count >= T.config.MinConnections { return 0 } - amount := T.options.MinConnections - T.count - T.count = T.options.MinConnections + amount := T.config.MinConnections - T.count + T.count = T.config.MinConnections return amount } @@ -38,8 +37,8 @@ func (T *Recipe) Allocate() bool { T.mu.Lock() defer T.mu.Unlock() - if T.options.MaxConnections != 0 { - if T.count >= T.options.MaxConnections { + if T.config.MaxConnections != 0 { + if T.count >= T.config.MaxConnections { return false } } @@ -52,7 +51,7 @@ func (T *Recipe) TryFree() bool { T.mu.Lock() defer T.mu.Unlock() - if T.count <= T.options.MinConnections { + if T.count <= T.config.MinConnections { return false } @@ -67,10 +66,10 @@ func (T *Recipe) Free() { T.count-- } -func (T *Recipe) Dial() (fed.Conn, backends.AcceptParams, error) { - return T.options.Dialer.Dial() +func (T *Recipe) Dial() (*fed.Conn, error) { + return T.config.Dialer.Dial() } -func (T *Recipe) Cancel(key [8]byte) error { - return T.options.Dialer.Cancel(key) +func (T *Recipe) Cancel(key [8]byte) { + T.config.Dialer.Cancel(key) } diff --git a/lib/gat/pool/scaler.go b/lib/gat/pool/scaler.go index 3e3c57012e0233ee4b1502d8778d269d86d9274d..8beb64d3097d341369f26858cdb507dfb0bfb630 100644 --- a/lib/gat/pool/scaler.go +++ b/lib/gat/pool/scaler.go @@ -3,7 +3,7 @@ package pool import ( "time" - "tuxpa.in/a/zlog/log" + "go.uber.org/zap" ) type scaler struct { @@ -20,11 +20,11 @@ type scaler struct { func newScaler(pool *Pool) *scaler { s := &scaler{ pool: pool, - backoff: pool.options.ServerReconnectInitialTime, + backoff: pool.config.ServerReconnectInitialTime.Duration(), } - if pool.options.ServerIdleTimeout != 0 { - s.idle = time.NewTimer(pool.options.ServerIdleTimeout) + if pool.config.ServerIdleTimeout != 0 { + s.idle = time.NewTimer(pool.config.ServerIdleTimeout.Duration()) } return s @@ -36,14 +36,14 @@ func (T *scaler) idleTimeout(now time.Time) { var idlest *pooledServer var idleStart time.Time - for idlest, idleStart = T.pool.idlest(); idlest != nil && now.Sub(idleStart) > T.pool.options.ServerIdleTimeout; idlest, idleStart = T.pool.idlest() { + for idlest, idleStart = T.pool.idlest(); idlest != nil && now.Sub(idleStart) > T.pool.config.ServerIdleTimeout.Duration(); idlest, idleStart = T.pool.idlest() { T.pool.removeServer(idlest) } if idlest == nil { - wait = T.pool.options.ServerIdleTimeout + wait = T.pool.config.ServerIdleTimeout.Duration() } else { - wait = idleStart.Add(T.pool.options.ServerIdleTimeout).Sub(now) + wait = idleStart.Add(T.pool.config.ServerIdleTimeout.Duration()).Sub(now) } T.idle.Reset(wait) @@ -52,8 +52,8 @@ func (T *scaler) idleTimeout(now time.Time) { func (T *scaler) pendingTimeout() { if T.backingOff { T.backoff *= 2 - if T.pool.options.ServerReconnectMaxTime != 0 && T.backoff > T.pool.options.ServerReconnectMaxTime { - T.backoff = T.pool.options.ServerReconnectMaxTime + if T.pool.config.ServerReconnectMaxTime != 0 && T.backoff > T.pool.config.ServerReconnectMaxTime.Duration() { + T.backoff = T.pool.config.ServerReconnectMaxTime.Duration() } } @@ -61,14 +61,14 @@ func (T *scaler) pendingTimeout() { // pending loop for scaling up if T.pool.scaleUp() { // scale up successful, see if we need to scale up more - T.backoff = T.pool.options.ServerReconnectInitialTime + T.backoff = T.pool.config.ServerReconnectInitialTime.Duration() T.backingOff = false continue } if T.backoff == 0 { // no backoff - T.backoff = T.pool.options.ServerReconnectInitialTime + T.backoff = T.pool.config.ServerReconnectInitialTime.Duration() T.backingOff = false continue } @@ -80,7 +80,7 @@ func (T *scaler) pendingTimeout() { T.pending.Reset(T.backoff) } - log.Printf("failed to dial server. trying again in %v", T.backoff) + T.pool.config.Logger.Warn("failed to dial server", zap.Duration("backoff", T.backoff)) return } diff --git a/lib/gat/pool/server.go b/lib/gat/pool/server.go index 5c250ee7ea5c014600c96a66dfacc7247be32321..9afa7c994359bd6157c0df746dc368efd1c6500c 100644 --- a/lib/gat/pool/server.go +++ b/lib/gat/pool/server.go @@ -2,11 +2,8 @@ package pool import ( "gfx.cafe/gfx/pggat/lib/fed" - "gfx.cafe/gfx/pggat/lib/middleware" - "gfx.cafe/gfx/pggat/lib/middleware/interceptor" - "gfx.cafe/gfx/pggat/lib/middleware/middlewares/eqp" - "gfx.cafe/gfx/pggat/lib/middleware/middlewares/ps" - "gfx.cafe/gfx/pggat/lib/util/strutil" + "gfx.cafe/gfx/pggat/lib/fed/middlewares/eqp" + "gfx.cafe/gfx/pggat/lib/fed/middlewares/ps" ) type pooledServer struct { @@ -19,40 +16,27 @@ type pooledServer struct { } func newServer( - options Options, + options Config, recipe string, - conn fed.Conn, - initialParameters map[strutil.CIString]string, - backendKey [8]byte, + conn *fed.Conn, ) *pooledServer { - var middlewares []middleware.Middleware - var psServer *ps.Server if options.ParameterStatusSync == ParameterStatusSyncDynamic { // add ps middleware - psServer = ps.NewServer(initialParameters) - middlewares = append(middlewares, psServer) + psServer = ps.NewServer(conn.InitialParameters) + conn.Middleware = append(conn.Middleware, psServer) } var eqpServer *eqp.Server if options.ExtendedQuerySync { // add eqp middleware eqpServer = eqp.NewServer() - middlewares = append(middlewares, eqpServer) - } - - if len(middlewares) > 0 { - conn = interceptor.NewInterceptor( - conn, - middlewares..., - ) + conn.Middleware = append(conn.Middleware, eqpServer) } return &pooledServer{ pooledConn: makeConn( conn, - initialParameters, - backendKey, ), recipe: recipe, ps: psServer, diff --git a/lib/gat/pool/withcredentials.go b/lib/gat/pool/withcredentials.go new file mode 100644 index 0000000000000000000000000000000000000000..9c7382b380788ed36b6df39d241f3b5498e50493 --- /dev/null +++ b/lib/gat/pool/withcredentials.go @@ -0,0 +1,8 @@ +package pool + +import "gfx.cafe/gfx/pggat/lib/auth" + +type WithCredentials struct { + *Pool + Credentials auth.Credentials +} diff --git a/lib/gat/pooler.go b/lib/gat/pooler.go new file mode 100644 index 0000000000000000000000000000000000000000..39d130621821146ecaaff5b708f35c4285ea913b --- /dev/null +++ b/lib/gat/pooler.go @@ -0,0 +1,5 @@ +package gat + +type Pooler interface { + NewPool() *Pool +} diff --git a/lib/gat/poolers/session/module.go b/lib/gat/poolers/session/module.go new file mode 100644 index 0000000000000000000000000000000000000000..04d65b7121f162820295fb6f2a86a0ac30247946 --- /dev/null +++ b/lib/gat/poolers/session/module.go @@ -0,0 +1,54 @@ +package session + +import ( + "github.com/caddyserver/caddy/v2" + "go.uber.org/zap" + + "gfx.cafe/gfx/pggat/lib/gat" + "gfx.cafe/gfx/pggat/lib/gat/pool" +) + +var PoolingOptions = pool.PoolingConfig{ + NewPooler: NewPooler, + ReleaseAfterTransaction: false, + ParameterStatusSync: pool.ParameterStatusSyncInitial, + ExtendedQuerySync: false, +} + +func init() { + caddy.RegisterModule((*Module)(nil)) +} + +type Module struct { + pool.ManagementConfig + + log *zap.Logger +} + +func (T *Module) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.poolers.session", + New: func() caddy.Module { + return new(Module) + }, + } +} + +func (T *Module) Provision(ctx caddy.Context) error { + T.log = ctx.Logger() + return nil +} + +func (T *Module) NewPool() *gat.Pool { + return pool.NewPool(pool.Config{ + PoolingConfig: PoolingOptions, + + ManagementConfig: T.ManagementConfig, + + Logger: T.log, + }) +} + +var _ gat.Pooler = (*Module)(nil) +var _ caddy.Module = (*Module)(nil) +var _ caddy.Provisioner = (*Module)(nil) diff --git a/lib/gat/pool/pools/session/pooler.go b/lib/gat/poolers/session/pooler.go similarity index 87% rename from lib/gat/pool/pools/session/pooler.go rename to lib/gat/poolers/session/pooler.go index 9827727ef102575e655e5580d1bf7bb8a79dac35..8f0dd004ce3ab9caf066e670325726eaf8508f23 100644 --- a/lib/gat/pool/pools/session/pooler.go +++ b/lib/gat/poolers/session/pooler.go @@ -13,6 +13,7 @@ type Pooler struct { queue []uuid.UUID servers map[uuid.UUID]struct{} ready *sync.Cond + closed bool mu sync.Mutex } @@ -56,6 +57,10 @@ func (T *Pooler) TryAcquire() uuid.UUID { T.mu.Lock() defer T.mu.Unlock() + if T.closed { + return uuid.Nil + } + if len(T.queue) == 0 { return uuid.Nil } @@ -69,6 +74,10 @@ func (T *Pooler) AcquireBlocking() uuid.UUID { T.mu.Lock() defer T.mu.Unlock() + if T.closed { + return uuid.Nil + } + for len(T.queue) == 0 { if T.ready == nil { T.ready = sync.NewCond(&T.mu) @@ -76,6 +85,10 @@ func (T *Pooler) AcquireBlocking() uuid.UUID { T.ready.Wait() } + if T.closed { + return uuid.Nil + } + server := T.queue[len(T.queue)-1] T.queue = T.queue[:len(T.queue)-1] return server @@ -104,4 +117,14 @@ func (T *Pooler) Release(server uuid.UUID) { T.queue = append(T.queue, server) } +func (T *Pooler) Close() { + T.mu.Lock() + defer T.mu.Unlock() + + T.closed = true + if T.ready != nil { + T.ready.Broadcast() + } +} + var _ pool.Pooler = (*Pooler)(nil) diff --git a/lib/gat/poolers/transaction/module.go b/lib/gat/poolers/transaction/module.go new file mode 100644 index 0000000000000000000000000000000000000000..95a06b3686f607e8dfe95101d4e4c4e9854cb04f --- /dev/null +++ b/lib/gat/poolers/transaction/module.go @@ -0,0 +1,53 @@ +package transaction + +import ( + "github.com/caddyserver/caddy/v2" + "go.uber.org/zap" + + "gfx.cafe/gfx/pggat/lib/gat" + "gfx.cafe/gfx/pggat/lib/gat/pool" +) + +var PoolingOptions = pool.PoolingConfig{ + NewPooler: NewPooler, + ReleaseAfterTransaction: true, + ParameterStatusSync: pool.ParameterStatusSyncDynamic, + ExtendedQuerySync: true, +} + +func init() { + caddy.RegisterModule((*Module)(nil)) +} + +type Module struct { + pool.ManagementConfig + + log *zap.Logger +} + +func (T *Module) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.poolers.transaction", + New: func() caddy.Module { + return new(Module) + }, + } +} + +func (T *Module) Provision(ctx caddy.Context) error { + T.log = ctx.Logger() + return nil +} + +func (T *Module) NewPool() *gat.Pool { + return pool.NewPool(pool.Config{ + PoolingConfig: PoolingOptions, + + ManagementConfig: T.ManagementConfig, + + Logger: T.log, + }) +} + +var _ gat.Pooler = (*Module)(nil) +var _ caddy.Module = (*Module)(nil) diff --git a/lib/gat/pool/pools/transaction/pooler.go b/lib/gat/poolers/transaction/pooler.go similarity index 95% rename from lib/gat/pool/pools/transaction/pooler.go rename to lib/gat/poolers/transaction/pooler.go index 939718a29a4e27775c132783a011859709f69879..3eb1b9e1ca36403a498467ad86a42a86ad1b1b1b 100644 --- a/lib/gat/pool/pools/transaction/pooler.go +++ b/lib/gat/poolers/transaction/pooler.go @@ -47,4 +47,8 @@ func (T *Pooler) Release(server uuid.UUID) { T.s.Release(server) } +func (T *Pooler) Close() { + T.s.Close() +} + var _ pool.Pooler = (*Pooler)(nil) diff --git a/lib/gat/provider.go b/lib/gat/provider.go deleted file mode 100644 index 19d3662b7f26bff5a970435a5ea4846584f4fe15..0000000000000000000000000000000000000000 --- a/lib/gat/provider.go +++ /dev/null @@ -1,11 +0,0 @@ -package gat - -import "gfx.cafe/gfx/pggat/lib/gat/metrics" - -// Provider provides pool to the server -type Provider interface { - Module - - Lookup(user, database string) *Pool - ReadMetrics(metrics *metrics.Pools) -} diff --git a/lib/gat/route.go b/lib/gat/route.go new file mode 100644 index 0000000000000000000000000000000000000000..dc8e97fb7497300b57673f1f38a030435b126121 --- /dev/null +++ b/lib/gat/route.go @@ -0,0 +1,40 @@ +package gat + +import ( + "encoding/json" + "fmt" + + "github.com/caddyserver/caddy/v2" +) + +type RouteConfig struct { + Match json.RawMessage `json:"match,omitempty" caddy:"namespace=pggat.matchers inline_key=matcher"` + Handle json.RawMessage `json:"handle,omitempty" caddy:"namespace=pggat.handlers inline_key=handler"` +} + +type Route struct { + RouteConfig + + match Matcher + handle Handler +} + +func (T *Route) Provision(ctx caddy.Context) error { + if T.Match != nil { + val, err := ctx.LoadModule(T, "Match") + if err != nil { + return fmt.Errorf("loading matcher module: %v", err) + } + T.match = val.(Matcher) + } + if T.Handle != nil { + val, err := ctx.LoadModule(T, "Handle") + if err != nil { + return fmt.Errorf("loading handle module: %v", err) + } + T.handle = val.(Handler) + } + return nil +} + +var _ caddy.Provisioner = (*Route)(nil) diff --git a/lib/gat/server.go b/lib/gat/server.go index 8c4d587e03af8969691f4bb673a3a760f82cfb54..d31a0f6d593725e6214bbc6a9c98772a21d16a9d 100644 --- a/lib/gat/server.go +++ b/lib/gat/server.go @@ -1,174 +1,193 @@ package gat import ( + "crypto/tls" "errors" + "fmt" "io" "net" - "tuxpa.in/a/zlog/log" + "github.com/caddyserver/caddy/v2" + "go.uber.org/zap" "gfx.cafe/gfx/pggat/lib/bouncer/frontends/v0" "gfx.cafe/gfx/pggat/lib/fed" + packets "gfx.cafe/gfx/pggat/lib/fed/packets/v3.0" "gfx.cafe/gfx/pggat/lib/gat/metrics" - "gfx.cafe/gfx/pggat/lib/util/beforeexit" - "gfx.cafe/gfx/pggat/lib/util/flip" - "gfx.cafe/gfx/pggat/lib/util/maps" + "gfx.cafe/gfx/pggat/lib/perror" ) +type ServerConfig struct { + Listen []ListenerConfig `json:"listen,omitempty"` + Routes []RouteConfig `json:"routes,omitempty"` +} + type Server struct { - modules []Module - providers []Provider - listeners []Listener + ServerConfig - keys maps.RWLocked[[8]byte, *Pool] + listen []*Listener + routes []*Route + cancellableHandlers []CancellableHandler + metricsHandlers []MetricsHandler + + log *zap.Logger } -func (T *Server) AddModule(module Module) { - T.modules = append(T.modules, module) - if provider, ok := module.(Provider); ok { - T.providers = append(T.providers, provider) +func (T *Server) Provision(ctx caddy.Context) error { + T.log = ctx.Logger() + + T.listen = make([]*Listener, 0, len(T.Listen)) + for _, config := range T.Listen { + listener := &Listener{ + ListenerConfig: config, + } + if err := listener.Provision(ctx); err != nil { + return err + } + T.listen = append(T.listen, listener) } - if listener, ok := module.(Listener); ok { - T.listeners = append(T.listeners, listener) + + T.routes = make([]*Route, 0, len(T.Routes)) + for _, config := range T.Routes { + route := &Route{ + RouteConfig: config, + } + if err := route.Provision(ctx); err != nil { + return err + } + if cancellableHandler, ok := route.handle.(CancellableHandler); ok { + T.cancellableHandlers = append(T.cancellableHandlers, cancellableHandler) + } + if metricsHandler, ok := route.handle.(MetricsHandler); ok { + T.metricsHandlers = append(T.metricsHandlers, metricsHandler) + } + T.routes = append(T.routes, route) } + + return nil } -func (T *Server) cancel(key [8]byte) error { - p, ok := T.keys.Load(key) - if !ok { - return nil +func (T *Server) Start() error { + for _, listener := range T.listen { + if err := listener.Start(); err != nil { + return err + } + + go func(listener *Listener) { + for { + if !T.acceptFrom(listener) { + break + } + } + }(listener) } - return p.Cancel(key) + return nil } -func (T *Server) lookupPool(user, database string) *Pool { - for _, provider := range T.providers { - p := provider.Lookup(user, database) - if p != nil { - return p +func (T *Server) Stop() error { + for _, listen := range T.listen { + if err := listen.Stop(); err != nil { + return err } } return nil } -func (T *Server) registerKey(key [8]byte, p *Pool) { - T.keys.Store(key, p) +func (T *Server) Cancel(key [8]byte) { + for _, cancellableHandler := range T.cancellableHandlers { + cancellableHandler.Cancel(key) + } } -func (T *Server) unregisterKey(key [8]byte) { - T.keys.Delete(key) +func (T *Server) ReadMetrics(m *metrics.Server) { + for _, metricsHandler := range T.metricsHandlers { + metricsHandler.ReadMetrics(&m.Handler) + } } -func (T *Server) serve(conn fed.Conn, params frontends.AcceptParams) error { - if params.CancelKey != [8]byte{} { - return T.cancel(params.CancelKey) - } +func (T *Server) Serve(conn *fed.Conn) { + for _, route := range T.routes { + if route.match != nil && !route.match.Matches(conn) { + continue + } - p := T.lookupPool(params.User, params.Database) - if p == nil { - return errPoolNotFound{ - User: params.User, - Database: params.Database, + if route.handle == nil { + continue } - } + err := route.handle.Handle(conn) + if err != nil { + if errors.Is(err, io.EOF) { + // normal closure + return + } - ctx := frontends.AuthenticateContext{ - Conn: conn, - Options: frontends.AuthenticateOptions{ - Credentials: p.GetCredentials(), - }, - } - auth, err := frontends.Authenticate(&ctx) - if err != nil { - return err + errResp := packets.ErrorResponse{ + Error: perror.Wrap(err), + } + _ = conn.WritePacket(errResp.IntoPacket(nil)) + return + } } - T.registerKey(auth.BackendKey, p) - defer T.unregisterKey(auth.BackendKey) - - return p.Serve(conn, params.InitialParameters, auth.BackendKey) + // database not found + errResp := packets.ErrorResponse{ + Error: perror.New( + perror.FATAL, + perror.InvalidPassword, + fmt.Sprintf(`Database "%s" not found`, conn.Database), + ), + } + _ = conn.WritePacket(errResp.IntoPacket(nil)) + T.log.Warn("database not found", zap.String("user", conn.User), zap.String("database", conn.Database)) } -func (T *Server) accept(raw net.Conn, acceptOptions FrontendAcceptOptions) { - conn := fed.WrapNetConn(raw) - +func (T *Server) accept(listener *Listener, conn *fed.Conn) { defer func() { _ = conn.Close() }() - ctx := frontends.AcceptContext{ - Conn: conn, - Options: acceptOptions, - } - params, err2 := frontends.Accept(&ctx) - if err2 != nil { - log.Print("error accepting client: ", err2) - return + var tlsConfig *tls.Config + if listener.ssl != nil { + tlsConfig = listener.ssl.ServerTLSConfig() } - err := T.serve(conn, params) - if err != nil && !errors.Is(err, io.EOF) { - log.Print("error serving client: ", err) + var cancelKey [8]byte + var isCanceling bool + var err error + cancelKey, isCanceling, err = frontends.Accept(conn, tlsConfig) + if err != nil { + T.log.Warn("error accepting client", zap.Error(err)) return } -} -func (T *Server) Listen(network, address string) (net.Listener, error) { - listener, err := net.Listen(network, address) - if err != nil { - return nil, err - } - if network == "unix" { - beforeexit.Run(func() { - _ = listener.Close() - }) + if isCanceling { + T.Cancel(cancelKey) + return } - log.Printf("listening on %s(%s)", network, address) - - return listener, nil + T.Serve(conn) } -func (T *Server) Serve(listener net.Listener, acceptOptions FrontendAcceptOptions) error { - for { - raw, err := listener.Accept() - if err != nil { - if errors.Is(err, net.ErrClosed) { - break - } +func (T *Server) acceptFrom(listener *Listener) bool { + conn, err := listener.accept() + if err != nil { + if errors.Is(err, net.ErrClosed) { + return false } - - go T.accept(raw, acceptOptions) - } - - return nil -} - -func (T *Server) ListenAndServe() error { - var b flip.Bank - - if len(T.listeners) > 0 { - l := T.listeners[0] - endpoints := l.Endpoints() - for _, endpoint := range endpoints { - e := endpoint - b.Queue(func() error { - listener, err := T.Listen(e.Network, e.Address) - if err != nil { - return err - } - return T.Serve(listener, e.AcceptOptions) - }) + if netErr, ok := err.(*net.OpError); ok { + // why can't they just expose this error + if netErr.Err.Error() == "listener 'closed' 😉" { + return false + } } + T.log.Warn("error accepting client", zap.Error(err)) + return true } - return b.Wait() + go T.accept(listener, conn) + return true } -func (T *Server) ReadMetrics(m *metrics.Server) { - for _, provider := range T.providers { - provider.ReadMetrics(&m.Pools) - } -} +var _ caddy.Provisioner = (*Server)(nil) diff --git a/lib/gat/ssl.go b/lib/gat/ssl.go new file mode 100644 index 0000000000000000000000000000000000000000..71e6289add250b1f7eaa5f0529aff0459a433a26 --- /dev/null +++ b/lib/gat/ssl.go @@ -0,0 +1,11 @@ +package gat + +import "crypto/tls" + +type SSLServer interface { + ServerTLSConfig() *tls.Config +} + +type SSLClient interface { + ClientTLSConfig() *tls.Config +} diff --git a/lib/gat/ssl/clients/insecure_skip_verify/client.go b/lib/gat/ssl/clients/insecure_skip_verify/client.go new file mode 100644 index 0000000000000000000000000000000000000000..e1c1b3519f1eded4b5a724133fb142771659d346 --- /dev/null +++ b/lib/gat/ssl/clients/insecure_skip_verify/client.go @@ -0,0 +1,34 @@ +package insecure_skip_verify + +import ( + "crypto/tls" + + "github.com/caddyserver/caddy/v2" + + "gfx.cafe/gfx/pggat/lib/gat" +) + +func init() { + caddy.RegisterModule((*Client)(nil)) +} + +type Client struct { +} + +func (T *Client) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.ssl.clients.insecure_skip_verify", + New: func() caddy.Module { + return new(Client) + }, + } +} + +func (T *Client) ClientTLSConfig() *tls.Config { + return &tls.Config{ + InsecureSkipVerify: true, + } +} + +var _ gat.SSLClient = (*Client)(nil) +var _ caddy.Module = (*Client)(nil) diff --git a/lib/gat/modules/ssl_endpoint/module.go b/lib/gat/ssl/servers/self_signed/server.go similarity index 53% rename from lib/gat/modules/ssl_endpoint/module.go rename to lib/gat/ssl/servers/self_signed/server.go index 4589cd98db87ad8eac08d75f3fc88c4034ac2c20..7dec991c5ef194c66f40a16fe9459e03653c2dc2 100644 --- a/lib/gat/modules/ssl_endpoint/module.go +++ b/lib/gat/ssl/servers/self_signed/server.go @@ -1,4 +1,4 @@ -package ssl_endpoint +package self_signed import ( "crypto/rand" @@ -10,25 +10,33 @@ import ( "net" "time" - "tuxpa.in/a/zlog/log" + "github.com/caddyserver/caddy/v2" "gfx.cafe/gfx/pggat/lib/gat" - "gfx.cafe/gfx/pggat/lib/util/strutil" ) -type Module struct { - config *tls.Config +func init() { + caddy.RegisterModule((*Server)(nil)) } -func NewModule() (*Module, error) { - return &Module{}, nil +type Server struct { + tlsConfig *tls.Config } -func (T *Module) generateKeys() error { +func (T *Server) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pggat.ssl.servers.self_signed", + New: func() caddy.Module { + return new(Server) + }, + } +} + +func (T *Server) signCert() (tls.Certificate, error) { // generate private key priv, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { - return err + return tls.Certificate{}, err } keyUsage := x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment @@ -39,7 +47,7 @@ func (T *Module) generateKeys() error { serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { - return err + return tls.Certificate{}, err } template := x509.Certificate{ @@ -54,20 +62,27 @@ func (T *Module) generateKeys() error { ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, } - // TODO(garet) - template.IPAddresses = append(template.IPAddresses, net.ParseIP("192.168.1.1")) + template.IPAddresses = append(template.IPAddresses, net.ParseIP("127.0.0.1")) derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) if err != nil { - return err + return tls.Certificate{}, err } var cert tls.Certificate cert.PrivateKey = priv cert.Certificate = append(cert.Certificate, derBytes) - T.config = &tls.Config{ + return cert, nil +} + +func (T *Server) Provision(ctx caddy.Context) error { + cert, err := T.signCert() + if err != nil { + return err + } + T.tlsConfig = &tls.Config{ Certificates: []tls.Certificate{ cert, }, @@ -75,35 +90,10 @@ func (T *Module) generateKeys() error { return nil } -func (T *Module) GatModule() {} - -func (T *Module) Endpoints() []gat.Endpoint { - if T.config == nil { - if err := T.generateKeys(); err != nil { - log.Printf("failed to generate ssl certificate: %v", err) - } - } - - return []gat.Endpoint{ - { - Network: "tcp", - Address: ":5432", - AcceptOptions: gat.FrontendAcceptOptions{ - SSLRequired: false, - SSLConfig: T.config, - AllowedStartupOptions: []strutil.CIString{ - strutil.MakeCIString("client_encoding"), - strutil.MakeCIString("datestyle"), - strutil.MakeCIString("timezone"), - strutil.MakeCIString("standard_conforming_strings"), - strutil.MakeCIString("application_name"), - strutil.MakeCIString("extra_float_digits"), - strutil.MakeCIString("options"), - }, - }, - }, - } +func (T *Server) ServerTLSConfig() *tls.Config { + return T.tlsConfig } -var _ gat.Module = (*Module)(nil) -var _ gat.Listener = (*Module)(nil) +var _ gat.SSLServer = (*Server)(nil) +var _ caddy.Module = (*Server)(nil) +var _ caddy.Provisioner = (*Server)(nil) 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 0000000000000000000000000000000000000000..905e5d995ac95bf17a2068b0ee2f6be78b3cb927 --- /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 new file mode 100644 index 0000000000000000000000000000000000000000..507ea27b9b33812887888db9bc49a3080ef3b38e --- /dev/null +++ b/lib/gat/standard/standard.go @@ -0,0 +1,40 @@ +package standard + +import ( + // base server + _ "gfx.cafe/gfx/pggat/lib/gat" + + // matchers + _ "gfx.cafe/gfx/pggat/lib/gat/matchers" + + // 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" + + // middlewares + _ "gfx.cafe/gfx/pggat/lib/gat/handlers/allowed_startup_parameters" + _ "gfx.cafe/gfx/pggat/lib/gat/handlers/error" + _ "gfx.cafe/gfx/pggat/lib/gat/handlers/require_ssl" + _ "gfx.cafe/gfx/pggat/lib/gat/handlers/rewrite_database" + _ "gfx.cafe/gfx/pggat/lib/gat/handlers/rewrite_parameter" + _ "gfx.cafe/gfx/pggat/lib/gat/handlers/rewrite_password" + _ "gfx.cafe/gfx/pggat/lib/gat/handlers/rewrite_user" + + // handlers + _ "gfx.cafe/gfx/pggat/lib/gat/handlers/discovery" + _ "gfx.cafe/gfx/pggat/lib/gat/handlers/pgbouncer" + _ "gfx.cafe/gfx/pggat/lib/gat/handlers/pgbouncer_spilo" + _ "gfx.cafe/gfx/pggat/lib/gat/handlers/pool" + + // discovery + _ "gfx.cafe/gfx/pggat/lib/gat/handlers/discovery/discoverers/digitalocean" + _ "gfx.cafe/gfx/pggat/lib/gat/handlers/discovery/discoverers/google_cloud_sql" + _ "gfx.cafe/gfx/pggat/lib/gat/handlers/discovery/discoverers/zalando_operator" + + // poolers + _ "gfx.cafe/gfx/pggat/lib/gat/poolers/session" + _ "gfx.cafe/gfx/pggat/lib/gat/poolers/transaction" +) diff --git a/lib/gsql/addr.go b/lib/gsql/addr.go new file mode 100644 index 0000000000000000000000000000000000000000..61bcf5d2439817290ec46ef99a1ed926d9003004 --- /dev/null +++ b/lib/gsql/addr.go @@ -0,0 +1,15 @@ +package gsql + +import "net" + +type Addr struct{} + +func (Addr) Network() string { + return "gsql" +} + +func (Addr) String() string { + return "local gsql client" +} + +var _ net.Addr = Addr{} diff --git a/lib/gsql/client.go b/lib/gsql/client.go index 85b5d2d9f81bf72dd2a81b01a7c3dd34a32a9cdc..de0a0630725fab6bf7b678fe490196a813a992f3 100644 --- a/lib/gsql/client.go +++ b/lib/gsql/client.go @@ -143,4 +143,4 @@ func (T *Client) Close() error { return nil } -var _ fed.Conn = (*Client)(nil) +var _ fed.ReadWriteCloser = (*Client)(nil) diff --git a/lib/gsql/query_test.go b/lib/gsql/query_test.go index 71ef6af29144e047ab26c7f349847c6a226d2b21..320bf4d5d048e267b97b92692472d56f84e83985 100644 --- a/lib/gsql/query_test.go +++ b/lib/gsql/query_test.go @@ -1,11 +1,10 @@ package gsql import ( + "log" "net" "testing" - "tuxpa.in/a/zlog/log" - "gfx.cafe/gfx/pggat/lib/auth/credentials" "gfx.cafe/gfx/pggat/lib/bouncer/backends/v0" "gfx.cafe/gfx/pggat/lib/bouncer/bouncers/v2" @@ -24,19 +23,19 @@ func TestQuery(t *testing.T) { t.Error(err) return } - server := fed.WrapNetConn(s) - ctx := backends.AcceptContext{ - Conn: server, - Options: backends.AcceptOptions{ + server := fed.NewConn(fed.NewNetConn(s)) + err = backends.Accept( + server, + "", + nil, + "postgres", + credentials.Cleartext{ Username: "postgres", - Credentials: credentials.Cleartext{ - Username: "postgres", - Password: "password", - }, - Database: "postgres", + Password: "password", }, - } - _, err = backends.Accept(&ctx) + "postgres", + nil, + ) if err != nil { t.Error(err) return @@ -59,7 +58,7 @@ func TestQuery(t *testing.T) { if err != nil { t.Error(err) } - _, clientErr, serverErr := bouncers.Bounce(client, server, initial) + _, clientErr, serverErr := bouncers.Bounce(fed.NewConn(client), server, initial) if clientErr != nil { t.Error(clientErr) } diff --git a/lib/middleware/context.go b/lib/middleware/context.go deleted file mode 100644 index be60aa0ee3fe8eb7f8ceaa6700e90b07f6f8dc9b..0000000000000000000000000000000000000000 --- a/lib/middleware/context.go +++ /dev/null @@ -1,11 +0,0 @@ -package middleware - -import "gfx.cafe/gfx/pggat/lib/fed" - -type Context interface { - // Cancel the current packet - Cancel() - - // Write packet to underlying connection - Write(packet fed.Packet) error -} diff --git a/lib/middleware/interceptor/context.go b/lib/middleware/interceptor/context.go deleted file mode 100644 index ea4c7a767aa8d606fe282fbfd4e463803830fad0..0000000000000000000000000000000000000000 --- a/lib/middleware/interceptor/context.go +++ /dev/null @@ -1,36 +0,0 @@ -package interceptor - -import ( - "gfx.cafe/gfx/pggat/lib/fed" - "gfx.cafe/gfx/pggat/lib/middleware" - "gfx.cafe/gfx/pggat/lib/util/decorator" -) - -type Context struct { - noCopy decorator.NoCopy - - cancelled bool - - // for normal Write / WriteUntyped - rw fed.ReadWriter -} - -func makeContext(rw fed.ReadWriter) Context { - return Context{ - rw: rw, - } -} - -func (T *Context) reset() { - T.cancelled = false -} - -func (T *Context) Cancel() { - T.cancelled = true -} - -func (T *Context) Write(packet fed.Packet) error { - return T.rw.WritePacket(packet) -} - -var _ middleware.Context = (*Context)(nil) diff --git a/lib/middleware/interceptor/interceptor.go b/lib/middleware/interceptor/interceptor.go deleted file mode 100644 index 3c13c928eca1e52bd615892d9c217e6006890c78..0000000000000000000000000000000000000000 --- a/lib/middleware/interceptor/interceptor.go +++ /dev/null @@ -1,69 +0,0 @@ -package interceptor - -import ( - "gfx.cafe/gfx/pggat/lib/fed" - "gfx.cafe/gfx/pggat/lib/middleware" -) - -type Interceptor struct { - middlewares []middleware.Middleware - context Context - rw fed.Conn -} - -func NewInterceptor(rw fed.Conn, middlewares ...middleware.Middleware) *Interceptor { - if v, ok := rw.(*Interceptor); ok { - v.middlewares = append(v.middlewares, middlewares...) - return v - } - return &Interceptor{ - middlewares: middlewares, - context: makeContext(rw), - rw: rw, - } -} - -func (T *Interceptor) ReadPacket(typed bool, packet fed.Packet) (fed.Packet, error) { -outer: - for { - var err error - packet, err = T.rw.ReadPacket(typed, packet) - if err != nil { - return packet, err - } - - for _, mw := range T.middlewares { - T.context.reset() - err = mw.Read(&T.context, packet) - if err != nil { - return packet, err - } - if T.context.cancelled { - continue outer - } - } - - return packet, nil - } -} - -func (T *Interceptor) WritePacket(packet fed.Packet) error { - for _, mw := range T.middlewares { - T.context.reset() - err := mw.Write(&T.context, packet) - if err != nil { - return err - } - if T.context.cancelled { - return nil - } - } - - return T.rw.WritePacket(packet) -} - -func (T *Interceptor) Close() error { - return T.rw.Close() -} - -var _ fed.Conn = (*Interceptor)(nil) diff --git a/lib/middleware/middleware.go b/lib/middleware/middleware.go deleted file mode 100644 index 63f5401dad430cd2aa11782bc4c1914bd9de6a80..0000000000000000000000000000000000000000 --- a/lib/middleware/middleware.go +++ /dev/null @@ -1,8 +0,0 @@ -package middleware - -import "gfx.cafe/gfx/pggat/lib/fed" - -type Middleware interface { - Read(ctx Context, packet fed.Packet) error - Write(ctx Context, packet fed.Packet) error -} diff --git a/lib/middleware/middlewares/eqp/client.go b/lib/middleware/middlewares/eqp/client.go deleted file mode 100644 index 89175b71897dbab224e41f673f8700b52cd797d4..0000000000000000000000000000000000000000 --- a/lib/middleware/middlewares/eqp/client.go +++ /dev/null @@ -1,26 +0,0 @@ -package eqp - -import ( - "gfx.cafe/gfx/pggat/lib/fed" - "gfx.cafe/gfx/pggat/lib/middleware" -) - -type Client struct { - state State -} - -func NewClient() *Client { - return new(Client) -} - -func (T *Client) Read(_ middleware.Context, packet fed.Packet) error { - T.state.C2S(packet) - return nil -} - -func (T *Client) Write(_ middleware.Context, packet fed.Packet) error { - T.state.S2C(packet) - return nil -} - -var _ middleware.Middleware = (*Client)(nil) diff --git a/lib/middleware/middlewares/eqp/server.go b/lib/middleware/middlewares/eqp/server.go deleted file mode 100644 index 04a11d178597a9c1bafdf33cd476282aeb62bb0b..0000000000000000000000000000000000000000 --- a/lib/middleware/middlewares/eqp/server.go +++ /dev/null @@ -1,26 +0,0 @@ -package eqp - -import ( - "gfx.cafe/gfx/pggat/lib/fed" - "gfx.cafe/gfx/pggat/lib/middleware" -) - -type Server struct { - state State -} - -func NewServer() *Server { - return new(Server) -} - -func (T *Server) Read(_ middleware.Context, packet fed.Packet) error { - T.state.S2C(packet) - return nil -} - -func (T *Server) Write(_ middleware.Context, packet fed.Packet) error { - T.state.C2S(packet) - return nil -} - -var _ middleware.Middleware = (*Server)(nil) diff --git a/lib/perror/wrap.go b/lib/perror/wrap.go index 7a652d7b50c569f42fe02571ef8f4215a58c48ad..72e918deafadfe0782d1f315fcfd3dd3fac2327f 100644 --- a/lib/perror/wrap.go +++ b/lib/perror/wrap.go @@ -4,6 +4,9 @@ func Wrap(err error) Error { if err == nil { return nil } + if perr, ok := err.(Error); ok { + return perr + } return New( FATAL, InternalError, diff --git a/lib/rob/scheduler.go b/lib/rob/scheduler.go index 1cef414e6ffcaad16cb1b2a8526bf06ad0c6cf5b..97fcadd721142da07b537c7ac78b358bc0376ab1 100644 --- a/lib/rob/scheduler.go +++ b/lib/rob/scheduler.go @@ -28,4 +28,6 @@ type Scheduler interface { // Release will release a worker. // This should be called after acquire unless the worker is removed with RemoveWorker Release(worker uuid.UUID) + + Close() } diff --git a/lib/rob/schedulers/v2/scheduler.go b/lib/rob/schedulers/v2/scheduler.go index 2a5d72e6d3b3e385c514d6c1d574614738a8e07f..01443c79b9a36156e302cdc3b66e2bedd8d428cf 100644 --- a/lib/rob/schedulers/v2/scheduler.go +++ b/lib/rob/schedulers/v2/scheduler.go @@ -1,13 +1,15 @@ package schedulers import ( + "sync" + + "github.com/google/uuid" + "gfx.cafe/gfx/pggat/lib/rob" "gfx.cafe/gfx/pggat/lib/rob/schedulers/v2/job" "gfx.cafe/gfx/pggat/lib/rob/schedulers/v2/sink" "gfx.cafe/gfx/pggat/lib/util/maps" "gfx.cafe/gfx/pggat/lib/util/pools" - "github.com/google/uuid" - "sync" ) type Scheduler struct { @@ -20,6 +22,7 @@ type Scheduler struct { backlog []job.Stalled bmu sync.Mutex sinks map[uuid.UUID]*sink.Sink + closed bool mu sync.RWMutex } @@ -102,6 +105,10 @@ func (T *Scheduler) TryAcquire(j job.Concurrent) uuid.UUID { T.mu.RLock() defer T.mu.RUnlock() + if T.closed { + return uuid.Nil + } + return T.tryAcquire(j) } @@ -130,6 +137,13 @@ func (T *Scheduler) Enqueue(j ...job.Stalled) { T.mu.RLock() defer T.mu.RUnlock() + if T.closed { + for _, jj := range j { + close(jj.Ready) + return + } + } + for _, jj := range j { T.enqueue(jj) } @@ -146,7 +160,6 @@ func (T *Scheduler) Acquire(user uuid.UUID, mode rob.SyncMode) uuid.UUID { if !ok { ready = make(chan uuid.UUID, 1) } - defer T.ready.Put(ready) j := job.Stalled{ Concurrent: job.Concurrent{ @@ -156,7 +169,11 @@ func (T *Scheduler) Acquire(user uuid.UUID, mode rob.SyncMode) uuid.UUID { } T.Enqueue(j) - return <-ready + s, ok := <-ready + if ok { + T.ready.Put(ready) + } + return s case rob.SyncModeTryNonBlocking: if id := T.Acquire(user, rob.SyncModeNonBlocking); id != uuid.Nil { return id @@ -201,4 +218,27 @@ func (T *Scheduler) stealFor(worker uuid.UUID) { } } +func (T *Scheduler) Close() { + T.mu.Lock() + defer T.mu.Unlock() + + T.closed = true + + for worker, s := range T.sinks { + delete(T.sinks, worker) + + // now we need to reschedule all the work that was scheduled to s (stalled only). + jobs := s.StealAll() + + for _, j := range jobs { + close(j.Ready) + } + } + + for _, j := range T.backlog { + close(j.Ready) + } + T.backlog = T.backlog[:0] +} + var _ rob.Scheduler = (*Scheduler)(nil) diff --git a/lib/util/beforeexit/run.go b/lib/util/beforeexit/run.go deleted file mode 100644 index 7dfc25c29aa889ca1dfe448721d05693c66b11ed..0000000000000000000000000000000000000000 --- a/lib/util/beforeexit/run.go +++ /dev/null @@ -1,50 +0,0 @@ -package beforeexit - -import ( - "os" - "os/signal" - "sync" - "syscall" -) - -var ( - q []func() - active bool - mu sync.Mutex -) - -func registerHandler() { - c := make(chan os.Signal, 2) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - - go func() { - <-c - - mu.Lock() - defer mu.Unlock() - for _, fn := range q { - // ignore any panics in funcs - func() { - defer func() { - recover() - }() - fn() - }() - } - - os.Exit(1) - }() -} - -// Run will register a func to run before exit on receiving an interrupt -// Tasks will run in the order that they are added -func Run(fn func()) { - mu.Lock() - defer mu.Unlock() - - q = append(q, fn) - if !active { - active = true - registerHandler() - } -} diff --git a/lib/util/dur/duration.go b/lib/util/dur/duration.go new file mode 100644 index 0000000000000000000000000000000000000000..b2847c62969a15466e6bb090276a0d0899b46bf7 --- /dev/null +++ b/lib/util/dur/duration.go @@ -0,0 +1,32 @@ +package dur + +import ( + "encoding/json" + "time" +) + +type Duration time.Duration + +func (T *Duration) Duration() time.Duration { + return time.Duration(*T) +} + +func (T *Duration) UnmarshalJSON(bytes []byte) error { + // try as string + var str string + if err := json.Unmarshal(bytes, &str); err == nil { + *(*time.Duration)(T), err = time.ParseDuration(str) + return err + } + + // try num + var num int64 + if err := json.Unmarshal(bytes, &num); err != nil { + return err + } + *T = Duration(num) + + return nil +} + +var _ json.Unmarshaler = (*Duration)(nil) diff --git a/lib/util/slices/remove.go b/lib/util/slices/remove.go index 1848711f5065da4f78b972da1e0675d17ecd24bc..04ca24c193ccdf31c21187f0b9452dc78f865346 100644 --- a/lib/util/slices/remove.go +++ b/lib/util/slices/remove.go @@ -8,7 +8,12 @@ func Remove[T comparable](slice []T, item T) []T { if i == -1 { return slice } - copy(slice[i:], slice[i+1:]) + return RemoveIndex(slice, i) +} + +func RemoveIndex[T any](slice []T, idx int) []T { + item := slice[idx] + copy(slice[idx:], slice[idx+1:]) slice[len(slice)-1] = item return slice[:len(slice)-1] } @@ -19,7 +24,11 @@ func Delete[T comparable](slice []T, item T) []T { if i == -1 { return slice } - copy(slice[i:], slice[i+1:]) + return DeleteIndex(slice, i) +} + +func DeleteIndex[T any](slice []T, idx int) []T { + copy(slice[idx:], slice[idx+1:]) slice[len(slice)-1] = *new(T) return slice[:len(slice)-1] } diff --git a/lib/util/slices/sorted_test.go b/lib/util/slices/sorted_test.go index 49e19e073bf939cd94dfaaed2f793cfde6936b31..bb8dd923b163bc59e0b42eb5ae9e271900a793ec 100644 --- a/lib/util/slices/sorted_test.go +++ b/lib/util/slices/sorted_test.go @@ -1,10 +1,9 @@ package slices import ( + "log" "sort" "testing" - - "tuxpa.in/a/zlog/log" ) func TestSorted_Insert(t *testing.T) { diff --git a/lib/util/strutil/cut.go b/lib/util/strutil/cut.go new file mode 100644 index 0000000000000000000000000000000000000000..d8798b33d244a0368bd6acd7b3557f2aa5dc3c57 --- /dev/null +++ b/lib/util/strutil/cut.go @@ -0,0 +1,21 @@ +package strutil + +import "strings" + +// CutLeft is similar to strings.Cut but it returns "", s, false if not found +func CutLeft(s string, sep string) (before, after string, found bool) { + before, after, found = strings.Cut(s, sep) + if !found { + after = before + before = "" + } + return +} + +// CutRight is similar to strings.Cut but it searches from the end first +func CutRight(s string, sep string) (before, after string, found bool) { + if i := strings.LastIndex(s, sep); i >= 0 { + return s[:i], s[i+len(sep):], true + } + return s, "", false +} diff --git a/pggat.Dockerfile b/pggat.Dockerfile index 57061010df72e342af9468b6d92544448a2b58e6..a9b723d6601493f19fb9466acc2a19a1e32b48d7 100644 --- a/pggat.Dockerfile +++ b/pggat.Dockerfile @@ -5,7 +5,7 @@ WORKDIR /src COPY . . RUN go mod tidy -RUN go build -o cgat ./cmd/cgat +RUN go build -o caddygat ./cmd/caddygat FROM alpine:latest WORKDIR / @@ -13,7 +13,7 @@ RUN apk add --no-cache bash COPY entrypoint.sh . -COPY --from=GOBUILDER /src/cgat /usr/bin/pggat +COPY --from=GOBUILDER /src/presets /presets +COPY --from=GOBUILDER /src/caddygat /usr/bin/pggat -ENTRYPOINT ["/entrypoint.sh"] -cmd ["pggat"] +CMD ["/entrypoint.sh"] diff --git a/presets/default.Caddyfile b/presets/default.Caddyfile new file mode 100644 index 0000000000000000000000000000000000000000..f0e4d67654855b306f43c733fdc465e888e9ac78 --- /dev/null +++ b/presets/default.Caddyfile @@ -0,0 +1,3 @@ +:5432 { + error "server is not configured" +} diff --git a/presets/digitalocean_databases.Caddyfile b/presets/digitalocean_databases.Caddyfile new file mode 100644 index 0000000000000000000000000000000000000000..813c0ef6bf4aa38c20720ee244930296d9fe0931 --- /dev/null +++ b/presets/digitalocean_databases.Caddyfile @@ -0,0 +1,5 @@ +:5432 { + ssl + + discovery digitalocean {$PGGAT_DO_API_KEY} +} diff --git a/presets/google_cloud_sql.Caddyfile b/presets/google_cloud_sql.Caddyfile new file mode 100644 index 0000000000000000000000000000000000000000..89bbd21554c809f39f5faaf7c8368e990cd34b4b --- /dev/null +++ b/presets/google_cloud_sql.Caddyfile @@ -0,0 +1,5 @@ +:5432 { + ssl + + discovery google_cloud_sql {$PGGAT_GC_PROJECT} {$PGGAT_GC_AUTH_USER:pggat} {$PGGAT_GC_AUTH_PASSWORD} +} diff --git a/presets/pgbouncer_spilo.Caddyfile b/presets/pgbouncer_spilo.Caddyfile new file mode 100644 index 0000000000000000000000000000000000000000..b481eff38e34108537ab61fe698fc049ec950f0f --- /dev/null +++ b/presets/pgbouncer_spilo.Caddyfile @@ -0,0 +1,18 @@ +:{$CONNECTION_POOLER_PORT:5432} { + ssl x509_key_pair /etc/ssl/certs/pgbouncer.crt /etc/ssl/certs/pgbouncer.key + require_ssl + + pgbouncer_spilo { + host {$PGHOST} + port {$PGPORT} + user {$PGUSER} + schema {$PGSCHEMA} + password {$PGPASSWORD} + mode {$CONNECTION_POOLER_MODE} + default_size {$CONNECTION_POOLER_DEFAULT_SIZE} + min_size {$CONNECTION_POOLER_MIN_SIZE} + reserve_size {$CONNECTION_POOLER_RESERVE_SIZE} + max_client_conn {$CONNECTION_POOLER_MAX_CLIENT_CONN} + max_db_conn {$CONNECTION_POOLER_MAX_DB_CONN} + } +} \ No newline at end of file diff --git a/presets/zalando_kubernetes_operator.Caddyfile b/presets/zalando_kubernetes_operator.Caddyfile new file mode 100644 index 0000000000000000000000000000000000000000..7e55477200a3850c85ffd53acaa61aa42c945495 --- /dev/null +++ b/presets/zalando_kubernetes_operator.Caddyfile @@ -0,0 +1,5 @@ +:5432 { + ssl + + discovery zalando_operator {$POSTGRES_OPERATOR_CONFIGURATION_OBJECT} +} diff --git a/test/runner.go b/test/runner.go index ec4094d69ebff927b20d879c8614c873f56c8322..d23e94c631e58064f1d933793dcb7a782834a316 100644 --- a/test/runner.go +++ b/test/runner.go @@ -89,8 +89,8 @@ func (T *Runner) prepare(client *gsql.Client, until int) []Capturer { return results } -func (T *Runner) runModeL1(dialer recipe.Dialer, client *gsql.Client) error { - server, _, err := dialer.Dial() +func (T *Runner) runModeL1(dialer recipe.Dialer, client *fed.Conn) error { + server, err := dialer.Dial() if err != nil { return err } @@ -127,7 +127,7 @@ func (T *Runner) runModeOnce(dialer recipe.Dialer) ([]Capturer, error) { return nil, err } - if err := T.runModeL1(dialer, &client); err != nil { + if err := T.runModeL1(dialer, fed.NewConn(&client)); err != nil { return nil, err } @@ -142,7 +142,7 @@ func (T *Runner) runModeFail(dialer recipe.Dialer) error { return err } - if err := T.runModeL1(dialer, &client); err != nil && !errors.Is(err, io.EOF) { + if err := T.runModeL1(dialer, fed.NewConn(&client)); err != nil && !errors.Is(err, io.EOF) { return err } } diff --git a/test/tester_test.go b/test/tester_test.go index 99e985f37af5a9f681cf693079cda262582903fa..6ef9b43191e68e04c7132e452a46e5202fe78dbd 100644 --- a/test/tester_test.go +++ b/test/tester_test.go @@ -2,75 +2,165 @@ package test_test import ( "crypto/rand" - "encoding/hex" + "encoding/base64" "fmt" - "net" _ "net/http/pprof" "strconv" + "strings" "testing" - "gfx.cafe/gfx/pggat/lib/auth" + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "gfx.cafe/gfx/pggat/lib/auth/credentials" - "gfx.cafe/gfx/pggat/lib/bouncer/backends/v0" - "gfx.cafe/gfx/pggat/lib/bouncer/frontends/v0" "gfx.cafe/gfx/pggat/lib/gat" - "gfx.cafe/gfx/pggat/lib/gat/modules/raw_pools" + "gfx.cafe/gfx/pggat/lib/gat/gatcaddyfile" + pool_handler "gfx.cafe/gfx/pggat/lib/gat/handlers/pool" + "gfx.cafe/gfx/pggat/lib/gat/handlers/rewrite_password" + "gfx.cafe/gfx/pggat/lib/gat/matchers" "gfx.cafe/gfx/pggat/lib/gat/pool" - "gfx.cafe/gfx/pggat/lib/gat/pool/pools/session" - "gfx.cafe/gfx/pggat/lib/gat/pool/pools/transaction" "gfx.cafe/gfx/pggat/lib/gat/pool/recipe" + "gfx.cafe/gfx/pggat/lib/gat/poolers/session" + "gfx.cafe/gfx/pggat/lib/gat/poolers/transaction" "gfx.cafe/gfx/pggat/test" "gfx.cafe/gfx/pggat/test/tests" ) -func daisyChain(creds auth.Credentials, control recipe.Dialer, n int) (recipe.Dialer, error) { - for i := 0; i < n; i++ { - var server gat.Server +type dialer struct { + Address string + Username string + Password string + Database string +} + +var nextPort int + +func randAddress() string { + nextPort++ + return "/tmp/.s.PGGAT." + strconv.Itoa(nextPort) +} + +func resolveNetwork(address string) string { + if strings.HasPrefix(address, "/") { + return "unix" + } else { + return "tcp" + } +} + +func randPassword() (string, error) { + var b [20]byte + _, err := rand.Read(b[:]) + if err != nil { + return "", err + } + + return base64.StdEncoding.EncodeToString(b[:]), nil +} + +func createServer(parent dialer, poolers map[string]caddy.Module) (server gat.ServerConfig, dialers map[string]dialer, err error) { + address := randAddress() + + server.Listen = []gat.ListenerConfig{ + { + Address: address, + }, + } - var options = pool.Options{ - Credentials: creds, + var password string + password, err = randPassword() + if err != nil { + return + } + + server.Routes = append( + server.Routes, + gat.RouteConfig{ + Handle: gatcaddyfile.JSONModuleObject( + &rewrite_password.Module{ + Password: password, + }, + gatcaddyfile.Handler, + "handler", + nil, + ), + }, + ) + + for name, pooler := range poolers { + p := pool_handler.Module{ + Config: pool_handler.Config{ + Pooler: gatcaddyfile.JSONModuleObject( + pooler, + gatcaddyfile.Pooler, + "pooler", + nil, + ), + + ServerAddress: parent.Address, + + ServerUsername: parent.Username, + ServerPassword: parent.Password, + ServerDatabase: parent.Database, + }, } - if i%2 == 0 { - options = transaction.Apply(options) - } else { - options.ServerResetQuery = "DISCARD ALL" - options = session.Apply(options) + + server.Routes = append(server.Routes, gat.RouteConfig{ + Match: gatcaddyfile.JSONModuleObject( + &matchers.Database{ + Database: name, + }, + gatcaddyfile.Matcher, + "matcher", + nil, + ), + Handle: gatcaddyfile.JSONModuleObject( + &p, + gatcaddyfile.Handler, + "handler", + nil, + ), + }) + + if dialers == nil { + dialers = make(map[string]dialer) } + dialers[name] = dialer{ + Address: address, + Username: "pooler", + Password: password, + Database: name, + } + } - p := pool.NewPool(options) - p.AddRecipe("runner", recipe.NewRecipe(recipe.Options{ - Dialer: control, - })) + return +} - m, err := raw_pools.NewModule() - if err != nil { - return recipe.Dialer{}, err +func daisyChain(config *gat.Config, control dialer, n int) (dialer, error) { + for i := 0; i < n; i++ { + poolConfig := pool.ManagementConfig{} + var pooler caddy.Module + if i%2 == 0 { + pooler = &transaction.Module{ + ManagementConfig: poolConfig, + } + } else { + poolConfig.ServerResetQuery = "DISCARD ALL" + pooler = &session.Module{ + ManagementConfig: poolConfig, + } } - m.Add("runner", "pool", p) - server.AddModule(m) - listener, err := server.Listen("tcp", ":0") + server, dialers, err := createServer(control, map[string]caddy.Module{ + "pool": pooler, + }) + if err != nil { - return recipe.Dialer{}, err + return dialer{}, err } - port := listener.Addr().(*net.TCPAddr).Port - go func() { - err := server.Serve(listener, frontends.AcceptOptions{}) - if err != nil { - panic(err) - } - }() - - control = recipe.Dialer{ - Network: "tcp", - Address: ":" + strconv.Itoa(port), - AcceptOptions: backends.AcceptOptions{ - Username: "runner", - Credentials: creds, - Database: "pool", - }, - } + control = dialers["pool"] + config.Servers = append(config.Servers, server) } return control, nil @@ -78,96 +168,80 @@ func daisyChain(creds auth.Credentials, control recipe.Dialer, n int) (recipe.Di func TestTester(t *testing.T) { control := recipe.Dialer{ - Network: "tcp", - Address: "localhost:5432", - AcceptOptions: backends.AcceptOptions{ + Network: "tcp", + Address: "localhost:5432", + Username: "postgres", + Credentials: credentials.Cleartext{ Username: "postgres", - Credentials: credentials.Cleartext{ - Username: "postgres", - Password: "password", - }, - Database: "postgres", + Password: "password", }, + Database: "postgres", } - // generate random password for testing - var raw [32]byte - _, err := rand.Read(raw[:]) - if err != nil { - t.Error(err) - return - } - password := hex.EncodeToString(raw[:]) - creds := credentials.Cleartext{ - Username: "runner", - Password: password, - } + config := gat.Config{} - parent, err := daisyChain(creds, control, 16) + parent, err := daisyChain(&config, dialer{ + Address: "localhost:5432", + Username: "postgres", + Password: "password", + Database: "postgres", + }, 16) if err != nil { t.Error(err) return } - var server gat.Server - - m, err := raw_pools.NewModule() - if err != nil { - t.Error(err) - return - } - transactionPool := pool.NewPool(transaction.Apply(pool.Options{ - Credentials: creds, - })) - transactionPool.AddRecipe("runner", recipe.NewRecipe(recipe.Options{ - Dialer: parent, - })) - m.Add("runner", "transaction", transactionPool) - - sessionPool := pool.NewPool(session.Apply(pool.Options{ - Credentials: creds, - ServerResetQuery: "discard all", - })) - sessionPool.AddRecipe("runner", recipe.NewRecipe(recipe.Options{ - Dialer: parent, - })) - m.Add("runner", "session", sessionPool) - - server.AddModule(m) - - listener, err := server.Listen("tcp", ":0") + server, dialers, err := createServer(parent, map[string]caddy.Module{ + "transaction": &transaction.Module{}, + "session": &session.Module{ + ManagementConfig: pool.ManagementConfig{ + ServerResetQuery: "discard all", + }, + }, + }) if err != nil { t.Error(err) return } - port := listener.Addr().(*net.TCPAddr).Port - go func() { - err := server.Serve(listener, frontends.AcceptOptions{}) - if err != nil { - t.Error(err) - } - }() + config.Servers = append(config.Servers, server) transactionDialer := recipe.Dialer{ - Network: "tcp", - Address: ":" + strconv.Itoa(port), - AcceptOptions: backends.AcceptOptions{ - Username: "runner", - Credentials: creds, - Database: "transaction", - }, + Network: resolveNetwork(dialers["transaction"].Address), + Address: dialers["transaction"].Address, + Username: dialers["transaction"].Username, + Credentials: credentials.FromString( + dialers["transaction"].Username, + dialers["transaction"].Password, + ), + Database: "transaction", } sessionDialer := recipe.Dialer{ - Network: "tcp", - Address: ":" + strconv.Itoa(port), - AcceptOptions: backends.AcceptOptions{ - Username: "runner", - Credentials: creds, - Database: "session", + Network: resolveNetwork(dialers["transaction"].Address), + Address: dialers["session"].Address, + Username: dialers["session"].Username, + Credentials: credentials.FromString( + dialers["session"].Username, + dialers["session"].Password, + ), + Database: "session", + } + + caddyConfig := caddy.Config{ + AppsRaw: caddy.ModuleMap{ + "pggat": caddyconfig.JSON(config, nil), }, } + if err = caddy.Run(&caddyConfig); err != nil { + t.Error(err) + return + } + + defer func() { + _ = caddy.Stop() + }() + tester := test.NewTester(test.Config{ Stress: 8,