Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Kerberos auth support #702

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1854c5c
add krb auth support
Nov 29, 2021
f2e846d
worked on review comments
chandanjainn Nov 30, 2021
b46e98e
Rename kerberos variables.
chandanjainn Nov 30, 2021
1f85924
Removed leading space
chandanjainn Dec 1, 2021
2fe0bca
unexport initiator states
chandanjainn Dec 6, 2021
d361509
removed the global vars in kerbauth.go
chandanjainn Dec 7, 2021
86e0074
migrated gokrb from v7 to v8.
chandanjainn Dec 8, 2021
eca4758
worked on error message
chandanjainn Dec 9, 2021
c47a35b
worked on error messages.
chandanjainn Dec 13, 2021
27f50f1
updated readme.
chandanjainn Dec 20, 2021
f792281
Merge branch 'master' into kerberos_auth
chandanjainn Jan 19, 2022
ca67c06
Wroked on review comments
chandanjainn Jan 20, 2022
6e619be
fixed the changes for readme.
chandanjainn Jan 20, 2022
d4f52ce
fix: whitespace
Jan 20, 2022
530eb45
worked on review comments
chandanjainn Jan 20, 2022
85c5bb1
fix for unit testing
chandanjainn Jan 20, 2022
04c18be
Merge branch 'master' into kerberos_auth
chandanjainn Jan 24, 2022
8059af5
renamed kerberos config variable
chandanjainn Jan 24, 2022
79d6641
Merge branch 'master' into kerberos_auth
chandanjainn Jan 24, 2022
b4e96e3
nil pointer fix
chandanjainn Jan 25, 2022
729e190
Merge branch 'kerberos_auth' of github.com:chandanjainn/go-mssqldb in…
chandanjainn Jan 25, 2022
dc1a816
removed commented code
chandanjainn Feb 8, 2022
73ae20f
removed unused field
chandanjainn Feb 8, 2022
bf01def
code formatting
chandanjainn Feb 10, 2022
dd22d87
Merge branch 'master' into kerberos_auth
chandanjainn Jun 13, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
251 changes: 134 additions & 117 deletions README.md

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion go.mod
Expand Up @@ -4,5 +4,7 @@ go 1.11

require (
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c
github.com/jcmturner/gokrb5/v8 v8.4.2
github.com/stretchr/testify v1.7.0 // indirect
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9
)
42 changes: 40 additions & 2 deletions go.sum
@@ -1,5 +1,43 @@
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/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
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.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/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.4.2 h1:6ZIM6b/JJN0X8UM43ZOM6Z4SJzla+a/u7scXFJzodkA=
github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/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/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 h1:umElSU9WZirRdgu2yFHY0ayQkEnKiOC1TtM3fWXFnoU=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
129 changes: 129 additions & 0 deletions kerbauth.go
@@ -0,0 +1,129 @@
package mssql
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree 1.13 is probably a reasonable minimum requirement at this point.
That said - it may be worthwhile to decouple specific auth implementations like this one from msdsn in general and instead have each auth implementation inject itself into a discovery mechanism such as with some visitor pattern on the connection properties. I'm new enough to Go not to know the best way to have such dependency injection.
With such dependency injection you could mark the kerbauth files for 1.13+ only.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How could this PR align with #718 ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shueybubbles with 718, anyone using go-mssql driver for kerberos will have to implement their own custom logic and/or maintain their own repos to handle the entire kerberos authN process.
Moreover my PR provides with flexibility to use either krb cache or keytab file which I believe is missing from 718?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My other comment re: coupling the config to the specific krb implementation is my main concern. The other PR at least avoids such coupling. I think there should be a way for the backend of the driver to manage krb more transparently than having the client use krb-specific packages to even define the connection.


import (
"fmt"
"strconv"
"strings"

"github.com/jcmturner/gokrb5/v8/client"
"github.com/jcmturner/gokrb5/v8/config"
"github.com/jcmturner/gokrb5/v8/credentials"
"github.com/jcmturner/gokrb5/v8/keytab"
"github.com/jcmturner/gokrb5/v8/spnego"
)

type krb5Auth struct {
username string
realm string
serverSPN string
password string
port uint64
krb5Config *config.Config
krbKeytab *keytab.Keytab
krbCache *credentials.CCache
initkrbwithkeytab bool
krb5Client *client.Client
state krb5ClientState
}

