diff --git a/cmd/cgat/main.go b/cmd/cgat/main.go
index 546d5ca13905edb36a1e2aef6c2166d5edbe8f38..e5b383b97317d85522c4e412616421236166cef3 100644
--- a/cmd/cgat/main.go
+++ b/cmd/cgat/main.go
@@ -8,6 +8,7 @@ import (
 
 	"tuxpa.in/a/zlog/log"
 
+	"pggat/lib/gat/modes/cloud_sql_discovery"
 	"pggat/lib/gat/modes/digitalocean_discovery"
 	"pggat/lib/gat/modes/pgbouncer"
 	"pggat/lib/gat/modes/zalando"
@@ -50,6 +51,18 @@ func main() {
 		return
 	}
 
+	if os.Getenv("PGGAT_GC_PROJECT") != "" {
+		conf, err := cloud_sql_discovery.Load()
+		if err != nil {
+			panic(err)
+		}
+		err = conf.ListenAndServe()
+		if err != nil {
+			panic(err)
+		}
+		return
+	}
+
 	if os.Getenv("PGGAT_DO_API_KEY") != "" {
 		log.Printf("running in digitalocean discovery mode")
 
diff --git a/go.mod b/go.mod
index 17e750bd2d8c8c9cb6f5bb43bdf91170267f317f..e8bb04878239cc92e262f79e9ba90bcf9c719baa 100644
--- a/go.mod
+++ b/go.mod
@@ -8,12 +8,14 @@ require (
 	github.com/google/uuid v1.3.0
 	github.com/xdg-go/scram v1.1.2
 	github.com/zalando/postgres-operator v1.10.1
+	google.golang.org/api v0.30.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
 	github.com/davecgh/go-spew v1.1.1 // indirect
@@ -23,11 +25,13 @@ require (
 	github.com/go-openapi/jsonreference v0.20.1 // indirect
 	github.com/go-openapi/swag v0.22.3 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // 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/hashicorp/go-cleanhttp v0.5.2 // indirect
 	github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
 	github.com/imdario/mergo v0.3.6 // indirect
@@ -47,6 +51,7 @@ require (
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/xdg-go/pbkdf2 v1.0.0 // 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
@@ -55,6 +60,8 @@ require (
 	golang.org/x/text v0.9.0 // indirect
 	golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // 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
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
diff --git a/go.sum b/go.sum
index 2cab442610268da01c534c02fc398442c067962a..d512692cb614c240fd1aa43967ed43d83a745b66 100644
--- a/go.sum
+++ b/go.sum
@@ -12,6 +12,7 @@ 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=
@@ -35,12 +36,19 @@ gfx.cafe/util/go/gun v0.0.0-20230721185457-c559e86c829c h1:4XxKaHfYPam36FibTiy1T
 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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 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/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=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 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-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-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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -60,7 +68,10 @@ github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry
 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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+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=
@@ -81,6 +92,8 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@@ -101,6 +114,7 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
 github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
@@ -118,6 +132,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 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.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=
@@ -136,10 +151,13 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
 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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+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/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/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=
 github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
@@ -190,6 +208,7 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
 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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+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=
@@ -229,7 +248,9 @@ 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/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 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=
@@ -297,6 +318,7 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
 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-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-20220127200216-cd36cc0744dd/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=
@@ -345,6 +367,9 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
 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-20210330210617-4fbd30eecc44/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-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=
@@ -364,6 +389,7 @@ 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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
 golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
@@ -436,6 +462,7 @@ 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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -468,6 +495,7 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
 google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
 google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
@@ -475,6 +503,8 @@ 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/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=
@@ -487,6 +517,11 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
 google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
 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.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/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=
@@ -499,6 +534,8 @@ 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=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -510,6 +547,7 @@ 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/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.8/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=
diff --git a/lib/auth/credentials.go b/lib/auth/credentials.go
index b8b2299429e286ee2e2929c69d96f6cc8de02888..5bb6b7fa89950b9ba71a5e104956a4e007a3e659 100644
--- a/lib/auth/credentials.go
+++ b/lib/auth/credentials.go
@@ -1,20 +1,30 @@
 package auth
 
 type Credentials interface {
-	GetUsername() string
+	Credentials()
 }
 
-type Cleartext interface {
+type CleartextClient interface {
 	Credentials
 
 	EncodeCleartext() string
+}
+
+type CleartextServer interface {
+	Credentials
+
 	VerifyCleartext(value string) error
 }
 
-type MD5 interface {
+type MD5Client interface {
 	Credentials
 
 	EncodeMD5(salt [4]byte) string
+}
+
+type MD5Server interface {
+	Credentials
+
 	VerifyMD5(salt [4]byte, value string) error
 }
 
@@ -32,11 +42,16 @@ type SASLVerifier interface {
 	Write(bytes []byte) ([]byte, error)
 }
 
-type SASL interface {
+type SASLClient interface {
+	Credentials
+
+	EncodeSASL(mechanisms []SASLMechanism) (SASLMechanism, SASLEncoder, error)
+}
+
+type SASLServer interface {
 	Credentials
 
 	SupportedSASLMechanisms() []SASLMechanism
 
-	EncodeSASL(mechanisms []SASLMechanism) (SASLMechanism, SASLEncoder, error)
 	VerifySASL(mechanism SASLMechanism) (SASLVerifier, error)
 }
diff --git a/lib/auth/credentials/cleartext.go b/lib/auth/credentials/cleartext.go
index 45821e07f726f6e2c4042040b0843e5feed5e356..4a00ee13b925111c0019f74db19277ad18756cb6 100644
--- a/lib/auth/credentials/cleartext.go
+++ b/lib/auth/credentials/cleartext.go
@@ -16,9 +16,7 @@ type Cleartext struct {
 	Password string
 }
 
-func (T Cleartext) GetUsername() string {
-	return T.Username
-}
+func (Cleartext) Credentials() {}
 
 func (T Cleartext) EncodeCleartext() string {
 	return T.Password
@@ -67,31 +65,6 @@ func (T Cleartext) SupportedSASLMechanisms() []auth.SASLMechanism {
 	}
 }
 
-type CleartextScramEncoder struct {
-	conversation *scram.ClientConversation
-}
-
-func MakeCleartextScramEncoder(username, password string, hashGenerator scram.HashGeneratorFcn) (CleartextScramEncoder, error) {
-	client, err := hashGenerator.NewClient(username, password, "")
-	if err != nil {
-		return CleartextScramEncoder{}, err
-	}
-
-	return CleartextScramEncoder{
-		conversation: client.NewConversation(),
-	}, nil
-}
-
-func (T CleartextScramEncoder) Write(bytes []byte) ([]byte, error) {
-	msg, err := T.conversation.Step(string(bytes))
-	if err != nil {
-		return nil, err
-	}
-	return []byte(msg), nil
-}
-
-var _ auth.SASLEncoder = CleartextScramEncoder{}
-
 func (T Cleartext) EncodeSASL(mechanisms []auth.SASLMechanism) (auth.SASLMechanism, auth.SASLEncoder, error) {
 	for _, mechanism := range mechanisms {
 		switch mechanism {
@@ -107,57 +80,6 @@ func (T Cleartext) EncodeSASL(mechanisms []auth.SASLMechanism) (auth.SASLMechani
 	return "", nil, auth.ErrSASLMechanismNotSupported
 }
 
-type CleartextScramVerifier struct {
-	conversation *scram.ServerConversation
-}
-
-func MakeCleartextScramVerifier(username, password string, hashGenerator scram.HashGeneratorFcn) (CleartextScramVerifier, error) {
-	client, err := hashGenerator.NewClient(username, password, "")
-	if err != nil {
-		return CleartextScramVerifier{}, err
-	}
-
-	kf := scram.KeyFactors{
-		Iters: 4096,
-	}
-	stored := client.GetStoredCredentials(kf)
-
-	server, err := hashGenerator.NewServer(
-		func(string) (scram.StoredCredentials, error) {
-			return stored, nil
-		},
-	)
-	if err != nil {
-		return CleartextScramVerifier{}, err
-	}
-
-	return CleartextScramVerifier{
-		conversation: server.NewConversation(),
-	}, nil
-}
-
-func (T CleartextScramVerifier) Write(bytes []byte) ([]byte, error) {
-	msg, err := T.conversation.Step(string(bytes))
-	if err != nil {
-		return nil, err
-	}
-
-	if T.conversation.Done() {
-		// check if conversation params are valid
-		if !T.conversation.Valid() {
-			return nil, auth.ErrFailed
-		}
-
-		// done
-		return []byte(msg), auth.ErrSASLComplete
-	}
-
-	// there is more
-	return []byte(msg), nil
-}
-
-var _ auth.SASLVerifier = CleartextScramVerifier{}
-
 func (T Cleartext) VerifySASL(mechanism auth.SASLMechanism) (auth.SASLVerifier, error) {
 	switch mechanism {
 	case auth.ScramSHA256:
@@ -168,6 +90,9 @@ func (T Cleartext) VerifySASL(mechanism auth.SASLMechanism) (auth.SASLVerifier,
 }
 
 var _ auth.Credentials = Cleartext{}
-var _ auth.Cleartext = Cleartext{}
-var _ auth.MD5 = Cleartext{}
-var _ auth.SASL = Cleartext{}
+var _ auth.CleartextClient = Cleartext{}
+var _ auth.CleartextServer = Cleartext{}
+var _ auth.MD5Client = Cleartext{}
+var _ auth.MD5Server = Cleartext{}
+var _ auth.SASLClient = Cleartext{}
+var _ auth.SASLServer = Cleartext{}
diff --git a/lib/auth/credentials/errors.go b/lib/auth/credentials/errors.go
new file mode 100644
index 0000000000000000000000000000000000000000..e41c6f2379aa814c2020d6406d25e7fe96d7b9ca
--- /dev/null
+++ b/lib/auth/credentials/errors.go
@@ -0,0 +1,7 @@
+package credentials
+
+import "errors"
+
+var (
+	ErrInvalidSecretFormat = errors.New("invalid secret format")
+)
diff --git a/lib/auth/credentials/md5.go b/lib/auth/credentials/md5.go
index c96c48487a40b355e03dcba04ef97d4c0b66f7cc..bb891bf54505018e5b8c72ee37326b03e46ba034 100644
--- a/lib/auth/credentials/md5.go
+++ b/lib/auth/credentials/md5.go
@@ -10,14 +10,27 @@ import (
 )
 
 type MD5 struct {
-	Username string
-	Hash     []byte
+	Hash []byte
 }
 
-func (T MD5) GetUsername() string {
-	return T.Username
+func MD5FromString(value string) (MD5, error) {
+	if !strings.HasPrefix(value, "md5") {
+		return MD5{}, ErrInvalidSecretFormat
+	}
+
+	var res MD5
+	var err error
+	hexString := strings.TrimPrefix(value, "md5")
+	res.Hash, err = hex.DecodeString(hexString)
+	if err != nil {
+		return MD5{}, err
+	}
+
+	return res, nil
 }
 
+func (MD5) Credentials() {}
+
 func (T MD5) EncodeMD5(salt [4]byte) string {
 	hexEncoded := make([]byte, hex.EncodedLen(len(T.Hash)))
 	hex.Encode(hexEncoded, T.Hash)
@@ -44,4 +57,6 @@ func (T MD5) VerifyMD5(salt [4]byte, value string) error {
 	return nil
 }
 
-var _ auth.MD5 = MD5{}
+var _ auth.Credentials = MD5{}
+var _ auth.MD5Client = MD5{}
+var _ auth.MD5Server = MD5{}
diff --git a/lib/auth/credentials/scram.go b/lib/auth/credentials/scram.go
new file mode 100644
index 0000000000000000000000000000000000000000..431474419a8ecd306143f1da38d6611ceb628670
--- /dev/null
+++ b/lib/auth/credentials/scram.go
@@ -0,0 +1,89 @@
+package credentials
+
+import (
+	"github.com/xdg-go/scram"
+
+	"pggat/lib/auth"
+)
+
+type ConversationScramEncoder struct {
+	conversation *scram.ClientConversation
+}
+
+func MakeCleartextScramEncoder(username, password string, hashGenerator scram.HashGeneratorFcn) (auth.SASLEncoder, error) {
+	client, err := hashGenerator.NewClient(username, password, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return ConversationScramEncoder{
+		conversation: client.NewConversation(),
+	}, nil
+}
+
+func (T ConversationScramEncoder) Write(bytes []byte) ([]byte, error) {
+	msg, err := T.conversation.Step(string(bytes))
+	if err != nil {
+		return nil, err
+	}
+	return []byte(msg), nil
+}
+
+var _ auth.SASLEncoder = ConversationScramEncoder{}
+
+type ConversationScramVerifier struct {
+	conversation *scram.ServerConversation
+}
+
+func MakeCleartextScramVerifier(username, password string, hashGenerator scram.HashGeneratorFcn) (auth.SASLVerifier, error) {
+	client, err := hashGenerator.NewClient(username, password, "")
+	if err != nil {
+		return nil, err
+	}
+
+	kf := scram.KeyFactors{
+		Iters: 4096,
+	}
+	stored := client.GetStoredCredentials(kf)
+
+	return MakeStoredCredentialsScramVerifier(stored, hashGenerator)
+}
+
+func MakeStoredCredentialsScramVerifier(credentials scram.StoredCredentials, hashGenerator scram.HashGeneratorFcn) (auth.SASLVerifier, error) {
+	server, err := hashGenerator.NewServer(
+		func(string) (scram.StoredCredentials, error) {
+			return credentials, nil
+		},
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	return ConversationScramVerifier{
+		conversation: server.NewConversation(),
+	}, nil
+}
+
+func (T ConversationScramVerifier) Write(bytes []byte) ([]byte, error) {
+	msg, err := T.conversation.Step(string(bytes))
+	if err != nil {
+		return nil, err
+	}
+
+	if T.conversation.Done() {
+		// check if conversation params are valid
+		if !T.conversation.Valid() {
+			return nil, auth.ErrFailed
+		}
+
+		T.conversation.AuthzID()
+
+		// done
+		return []byte(msg), auth.ErrSASLComplete
+	}
+
+	// there is more
+	return []byte(msg), nil
+}
+
+var _ auth.SASLVerifier = ConversationScramVerifier{}
diff --git a/lib/auth/credentials/scram_sha_256.go b/lib/auth/credentials/scram_sha_256.go
new file mode 100644
index 0000000000000000000000000000000000000000..cbc382c0874c45cb60a66d553b6feb98d2edf935
--- /dev/null
+++ b/lib/auth/credentials/scram_sha_256.go
@@ -0,0 +1,77 @@
+package credentials
+
+import (
+	"encoding/base64"
+	"strconv"
+	"strings"
+
+	"github.com/xdg-go/scram"
+
+	"pggat/lib/auth"
+)
+
+type ScramSHA256 struct {
+	StoredCredentials scram.StoredCredentials
+}
+
+func ScramSHA256FromString(value string) (ScramSHA256, error) {
+	alg, iterKeys, ok := strings.Cut(value, "$")
+	if !ok || alg != "SCRAM-SHA-256" {
+		return ScramSHA256{}, ErrInvalidSecretFormat
+	}
+	iterSalt, keys, ok := strings.Cut(iterKeys, "$")
+	if !ok {
+		return ScramSHA256{}, ErrInvalidSecretFormat
+	}
+	iter, salt, ok := strings.Cut(iterSalt, ":")
+	if !ok {
+		return ScramSHA256{}, ErrInvalidSecretFormat
+	}
+	storedKey, serverKey, ok := strings.Cut(keys, ":")
+
+	var res ScramSHA256
+	var err error
+	res.StoredCredentials.Iters, err = strconv.Atoi(iter)
+	if err != nil {
+		return ScramSHA256{}, err
+	}
+
+	var saltBytes []byte
+	saltBytes, err = base64.StdEncoding.DecodeString(salt)
+	if err != nil {
+		return ScramSHA256{}, err
+	}
+	res.StoredCredentials.Salt = string(saltBytes)
+
+	res.StoredCredentials.StoredKey, err = base64.StdEncoding.DecodeString(storedKey)
+	if err != nil {
+		return ScramSHA256{}, err
+	}
+
+	res.StoredCredentials.ServerKey, err = base64.StdEncoding.DecodeString(serverKey)
+	if err != nil {
+		return ScramSHA256{}, err
+	}
+
+	return res, nil
+}
+
+func (T ScramSHA256) SupportedSASLMechanisms() []auth.SASLMechanism {
+	return []auth.SASLMechanism{
+		auth.ScramSHA256,
+	}
+}
+
+func (T ScramSHA256) VerifySASL(mechanism auth.SASLMechanism) (auth.SASLVerifier, error) {
+	switch mechanism {
+	case auth.ScramSHA256:
+		return MakeStoredCredentialsScramVerifier(T.StoredCredentials, scram.SHA256)
+	default:
+		return nil, auth.ErrSASLMechanismNotSupported
+	}
+}
+
+func (ScramSHA256) Credentials() {}
+
+var _ auth.Credentials = ScramSHA256{}
+var _ auth.SASLServer = ScramSHA256{}
diff --git a/lib/auth/credentials/string.go b/lib/auth/credentials/string.go
index 4f04779d0e9164cc9873c7ce66c63005461a3bcb..cebd45dab79d1465743d488144f70185b1aefea8 100644
--- a/lib/auth/credentials/string.go
+++ b/lib/auth/credentials/string.go
@@ -1,32 +1,20 @@
 package credentials
 
 import (
-	"encoding/hex"
-	"strings"
-
 	"pggat/lib/auth"
 )
 
 func FromString(user, password string) auth.Credentials {
 	if password == "" {
 		return nil
-	} else if strings.HasPrefix(password, "md5") {
-		hexHash := strings.TrimPrefix(password, "md5")
-		hash, err := hex.DecodeString(hexHash)
-		if err != nil {
-			return Cleartext{
-				Username: user,
-				Password: password,
-			}
-		}
-		return MD5{
-			Username: user,
-			Hash:     hash,
-		}
+	} else if v, err := ScramSHA256FromString(password); err == nil {
+		return v
+	} else if v, err := MD5FromString(password); err == nil {
+		return v
 	} else {
 		return Cleartext{
 			Username: user,
-			Password: password, // TODO(garet) sasl
+			Password: password,
 		}
 	}
 }
diff --git a/lib/bouncer/backends/v0/accept.go b/lib/bouncer/backends/v0/accept.go
index d07616d003ba72360e8cd733c25997e159edf1f7..fe11b1698ad6c12795bb7d8e8d24f1562b93197a 100644
--- a/lib/bouncer/backends/v0/accept.go
+++ b/lib/bouncer/backends/v0/accept.go
@@ -51,7 +51,7 @@ func authenticationSASLChallenge(ctx *AcceptContext, encoder auth.SASLEncoder) (
 	}
 }
 
-func authenticationSASL(ctx *AcceptContext, mechanisms []string, creds auth.SASL) error {
+func authenticationSASL(ctx *AcceptContext, mechanisms []string, creds auth.SASLClient) error {
 	mechanism, encoder, err := creds.EncodeSASL(mechanisms)
 	if err != nil {
 		return err
@@ -86,7 +86,7 @@ func authenticationSASL(ctx *AcceptContext, mechanisms []string, creds auth.SASL
 	return nil
 }
 
-func authenticationMD5(ctx *AcceptContext, salt [4]byte, creds auth.MD5) error {
+func authenticationMD5(ctx *AcceptContext, salt [4]byte, creds auth.MD5Client) error {
 	pw := packets.PasswordMessage{
 		Password: creds.EncodeMD5(salt),
 	}
@@ -98,7 +98,7 @@ func authenticationMD5(ctx *AcceptContext, salt [4]byte, creds auth.MD5) error {
 	return nil
 }
 
-func authenticationCleartext(ctx *AcceptContext, creds auth.Cleartext) error {
+func authenticationCleartext(ctx *AcceptContext, creds auth.CleartextClient) error {
 	pw := packets.PasswordMessage{
 		Password: creds.EncodeCleartext(),
 	}
@@ -122,7 +122,7 @@ func authentication(ctx *AcceptContext) (done bool, err error) {
 		err = errors.New("kerberos v5 is not supported")
 		return
 	case 3:
-		c, ok := ctx.Options.Credentials.(auth.Cleartext)
+		c, ok := ctx.Options.Credentials.(auth.CleartextClient)
 		if !ok {
 			return false, auth.ErrMethodNotSupported
 		}
@@ -134,7 +134,7 @@ func authentication(ctx *AcceptContext) (done bool, err error) {
 			return
 		}
 
-		c, ok := ctx.Options.Credentials.(auth.MD5)
+		c, ok := ctx.Options.Credentials.(auth.MD5Client)
 		if !ok {
 			return false, auth.ErrMethodNotSupported
 		}
@@ -156,7 +156,7 @@ func authentication(ctx *AcceptContext) (done bool, err error) {
 			return
 		}
 
-		c, ok := ctx.Options.Credentials.(auth.SASL)
+		c, ok := ctx.Options.Credentials.(auth.SASLClient)
 		if !ok {
 			return false, auth.ErrMethodNotSupported
 		}
@@ -272,7 +272,7 @@ func enableSSL(ctx *AcceptContext) (bool, error) {
 }
 
 func Accept(ctx *AcceptContext) (AcceptParams, error) {
-	username := ctx.Options.Credentials.GetUsername()
+	username := ctx.Options.Username
 
 	if ctx.Options.Database == "" {
 		ctx.Options.Database = username
diff --git a/lib/bouncer/backends/v0/options.go b/lib/bouncer/backends/v0/options.go
index b75821792c0eb02ee912423a2a954d8e14fe22bf..b05b8ba5d00a4d1024bd569be561d41fb5134850 100644
--- a/lib/bouncer/backends/v0/options.go
+++ b/lib/bouncer/backends/v0/options.go
@@ -11,6 +11,7 @@ import (
 type AcceptOptions struct {
 	SSLMode           bouncer.SSLMode
 	SSLConfig         *tls.Config
+	Username          string
 	Credentials       auth.Credentials
 	Database          string
 	StartupParameters map[strutil.CIString]string
diff --git a/lib/bouncer/frontends/v0/authenticate.go b/lib/bouncer/frontends/v0/authenticate.go
index ad05e298f60ceb1364847d8c0532dfe34d220db9..abcff8d37e0f263d794f986c10d5d8a8c79e8622 100644
--- a/lib/bouncer/frontends/v0/authenticate.go
+++ b/lib/bouncer/frontends/v0/authenticate.go
@@ -9,7 +9,7 @@ import (
 	"pggat/lib/perror"
 )
 
-func authenticationSASLInitial(ctx *AuthenticateContext, creds auth.SASL) (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)
@@ -66,7 +66,7 @@ func authenticationSASLContinue(ctx *AuthenticateContext, tool auth.SASLVerifier
 	return
 }
 
-func authenticationSASL(ctx *AuthenticateContext, creds auth.SASL) perror.Error {
+func authenticationSASL(ctx *AuthenticateContext, creds auth.SASLServer) perror.Error {
 	saslInitial := packets.AuthenticationSASL{
 		Mechanisms: creds.SupportedSASLMechanisms(),
 	}
@@ -108,7 +108,7 @@ func authenticationSASL(ctx *AuthenticateContext, creds auth.SASL) perror.Error
 	return nil
 }
 
-func authenticationMD5(ctx *AuthenticateContext, creds auth.MD5) perror.Error {
+func authenticationMD5(ctx *AuthenticateContext, creds auth.MD5Server) perror.Error {
 	var salt [4]byte
 	_, err := rand.Read(salt[:])
 	if err != nil {
@@ -141,27 +141,21 @@ func authenticationMD5(ctx *AuthenticateContext, creds auth.MD5) perror.Error {
 }
 
 func authenticate(ctx *AuthenticateContext) (params AuthenticateParams, err perror.Error) {
-	if ctx.Options.Credentials == nil {
-		err = perror.New(
-			perror.FATAL,
-			perror.InvalidPassword,
-			"User or database not found",
-		)
-		return
-	}
-	if credsSASL, ok := ctx.Options.Credentials.(auth.SASL); ok {
-		err = authenticationSASL(ctx, credsSASL)
-	} else if credsMD5, ok := ctx.Options.Credentials.(auth.MD5); ok {
-		err = authenticationMD5(ctx, credsMD5)
-	} else {
-		err = perror.New(
-			perror.FATAL,
-			perror.InternalError,
-			"Auth method not supported",
-		)
-	}
-	if err != nil {
-		return
+	if ctx.Options.Credentials != nil {
+		if credsSASL, ok := ctx.Options.Credentials.(auth.SASLServer); ok {
+			err = authenticationSASL(ctx, credsSASL)
+		} else if credsMD5, ok := ctx.Options.Credentials.(auth.MD5Server); ok {
+			err = authenticationMD5(ctx, credsMD5)
+		} else {
+			err = perror.New(
+				perror.FATAL,
+				perror.InternalError,
+				"Auth method not supported",
+			)
+		}
+		if err != nil {
+			return
+		}
 	}
 
 	// send auth Ok
diff --git a/lib/gat/modes/cloud_sql_discovery/config.go b/lib/gat/modes/cloud_sql_discovery/config.go
new file mode 100644
index 0000000000000000000000000000000000000000..28eaa55db5a568707333ac393fcf7fafc215eed2
--- /dev/null
+++ b/lib/gat/modes/cloud_sql_discovery/config.go
@@ -0,0 +1,144 @@
+package cloud_sql_discovery
+
+import (
+	"context"
+	"crypto/tls"
+	"errors"
+	"net"
+	"strings"
+	"time"
+
+	"gfx.cafe/util/go/gun"
+	sqladmin "google.golang.org/api/sqladmin/v1beta4"
+	"tuxpa.in/a/zlog/log"
+
+	"pggat/lib/auth/credentials"
+	"pggat/lib/bouncer"
+	"pggat/lib/bouncer/backends/v0"
+	"pggat/lib/bouncer/frontends/v0"
+	"pggat/lib/gat"
+	"pggat/lib/gat/pool"
+	"pggat/lib/gat/pool/dialer"
+	"pggat/lib/gat/pool/pools/transaction"
+	"pggat/lib/gat/pool/recipe"
+	"pggat/lib/util/flip"
+	"pggat/lib/util/strutil"
+)
+
+type Config struct {
+	Project       string `env:"PGGAT_GC_PROJECT"`
+	IpAddressType string `env:"PGGAT_GC_IP_ADDR_TYPE" default:"PRIMARY"`
+}
+
+func Load() (Config, error) {
+	var conf Config
+	gun.Load(&conf)
+	if conf.Project == "" {
+		return Config{}, errors.New("expected google cloud project id")
+	}
+	return conf, nil
+}
+
+func (T *Config) ListenAndServe() error {
+	service, err := sqladmin.NewService(context.Background())
+	if err != nil {
+		return err
+	}
+
+	instances, err := service.Instances.List(T.Project).Do()
+	if err != nil {
+		return err
+	}
+
+	var pools gat.PoolsMap
+
+	for _, instance := range instances.Items {
+		if !strings.HasPrefix(instance.DatabaseVersion, "POSTGRES_") {
+			continue
+		}
+
+		var address string
+		for _, ip := range instance.IpAddresses {
+			if ip.Type != T.IpAddressType {
+				continue
+			}
+			address = net.JoinHostPort(ip.IpAddress, "5432")
+		}
+		if address == "" {
+			continue
+		}
+
+		users, err := service.Users.List(T.Project, instance.Name).Do()
+		if err != nil {
+			return err
+		}
+		databases, err := service.Databases.List(T.Project, instance.Name).Do()
+		if err != nil {
+			return err
+		}
+		for _, user := range users.Items {
+			creds := credentials.Cleartext{
+				Username: user.Name,
+				Password: "password",
+			}
+
+			for _, database := range databases.Items {
+				d := dialer.Net{
+					Network: "tcp",
+					Address: address,
+					AcceptOptions: backends.AcceptOptions{
+						SSLMode: bouncer.SSLModePrefer,
+						SSLConfig: &tls.Config{
+							InsecureSkipVerify: true,
+						},
+						Username:    user.Name,
+						Credentials: creds,
+						Database:    database.Name,
+					},
+				}
+
+				options := transaction.Apply(pool.Options{
+					Credentials:                creds,
+					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"),
+					},
+				})
+
+				p := pool.NewPool(options)
+				p.AddRecipe(instance.Name, recipe.NewRecipe(recipe.Options{
+					Dialer: d,
+				}))
+
+				pools.Add(user.Name, database.Name, p)
+				log.Printf("registered database user=%s database=%s", user.Name, database.Name)
+			}
+		}
+	}
+
+	var b flip.Bank
+
+	b.Queue(func() error {
+		log.Print("listening on :5432")
+		return gat.ListenAndServe("tcp", ":5432", frontends.AcceptOptions{
+			// TODO(garet) ssl 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"),
+			},
+		}, &pools)
+	})
+
+	return b.Wait()
+}
diff --git a/lib/gat/modes/digitalocean_discovery/config.go b/lib/gat/modes/digitalocean_discovery/config.go
index f09a0472cd59ab211f0374433cfe71587ef8a987..4edf63e4318fcccc5dbacb6e6af1f7f86cf06770 100644
--- a/lib/gat/modes/digitalocean_discovery/config.go
+++ b/lib/gat/modes/digitalocean_discovery/config.go
@@ -122,6 +122,7 @@ func (T *Config) ListenAndServe() error {
 					SSLConfig: &tls.Config{
 						InsecureSkipVerify: true,
 					},
+					Username:    user.Name,
 					Credentials: creds,
 					Database:    dbname,
 				}
diff --git a/lib/gat/modes/pgbouncer/pools.go b/lib/gat/modes/pgbouncer/pools.go
index da870275974aa4307e1b552a9b8e4755a5f25882..77123a7c4a15e82609e1706ac4e2eb6933ccbbf5 100644
--- a/lib/gat/modes/pgbouncer/pools.go
+++ b/lib/gat/modes/pgbouncer/pools.go
@@ -191,6 +191,7 @@ func (T *Pools) Lookup(user, database string) *pool.Pool {
 		SSLConfig: &tls.Config{
 			InsecureSkipVerify: true, // TODO(garet)
 		},
+		Username:          user,
 		Credentials:       dbCreds,
 		Database:          backendDatabase,
 		StartupParameters: db.StartupParameters,
diff --git a/lib/gat/modes/zalando_operator_discovery/server.go b/lib/gat/modes/zalando_operator_discovery/server.go
index a705590ee5e8549f451aa64b97e8e1800e267266..8115889caa15c55f4f275b130ff7b0c04c2ded1a 100644
--- a/lib/gat/modes/zalando_operator_discovery/server.go
+++ b/lib/gat/modes/zalando_operator_discovery/server.go
@@ -153,7 +153,7 @@ func (T *Server) addPostgresql(psql *acidv1.Postgresql) {
 	T.updatePostgresql(nil, psql)
 }
 
-func (T *Server) addPool(name string, userCreds, serverCreds auth.Credentials, database string) {
+func (T *Server) addPool(name string, userCreds, serverCreds auth.Credentials, userUser, serverUser, database string) {
 	d := dialer.Net{
 		Network: "tcp",
 		Address: fmt.Sprintf("%s.%s.svc.%s:5432", name, T.config.Namespace, T.opConfig.ClusterDomain),
@@ -162,6 +162,7 @@ func (T *Server) addPool(name string, userCreds, serverCreds auth.Credentials, d
 			SSLConfig: &tls.Config{
 				InsecureSkipVerify: true,
 			},
+			Username:    serverUser,
 			Credentials: serverCreds,
 			Database:    database,
 		},
@@ -205,7 +206,7 @@ func (T *Server) addPool(name string, userCreds, serverCreds auth.Credentials, d
 
 	p.AddRecipe("service", r)
 
-	T.pools.Add(userCreds.GetUsername(), database, p)
+	T.pools.Add(userUser, database, p)
 }
 
 func (T *Server) updatePostgresql(oldPsql *acidv1.Postgresql, newPsql *acidv1.Postgresql) {
@@ -305,7 +306,7 @@ func (T *Server) updatePostgresql(oldPsql *acidv1.Postgresql, newPsql *acidv1.Po
 			Username: pair.User,
 			Password: creds.Password,
 		}
-		T.addPool(details.Name, userCreds, creds, pair.Database)
+		T.addPool(details.Name, userCreds, creds, pair.User, details.SecretUser, pair.Database)
 		log.Print("added pool username=", pair.User, " database=", pair.Database)
 	}
 }
diff --git a/lib/gat/pool/scaler.go b/lib/gat/pool/scaler.go
index 224c37fbcedfed63b74be006e0fd864a444f58a5..ef61951b7f1a83558a883db71606185a8be007fb 100644
--- a/lib/gat/pool/scaler.go
+++ b/lib/gat/pool/scaler.go
@@ -2,6 +2,7 @@ package pool
 
 import (
 	"time"
+
 	"tuxpa.in/a/zlog/log"
 )
 
@@ -19,7 +20,7 @@ type Scaler struct {
 func NewScaler(pool *Pool) *Scaler {
 	s := &Scaler{
 		pool:    pool,
-		backoff: pool.options.ServerIdleTimeout,
+		backoff: pool.options.ServerReconnectInitialTime,
 	}
 
 	if pool.options.ServerIdleTimeout != 0 {
diff --git a/lib/gsql/query_test.go b/lib/gsql/query_test.go
index 09b7f76fd0fbd7844620742e575edc2627ffcc25..ec9c8e78641ce6d18e487ce032eec3a329cdeede 100644
--- a/lib/gsql/query_test.go
+++ b/lib/gsql/query_test.go
@@ -28,6 +28,7 @@ func TestQuery(t *testing.T) {
 	ctx := backends.AcceptContext{
 		Conn: server,
 		Options: backends.AcceptOptions{
+			Username: "postgres",
 			Credentials: credentials.Cleartext{
 				Username: "postgres",
 				Password: "password",
diff --git a/test/tester_test.go b/test/tester_test.go
index 3555fa5c9066c487205dea478054a536a5bbf3b5..b0dcdfce9ffd0e477d78581da74b6ff69d97bffc 100644
--- a/test/tester_test.go
+++ b/test/tester_test.go
@@ -6,6 +6,9 @@ import (
 	"fmt"
 	"net"
 	_ "net/http/pprof"
+	"strconv"
+	"testing"
+
 	"pggat/lib/auth"
 	"pggat/lib/auth/credentials"
 	"pggat/lib/bouncer/backends/v0"
@@ -18,8 +21,6 @@ import (
 	"pggat/lib/gat/pool/recipe"
 	"pggat/test"
 	"pggat/test/tests"
-	"strconv"
-	"testing"
 )
 
 func daisyChain(creds auth.Credentials, control dialer.Net, n int) (dialer.Net, error) {
@@ -59,6 +60,7 @@ func daisyChain(creds auth.Credentials, control dialer.Net, n int) (dialer.Net,
 			Network: "tcp",
 			Address: ":" + strconv.Itoa(port),
 			AcceptOptions: backends.AcceptOptions{
+				Username:    "runner",
 				Credentials: creds,
 				Database:    "pool",
 			},
@@ -73,6 +75,7 @@ func TestTester(t *testing.T) {
 		Network: "tcp",
 		Address: "localhost:5432",
 		AcceptOptions: backends.AcceptOptions{
+			Username: "postgres",
 			Credentials: credentials.Cleartext{
 				Username: "postgres",
 				Password: "password",
@@ -137,6 +140,7 @@ func TestTester(t *testing.T) {
 		Network: "tcp",
 		Address: ":" + strconv.Itoa(port),
 		AcceptOptions: backends.AcceptOptions{
+			Username:    "runner",
 			Credentials: creds,
 			Database:    "transaction",
 		},
@@ -145,6 +149,7 @@ func TestTester(t *testing.T) {
 		Network: "tcp",
 		Address: ":" + strconv.Itoa(port),
 		AcceptOptions: backends.AcceptOptions{
+			Username:    "runner",
 			Credentials: creds,
 			Database:    "session",
 		},