diff --git a/auth/krb/go.mod b/auth/krb/go.mod new file mode 100644 index 0000000..9d62d17 --- /dev/null +++ b/auth/krb/go.mod @@ -0,0 +1,8 @@ +module github.com/lib/pq/auth/krb + +go 1.18 + +require ( + github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 + github.com/jcmturner/gokrb5/v8 v8.2.0 +) diff --git a/auth/krb/go.sum b/auth/krb/go.sum new file mode 100644 index 0000000..138e36f --- /dev/null +++ b/auth/krb/go.sum @@ -0,0 +1,40 @@ +github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 h1:P5U+E4x5OkVEKQDklVPmzs71WM56RTTRqV4OrDC//Y4= +github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= +github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= +github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.2.0 h1:lzPl/30ZLkTveYsYZPKMcgXc8MbnE6RsTd4F9KgiLtk= +github.com/jcmturner/gokrb5/v8 v8.2.0/go.mod h1:T1hnNppQsBtxW0tCHMHTkAt8n/sABdzZgZdoFrZaZNM= +github.com/jcmturner/rpc/v2 v2.0.2 h1:gMB4IwRXYsWw4Bc6o/az2HJgFUA1ffSh90i26ZJ6Xl0= +github.com/jcmturner/rpc/v2 v2.0.2/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200117160349-530e935923ad h1:Jh8cai0fqIK+f6nG0UgPW5wFk8wmiMhM3AyciDBdtQg= +golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/auth/krb/krb.go b/auth/krb/krb.go new file mode 100644 index 0000000..28cac27 --- /dev/null +++ b/auth/krb/krb.go @@ -0,0 +1,29 @@ +package krb + +import ( + "net" + "strings" +) + +/* + * Find the A record associated with a hostname + * In general, hostnames supplied to the driver should be + * canonicalized because the KDC usually only has one + * principal and not one per potential alias of a host. + */ +func canonicalizeHostname(host string) (string, error) { + canon := host + + name, err := net.LookupCNAME(host) + if err != nil { + return "", err + } + + name = strings.TrimSuffix(name, ".") + + if name != "" { + canon = name + } + + return canon, nil +} diff --git a/auth/krb/krb_unix.go b/auth/krb/krb_unix.go new file mode 100644 index 0000000..b5a0047 --- /dev/null +++ b/auth/krb/krb_unix.go @@ -0,0 +1,128 @@ +//go:build !windows +// +build !windows + +package krb + +import ( + "fmt" + "os" + "os/user" + "strings" + + "github.com/jcmturner/gokrb5/v8/client" + "github.com/jcmturner/gokrb5/v8/config" + "github.com/jcmturner/gokrb5/v8/credentials" + "github.com/jcmturner/gokrb5/v8/spnego" +) + +/* + * UNIX Kerberos support, using jcmturner's pure-go + * implementation + */ + +// GSS implements the pq.GSS interface. +type GSS struct { + cli *client.Client +} + +// NewGSS creates a new GSS provider. +func NewGSS() (*GSS, error) { + g := &GSS{} + err := g.init() + + if err != nil { + return nil, err + } + + return g, nil +} + +func (g *GSS) init() error { + cfgPath, ok := os.LookupEnv("KRB5_CONFIG") + if !ok { + cfgPath = "/etc/krb5.conf" + } + + cfg, err := config.Load(cfgPath) + if err != nil { + return err + } + + u, err := user.Current() + if err != nil { + return err + } + + ccpath := "/tmp/krb5cc_" + u.Uid + + ccname := os.Getenv("KRB5CCNAME") + if strings.HasPrefix(ccname, "FILE:") { + ccpath = strings.SplitN(ccname, ":", 2)[1] + } + + ccache, err := credentials.LoadCCache(ccpath) + if err != nil { + return err + } + + cl, err := client.NewFromCCache(ccache, cfg, client.DisablePAFXFAST(true)) + if err != nil { + return err + } + + cl.Login() + + g.cli = cl + + return nil +} + +// GetInitToken implements the GSS interface. +func (g *GSS) GetInitToken(host string, service string) ([]byte, error) { + + // Resolve the hostname down to an 'A' record, if required (usually, it is) + if g.cli.Config.LibDefaults.DNSCanonicalizeHostname { + var err error + host, err = canonicalizeHostname(host) + if err != nil { + return nil, err + } + } + + spn := service + "/" + host + + return g.GetInitTokenFromSPN(spn) +} + +// GetInitTokenFromSpn implements the GSS interface. +func (g *GSS) GetInitTokenFromSPN(spn string) ([]byte, error) { + s := spnego.SPNEGOClient(g.cli, spn) + + st, err := s.InitSecContext() + if err != nil { + return nil, fmt.Errorf("kerberos error (InitSecContext): %s", err.Error()) + } + + b, err := st.Marshal() + if err != nil { + return nil, fmt.Errorf("kerberos error (Marshaling token): %s", err.Error()) + } + + return b, nil +} + +// Continue implements the GSS interface. +func (g *GSS) Continue(inToken []byte) (done bool, outToken []byte, err error) { + t := &spnego.SPNEGOToken{} + err = t.Unmarshal(inToken) + if err != nil { + return true, nil, fmt.Errorf("kerberos error (Unmarshaling token): %s", err.Error()) + } + + state := t.NegTokenResp.State() + if state != spnego.NegStateAcceptCompleted { + return true, nil, fmt.Errorf("kerberos: expected state 'Completed' - got %d", state) + } + + return true, nil, nil +} diff --git a/auth/krb/krb_windows.go b/auth/krb/krb_windows.go new file mode 100644 index 0000000..3402df8 --- /dev/null +++ b/auth/krb/krb_windows.go @@ -0,0 +1,67 @@ +//go:build windows +// +build windows + +package krb + +import ( + "github.com/alexbrainman/sspi" + "github.com/alexbrainman/sspi/negotiate" +) + +// GSS implements the pq.GSS interface. +type GSS struct { + creds *sspi.Credentials + ctx *negotiate.ClientContext +} + +// NewGSS creates a new GSS provider. +func NewGSS() (*GSS, error) { + g := &GSS{} + err := g.init() + + if err != nil { + return nil, err + } + + return g, nil +} + +func (g *GSS) init() error { + creds, err := negotiate.AcquireCurrentUserCredentials() + if err != nil { + return err + } + + g.creds = creds + return nil +} + +// GetInitToken implements the GSS interface. +func (g *GSS) GetInitToken(host string, service string) ([]byte, error) { + + host, err := canonicalizeHostname(host) + if err != nil { + return nil, err + } + + spn := service + "/" + host + + return g.GetInitTokenFromSpn(spn) +} + +// GetInitTokenFromSpn implements the GSS interface. +func (g *GSS) GetInitTokenFromSpn(spn string) ([]byte, error) { + ctx, token, err := negotiate.NewClientContext(g.creds, spn) + if err != nil { + return nil, err + } + + g.ctx = ctx + + return token, nil +} + +// Continue implements the GSS interface. +func (g *GSS) Continue(inToken []byte) (done bool, outToken []byte, err error) { + return g.ctx.Update(inToken) +} diff --git a/config.go b/config.go index 5cee929..6e6930e 100644 --- a/config.go +++ b/config.go @@ -257,6 +257,8 @@ func ParseConfig(connString string) (*Config, error) { "sslkey": {}, "sslcert": {}, "sslrootcert": {}, + "krbspn": {}, + "krbsrvname": {}, "target_session_attrs": {}, "min_read_buffer_size": {}, "service": {}, diff --git a/go.mod b/go.mod index fb3ed18..8bdc2fa 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/jackc/pgconn -go 1.12 +go 1.18 require ( github.com/jackc/chunkreader/v2 v2.0.1 @@ -13,3 +13,11 @@ require ( golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 golang.org/x/text v0.3.7 ) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect +) + +replace github.com/jackc/pgproto3/v2 => github.com/otan-cockroach/pgproto3/v2 v2.2.1-0.20220411001252-6bb7be1c99b8 diff --git a/go.sum b/go.sum index bdb5ee8..c309baa 100644 --- a/go.sum +++ b/go.sum @@ -6,7 +6,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -24,15 +23,7 @@ github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5W github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= @@ -57,6 +48,8 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/otan-cockroach/pgproto3/v2 v2.2.1-0.20220411001252-6bb7be1c99b8 h1:dsO5Xc+8zuPFjqK0Ba0llL1pjt0DAsO5+lN2dCzk8bQ= +github.com/otan-cockroach/pgproto3/v2 v2.2.1-0.20220411001252-6bb7be1c99b8/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/pkg/errors v0.8.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= @@ -112,7 +105,6 @@ golang.org/x/text v0.3.0/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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= diff --git a/krb.go b/krb.go new file mode 100644 index 0000000..fa68544 --- /dev/null +++ b/krb.go @@ -0,0 +1,94 @@ +package pgconn + +import ( + "errors" + "github.com/jackc/pgproto3/v2" +) + +// NewGSSFunc creates a GSS authentication provider, for use with +// RegisterGSSProvider. +type NewGSSFunc func() (GSS, error) + +var newGSS NewGSSFunc + +// RegisterGSSProvider registers a GSS authentication provider. For example, if +// you need to use Kerberos to authenticate with your server, add this to your +// main package: +// +// import "github.com/jackc/pgconn/auth/krb" +// +// func init() { +// pgconn.RegisterGSSProvider(func() (pgconn.GSS, error) { return krb.NewGSS() }) +// } +func RegisterGSSProvider(newGSSArg NewGSSFunc) { + newGSS = newGSSArg +} + +// GSS provides GSSAPI authentication (e.g., Kerberos). +type GSS interface { + GetInitToken(host string, service string) ([]byte, error) + GetInitTokenFromSPN(spn string) ([]byte, error) + Continue(inToken []byte) (done bool, outToken []byte, err error) +} + +func (c *PgConn) gssAuth() error { + if newGSS == nil { + return errors.New("kerberos error: no GSSAPI provider registered") + } + cli, err := newGSS() + if err != nil { + return err + } + + var nextToken []byte + if spn, ok := c.config.RuntimeParams["krbspn"]; ok { + // Use the supplied SPN if provided. + nextToken, err = cli.GetInitTokenFromSPN(spn) + } else { + // Allow the kerberos service name to be overridden + service := "postgres" + if val, ok := c.config.RuntimeParams["krbsrvname"]; ok { + service = val + } + nextToken, err = cli.GetInitToken(c.config.Host, service) + } + if err != nil { + return err + } + + for { + gssResponse := &pgproto3.GSSResponse{ + Token: nextToken, + } + _, err = c.conn.Write(gssResponse.Encode(nil)) + if err != nil { + return err + } + resp, err := c.rxGSSContinue() + if err != nil { + return err + } + var done bool + done, nextToken, err = cli.Continue(resp.Token) + if err != nil { + return err + } + if done { + break + } + } + return nil +} + +func (c *PgConn) rxGSSContinue() (*pgproto3.AuthenticationGSSContinue, error) { + msg, err := c.receiveMessage() + if err != nil { + return nil, err + } + gssContinue, ok := msg.(*pgproto3.AuthenticationGSSContinue) + if ok { + return gssContinue, nil + } + + return nil, errors.New("expected AuthenticationGSSContinue message but received unexpected message") +} diff --git a/pgconn.go b/pgconn.go index 9a496ed..0d07ac5 100644 --- a/pgconn.go +++ b/pgconn.go @@ -320,7 +320,12 @@ func connect(ctx context.Context, config *Config, fallbackConfig *FallbackConfig pgConn.conn.Close() return nil, &connectError{config: config, msg: "failed SASL auth", err: err} } - + case *pgproto3.AuthenticationGSS: + err = pgConn.gssAuth() + if err != nil { + pgConn.conn.Close() + return nil, &connectError{config: config, msg: "failed GSS auth", err: err} + } case *pgproto3.ReadyForQuery: pgConn.status = connStatusIdle if config.ValidateConnect != nil {