func getKRB5Auth(user, password, serverSPN string, krb5Conf *config.Config, keytabContent *keytab.Keytab, cacheContent *credentials.CCache, initkrbwithkeytab bool) (auth, bool) {
var port uint64
var realm, serviceStr string
var err error

params1 := strings.Split(serverSPN, ":")
if len(params1) != 2 {
return nil, false
}

params2 := strings.Split(params1[1], "@")
switch len(params2) {
case 1:
port, err = strconv.ParseUint(params1[1], 10, 16)
if err != nil {
return nil, false
}
case 2:
port, err = strconv.ParseUint(params2[0], 10, 16)
if err != nil {
return nil, false
}
default:
return nil, false
}

params3 := strings.Split(serverSPN, "@")
switch len(params3) {
case 1:
serviceStr = params3[0]
params3 = strings.Split(params1[0], "/")
params3 = strings.Split(params3[1], ".")
realm = params3[1] + "." + params3[2]
case 2:
realm = params3[1]
serviceStr = params3[0]
default:
return nil, false
}

return &krb5Auth{
username: user,
serverSPN: serviceStr,
port: port,
realm: realm,
krb5Config: krb5Conf,
krbKeytab: keytabContent,
krbCache: cacheContent,
password: password,
initkrbwithkeytab: initkrbwithkeytab,
}, true
}

func (auth *krb5Auth) InitialBytes() ([]byte, error) {
// Set to lookup KDCs in DNS
auth.krb5Config.LibDefaults.DNSLookupKDC = false
chandanjainn marked this conversation as resolved.
Show resolved Hide resolved
var cl *client.Client
var err error
// Init keytab from conf
if auth.initkrbwithkeytab {
// Init krb5 client and login
cl = client.NewWithKeytab(auth.username, auth.realm, auth.krbKeytab, auth.krb5Config, client.DisablePAFXFAST(true))
} else {
cl, err = client.NewFromCCache(auth.krbCache, auth.krb5Config)
if err != nil {
return []byte{}, err
}
}
auth.krb5Client = cl
auth.state = initiatorStart
tkt, sessionKey, err := cl.GetServiceTicket(auth.serverSPN)
if err != nil {
return []byte{}, err
}

negTok, err := spnego.NewNegTokenInitKRB5(auth.krb5Client, tkt, sessionKey)
if err != nil {
return []byte{}, err
}

outToken, err := negTok.Marshal()
if err != nil {
return []byte{}, err
}
auth.state = initiatorWaitForMutal
return outToken, nil
}

func (auth *krb5Auth) Free() {
auth.krb5Client.Destroy()
}

func (auth *krb5Auth) NextBytes(token []byte) ([]byte, error) {
var spnegoToken spnego.SPNEGOToken
if err := spnegoToken.Unmarshal(token); err != nil {
err := fmt.Errorf("unmarshal APRep token failed: %w", err)
return []byte{}, err
}
auth.state = initiatorReady
return []byte{}, nil
}
162 changes: 162 additions & 0 deletions kerbauth_test.go
@@ -0,0 +1,162 @@
package mssql

import (
"reflect"
"testing"

"github.com/jcmturner/gokrb5/v8/client"
"github.com/jcmturner/gokrb5/v8/config"
"github.com/jcmturner/gokrb5/v8/credentials"
"github.com/jcmturner/gokrb5/v8/keytab"
)

func TestGetKRB5Auth(t *testing.T) {
krbConf := &config.Config{}
krbKeytab := &keytab.Keytab{}
krbCache := &credentials.CCache{}

got, _ := getKRB5Auth("", "", "MSSQLSvc/mssql.domain.com:1433", krbConf, krbKeytab, krbCache, true)
keytab := &krb5Auth{username: "",
realm: "domain.com",
serverSPN: "MSSQLSvc/mssql.domain.com:1433",
password: "",
port: 1433,
krb5Config: krbConf,
krbKeytab: krbKeytab,
krbCache: krbCache,
initkrbwithkeytab: true,
state: 0}

res := reflect.DeepEqual(got, keytab)
if !res {
t.Errorf("Failed to get correct krb5Auth object\nExpected:%v\nRecieved:%v", keytab, got)
}

got, _ = getKRB5Auth("", "", "MSSQLSvc/mssql.domain.com:1433", krbConf, krbKeytab, krbCache, false)
keytab = &krb5Auth{username: "",
realm: "domain.com",
serverSPN: "MSSQLSvc/mssql.domain.com:1433",
password: "",
port: 1433,
krb5Config: krbConf,
krbKeytab: krbKeytab,
krbCache: krbCache,
initkrbwithkeytab: false,
state: 0}

res = reflect.DeepEqual(got, keytab)
if !res {
t.Errorf("Failed to get correct krb5Auth object\nExpected:%v\nRecieved:%v", keytab, got)
}

_, val := getKRB5Auth("", "", "MSSQLSvc/mssql.domain.com", krbConf, krbKeytab, krbCache, true)
if val {
t.Errorf("Failed to get correct krb5Auth object: no port defined")
}

got, _ = getKRB5Auth("", "", "MSSQLSvc/mssql.domain.com:1433@DOMAIN.COM", krbConf, krbKeytab, krbCache, true)
keytab = &krb5Auth{username: "",
realm: "DOMAIN.COM",
serverSPN: "MSSQLSvc/mssql.domain.com:1433",
password: "",
port: 1433,
krb5Config: krbConf,
krbKeytab: krbKeytab,
krbCache: krbCache,
initkrbwithkeytab: true,
state: 0}

res = reflect.DeepEqual(got, keytab)
if !res {
t.Errorf("Failed to get correct krb5Auth object\nExpected:%v\nRecieved:%v", keytab, got)
}

_, val = getKRB5Auth("", "", "MSSQLSvc/mssql.domain.com:1433@domain.com@test", krbConf, krbKeytab, krbCache, true)
if val {
t.Errorf("Failed to get correct krb5Auth object due to incorrect serverSPN name")
}

_, val = getKRB5Auth("", "", "MSSQLSvc/mssql.domain.com:port@domain.com", krbConf, krbKeytab, krbCache, true)
if val {
t.Errorf("Failed to get correct krb5Auth object due to incorrect port")
}

_, val = getKRB5Auth("", "", "MSSQLSvc/mssql.domain.com:port", krbConf, krbKeytab, krbCache, true)
if val {
t.Errorf("Failed to get correct krb5Auth object due to incorrect port")
}
}

func TestInitialBytes(t *testing.T) {
krbConf := &config.Config{}
krbKeytab := &keytab.Keytab{}
krbCache := &credentials.CCache{}
krbObj := &krb5Auth{username: "",
realm: "domain.com",
serverSPN: "MSSQLSvc/mssql.domain.com:1433",
password: "",
port: 1433,
krb5Config: krbConf,
krbKeytab: krbKeytab,
krbCache: krbCache,
initkrbwithkeytab: false,
state: 0,
}

_, err := krbObj.InitialBytes()
if err == nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the test is expecting a nil err please change the t.ErrorF messages to indicate that. Otherwise the code reads like err == nil should be changed to err != nil

t.Errorf("Failed to get Initial bytes")
}

krbObj.initkrbwithkeytab = true
_, err = krbObj.InitialBytes()
if err == nil {
t.Errorf("Failed to get Initial bytes")
}
}

func TestNextBytes(t *testing.T) {
ans := []byte{}
krbConf := &config.Config{}
krbKeytab := &keytab.Keytab{}
krbCache := &credentials.CCache{}

var krbObj auth = &krb5Auth{username: "",
realm: "domain.com",
serverSPN: "MSSQLSvc/mssql.domain.com:1433",
password: "",
port: 1433,
krb5Config: krbConf,
krbKeytab: krbKeytab,
krbCache: krbCache,
initkrbwithkeytab: true,
state: 0}

_, err := krbObj.NextBytes(ans)
if err == nil {
t.Errorf("Error getting next byte")
}
}

func TestFree(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a test should have at least one assertion

krbConf := &config.Config{}
krbKeytab := &keytab.Keytab{}
krbCache := &credentials.CCache{}
kt := &keytab.Keytab{}
c := &config.Config{}
cl := client.NewWithKeytab("Administrator", "DOMAIN.COM", kt, c, client.DisablePAFXFAST(true))

var krbObj auth = &krb5Auth{username: "",
realm: "domain.com",
serverSPN: "MSSQLSvc/mssql.domain.com:1433",
password: "",
port: 1433,
krb5Config: krbConf,
krbKeytab: krbKeytab,
krbCache: krbCache,
initkrbwithkeytab: true,
state: 0,
krb5Client: cl,
}
krbObj.Free()
}