Repository: microsoft/go-mssqldb
Pull request #65 (2022-11-07 16:48:28)

krb5 authentication provider
Implementation of krb5 authentication in linux and windows using the github.com/jcmturner/gokrb5/v8/ package.
Fixes the issues in the previous submitted implementation.

codecov-commenter: # [Codecov](https://codecov.io/gh/microsoft/go-mssqldb/pull/65?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft) Report
> Merging [#65](https://codecov.io/gh/microsoft/go-mssqldb/pull/65?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft) (a7ea017) into [main](https://codecov.io/gh/microsoft/go-mssqldb/commit/537893aca44093e144c21d846cc51b6a4aa6e909?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft) (537893a) will **decrease** coverage by `0.08%`.
> The diff coverage is `n/a`.

```diff
@@            Coverage Diff             @@
##             main      #65      +/-   ##
==========================================
- Coverage   73.25%   73.16%   -0.09%     
==========================================
  Files          25       23       -2     
  Lines        5458     5445      -13     
==========================================
- Hits         3998     3984      -14     
- Misses       1224     1226       +2     
+ Partials      236      235       -1     
```


| [Impacted Files](https://codecov.io/gh/microsoft/go-mssqldb/pull/65?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft) | Coverage Δ | |
|---|---|---|
| [mssql.go](https://codecov.io/gh/microsoft/go-mssqldb/pull/65/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft#diff-bXNzcWwuZ28=) | `85.78% <0.00%> (-0.47%)` | :arrow_down: |
| [tds.go](https://codecov.io/gh/microsoft/go-mssqldb/pull/65/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft#diff-dGRzLmdv) | `68.08% <0.00%> (-0.13%)` | :arrow_down: |
| [mssql\_go118.go](https://codecov.io/gh/microsoft/go-mssqldb/pull/65/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft#diff-bXNzcWxfZ28xMTguZ28=) | | |
| [ucs22str\_32bit.go](https://codecov.io/gh/microsoft/go-mssqldb/pull/65/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft#diff-dWNzMjJzdHJfMzJiaXQuZ28=) | | |
| [token.go](https://codecov.io/gh/microsoft/go-mssqldb/pull/65/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft#diff-dG9rZW4uZ28=) | `64.87% <0.00%> (+0.58%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft)

LessTravelledWay: @shueybubbles - What is stopping this being merged ? Has been sitting for review for a while now, would be good to get some feedback on this.
shueybubbles: > @shueybubbles - What is stopping this being merged ? Has been sitting for review for a while now, would be good to get some feedback on this.

@LessTravelledWay I'd love to get one or two more people with krb5 experience (I am not one) to sign off on this PR . To my eye Pete's change looks like a pretty extensive rewrite of Chandan's change. Are the developers who plan to consume this change ok with it?

shueybubbles: thx for the update, is the changelog update the last commit? if so I will merge

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a0aa649..dfd2405 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,18 @@
 ### Bug fixes
 
 * Fixed uninitialized server name in TLS config ([#93](https://github.com/microsoft/go-mssqldb/issues/93))([#94](https://github.com/microsoft/go-mssqldb/pull/94))
+* Fixed several kerberos authentication usages on Linux with new krb5 authentication provider. 
+
+### Changed
+
+* New kerberos authenticator implementation uses more explicit connection string parameters.
+
+| Old          | New                |
+|--------------|--------------------|
+| krb5conffile | krb5-configfile    |
+| krbcache     | krb5-credcachefile |
+| keytabfile   | krb5-keytabfile    |
+| realm        | krb5-realm         |
 
 ## 0.20.0
 
diff --git a/README.md b/README.md
index 4349792..446d58b 100644
--- a/README.md
+++ b/README.md
@@ -81,28 +81,50 @@ To force a specific protocol for the connection there two several options:
 `msdsn.ProtocolParsers` can be reordered to prioritize other protocols ahead of `tcp`
 
 ### Kerberos Active Directory authentication outside Windows
+
+To connect with kerberos authentication from a Linux server you can use the optional krb5 package. 
+Imported krb alongside the main driver 
+```
+package main
+
+import (
+    ...
+    _ "github.com/microsoft/go-mssqldb"
+    _ "github.com/microsoft/go-mssqldb/integratedauth/krb5"
+)
+
+func main() {
+    ...
+}
+```
+
+It will become available for use when the connection string parameter "authenticator=krb5" is used.
+
 The package supports authentication via 3 methods.
 
 * Keytabs - Specify the username, keytab file, the krb5.conf file, and realm.
 
-      authenticator=krb5;server=DatabaseServerName;database=DBName;user id=MyUserName;realm=domain.com;krb5conffile=/etc/krb5.conf;keytabfile=~/MyUserName.keytab
+      authenticator=krb5;server=DatabaseServerName;database=DBName;user id=MyUserName;krb5-realm=domain.com;krb5-configfile=/etc/krb5.conf;krb5-keytabfile=~/MyUserName.keytab
 
 * Credential Cache - Specify the krb5.conf file path and credential cache file path.
 
-      authenticator=krb5;server=DatabaseServerName;database=DBName;krb5conffile=/etc/krb5.conf;krbcache=~/MyUserNameCachedCreds
+      authenticator=krb5;server=DatabaseServerName;database=DBName;krb5-configfile=/etc/krb5.conf;krb5-credcachefile=~/MyUserNameCachedCreds 
 
 * Raw credentials - Specity krb5.confg, Username, Password and Realm.
 
-      authenticator=krb5;server=DatabaseServerName;database=DBName;user id=MyUserName;password=foo;realm=comani.com;krb5conffile=/etc/krb5.conf;
+      authenticator=krb5;server=DatabaseServerName;database=DBName;user id=MyUserName;password=foo;krb5-realm=comani.com;krb5-configfile=/etc/krb5.conf;
 
 ### Kerberos Parameters
 
 * `authenticator` - set this to `krb5` to enable kerberos authentication. If this is not present, the default provider would be `ntlm` for unix and `winsspi` for windows.
-* `krb5conffile` (mandatory) - path to kerberos configuration file. 
-* `realm` (required with keytab and raw credentials) - Domain name for kerberos authentication. 
-* `keytabfile` - path to Keytab file.
-* `krbcache` - path to Credential cache.
-* For further information on usage: 
+* `krb5-configfile` (mandatory) - path to kerberos configuration file. 
+* `krb5-realm` (required with keytab and raw credentials) - Domain name for kerberos authentication. 
+* `krb5-keytabfile` - path to Keytab file.
+* `krb5-credcachefile` - path to Credential cache.
+* `krb5-dnslookupkdc` - Optional parameter in all contexts. Set to lookup KDCs in DNS. Boolean. Default is true. 
+* `krb5-udppreferencelimit` - Optional parameter in all contexts. 1 means to always use tcp. MIT krb5 has a default value of 1465, and it prevents user setting more than 32700. Integer. Default is 1.
+  
+For further information on usage: 
   * <https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html>
   * <https://web.mit.edu/kerberos/krb5-1.12/doc/basic/index.html>
 
@@ -135,16 +157,16 @@ The package supports authentication via 3 methods.
 
     ```
 
-* `sqlserver://username@host/instance?krb5conffile=path/to/file&krbcache=/path/to/cache`
-    * `sqlserver://username@host/instance?krb5conffile=path/to/file&realm=domain.com&keytabfile=/path/to/keytabfile`
+* `sqlserver://username@host/instance?krb5-configfile=path/to/file&krb5-credcachefile=/path/to/cache`
+    * `sqlserver://username@host/instance?krb5-configfile=path/to/file&krb5-realm=domain.com&krb5-keytabfile=/path/to/keytabfile`
 
 2. ADO: `key=value` pairs separated by `;`. Values may not contain `;`, leading and trailing whitespace is ignored.
      Examples:
 
     * `server=localhost\\SQLExpress;user id=sa;database=master;app name=MyAppName`
     * `server=localhost;user id=sa;database=master;app name=MyAppName`
-    * `server=localhost;user id=sa;database=master;app name=MyAppName;krb5conffile=path/to/file;krbcache=path/to/cache;authenticator=krb5`
-    * `server=localhost;user id=sa;database=master;app name=MyAppName;krb5conffile=path/to/file;realm=domain.com;keytabfile=path/to/keytabfile;authenticator=krb5`
+    * `server=localhost;user id=sa;database=master;app name=MyAppName;krb5-configfile=path/to/file;krb5-credcachefile=path/to/cache;authenticator=krb5`
+    * `server=localhost;user id=sa;database=master;app name=MyAppName;krb5-configfile=path/to/file;krb5-realm=domain.com;krb5-keytabfile=path/to/keytabfile;authenticator=krb5`
 
 
     ADO strings support synonyms for database, app name, user id, and server
@@ -165,8 +187,8 @@ The package supports authentication via 3 methods.
     * `odbc:server=localhost;user id=sa;password=foo}bar`   // Literal `}`, password is "foo}bar"
     * `odbc:server=localhost;user id=sa;password={foo{bar}` // Literal `{`, password is "foo{bar"
     * `odbc:server=localhost;user id=sa;password={foo}}bar}` // Escaped `} with`}}`, password is "foo}bar"
-    * `odbc:server=localhost;user id=sa;database=master;app name=MyAppName;krb5conffile=path/to/file;krbcache=path/to/cache;authenticator=krb5`
-    * `odbc:server=localhost;user id=sa;database=master;app name=MyAppName;krb5conffile=path/to/file;realm=domain.com;keytabfile=path/to/keytabfile;authenticator=krb5`
+    * `odbc:server=localhost;user id=sa;database=master;app name=MyAppName;krb5-configfile=path/to/file;krb5-credcachefile=path/to/cache;authenticator=krb5`
+    * `odbc:server=localhost;user id=sa;database=master;app name=MyAppName;krb5-configfile=path/to/file;krb5-realm=domain.com;krb5-keytabfile=path/to/keytabfile;authenticator=krb5`
 
 ### Azure Active Directory authentication
 
diff --git a/integratedauth/krb5/krb5.go b/integratedauth/krb5/krb5.go
index 6d96871..3f443ec 100644
--- a/integratedauth/krb5/krb5.go
+++ b/integratedauth/krb5/krb5.go
@@ -1,12 +1,11 @@
-//go:build !windows && go1.13
-// +build !windows,go1.13
-
+// Package krb5 implements the integratedauth.IntegratedAuthenticator interface in order to provide kerberos/active directory (Windows) based authentication.
 package krb5
 
 import (
 	"errors"
 	"fmt"
 	"io/ioutil"
+	"net"
 	"os"
 	"strconv"
 	"strings"
@@ -14,58 +13,39 @@ import (
 	"github.com/jcmturner/gokrb5/v8/client"
 	"github.com/jcmturner/gokrb5/v8/config"
 	"github.com/jcmturner/gokrb5/v8/credentials"
+	"github.com/jcmturner/gokrb5/v8/gssapi"
 	"github.com/jcmturner/gokrb5/v8/keytab"
 	"github.com/jcmturner/gokrb5/v8/spnego"
+
 	"github.com/microsoft/go-mssqldb/integratedauth"
 	"github.com/microsoft/go-mssqldb/msdsn"
 )
 
-var (
-	SetKrbConfig = setupKerbConfig
-	SetKrbKeytab = setupKerbKeytab
-	SetKrbCache  = setupKerbCache
+const (
+	keytabConfigFile   = "krb5-configfile"
+	keytabFile         = "krb5-keytabfile"
+	credCacheFile      = "krb5-credcachefile"
+	realm              = "krb5-realm"
+	dnsLookupKDC       = "krb5-dnslookupkdc"
+	udpPreferenceLimit = "krb5-udppreferencelimit"
 )
 
-// Kerberos Client State
-type krb5ClientState int
-
-type krb5Auth struct {
-	username   string
-	password   string
-	realm      string
-	serverSPN  string
-	port       uint64
-	krb5Config *config.Config
-	krbKeytab  *keytab.Keytab
-	krbCache   *credentials.CCache
-	krb5Client *client.Client
-	state      krb5ClientState
-}
-
-type Kerberos struct {
-	// Kerberos configuration details
-	Config *config.Config
-
-	// Credential cache
-	Cache *credentials.CCache
-
-	// A Kerberos realm is the domain over which a Kerberos authentication server has the authority
-	// to authenticate a user, host or service.
-	Realm string
-
-	// Kerberos keytab that stores long-term keys for one or more principals
-	Keytab *keytab.Keytab
-}
-
-const (
-	// Initiator states
-	initiatorStart        krb5ClientState = iota
-	initiatorWaitForMutal                 = iota + 2
-	initiatorReady
+var (
+	ErrRequiredParametersMissing                     = errors.New("failed to create krb5 client from login parameters")
+	ErrRealmRequiredWithUsernameAndPassword          = errors.New("krb5-realm is required to login with krb5 when using user id and password")
+	ErrKrb5ConfigFileRequiredWithUsernameAndPassword = errors.New("krb5-configfile is required to login with krb5 when using user id and password")
+	ErrUsernameRequiredWithKeytab                    = errors.New("user id is required to login with krb5 when using krb5-keytabfile")
+	ErrRealmRequiredWithKeytab                       = errors.New("krb5-realm is required to login with krb5 when using krb5-keytabfile")
+	ErrKrb5ConfigFileRequiredWithKeytab              = errors.New("krb5-configfile is required to login with krb5 when using krb5-keytabfile")
+	ErrKrb5ConfigFileDoesNotExist                    = errors.New("krb5-configfile does not exist")
+	ErrKeytabFileDoesNotExist                        = errors.New("krb5-keytabfile does not exist")
+	ErrKrb5ConfigFileRequiredWithCredCache           = errors.New("krb5-configfile is required to login with krb5 when using krb5-credcachefile")
+	ErrCredCacheFileDoesNotExist                     = errors.New("krb5-credcachefile does not exist")
 )
 
 var (
-	_                integratedauth.IntegratedAuthenticator = (*krb5Auth)(nil)
+	_                integratedauth.IntegratedAuthenticator = (*krbAuth)(nil)
+	fileExists                                              = fileExistsOS
 	AuthProviderFunc integratedauth.Provider                = integratedauth.ProviderFunc(getAuth)
 )
 
@@ -77,192 +57,279 @@ func init() {
 }
 
 func getAuth(config msdsn.Config) (integratedauth.IntegratedAuthenticator, error) {
-	var port uint64
-	var realm, serviceStr string
-	var err error
+	krb5Config, err := readKrb5Config(config)
+	if err != nil {
+		return nil, err
+	}
 
-	krb, err := readKrb5Config(config)
+	err = validateKrb5LoginParams(krb5Config)
 	if err != nil {
-		return &krb5Auth{}, err
+		return nil, err
 	}
-	params1 := strings.Split(config.ServerSPN, ":")
-	if len(params1) != 2 {
-		return nil, errors.New("invalid ServerSPN")
+
+	return &krbAuth{
+		krb5Config: krb5Config,
+	}, nil
+}
+
+type loginMethod uint8
+
+const (
+	none loginMethod = iota
+	usernameAndPassword
+	keyTabFile
+	cachedCredentialsFile
+)
+
+type krb5Login struct {
+	Krb5ConfigFile     string
+	KeytabFile         string
+	CredCacheFile      string
+	Realm              string
+	UserName           string
+	Password           string
+	ServerSPN          string
+	DNSLookupKDC       bool
+	UDPPreferenceLimit int
+	loginMethod        loginMethod
+}
+
+// copies string parameters from connection string, parses optional parameters
+func readKrb5Config(config msdsn.Config) (*krb5Login, error) {
+	login := &krb5Login{
+		Krb5ConfigFile:     config.Parameters[keytabConfigFile],
+		KeytabFile:         config.Parameters[keytabFile],
+		CredCacheFile:      config.Parameters[credCacheFile],
+		Realm:              config.Parameters[realm],
+		UserName:           config.User,
+		Password:           config.Password,
+		ServerSPN:          config.ServerSPN,
+		DNSLookupKDC:       true,
+		UDPPreferenceLimit: 1,
+		loginMethod:        none,
 	}
 
-	params2 := strings.Split(params1[1], "@")
-	switch len(params2) {
-	case 1:
-		port, err = strconv.ParseUint(params1[1], 10, 16)
+	// read optional parameters
+	val, ok := config.Parameters[dnsLookupKDC]
+	if ok {
+		parsed, err := strconv.ParseBool(val)
 		if err != nil {
-			return nil, err
+			return nil, fmt.Errorf("invalid '%s' parameter '%s': %s", dnsLookupKDC, val, err.Error())
 		}
-	case 2:
-		port, err = strconv.ParseUint(params2[0], 10, 16)
+		login.DNSLookupKDC = parsed
+	}
+
+	val, ok = config.Parameters[udpPreferenceLimit]
+	if ok {
+		parsed, err := strconv.Atoi(val)
 		if err != nil {
-			return nil, err
+			return nil, fmt.Errorf("invalid '%s' parameter '%s': %s", udpPreferenceLimit, val, err.Error())
 		}
-	default:
-		return nil, errors.New("invalid ServerSPN")
+		login.UDPPreferenceLimit = parsed
 	}
 
-	params3 := strings.Split(config.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]
+	return login, nil
+}
+
+func validateKrb5LoginParams(krbLoginParams *krb5Login) error {
+	switch {
+	// using explicit credentials
+	case krbLoginParams.UserName != "" && krbLoginParams.Password != "":
+		if krbLoginParams.Realm == "" {
+			return ErrRealmRequiredWithUsernameAndPassword
+		}
+		if krbLoginParams.Krb5ConfigFile == "" {
+			return ErrKrb5ConfigFileRequiredWithUsernameAndPassword
+		}
+		if ok, err := fileExists(krbLoginParams.Krb5ConfigFile, ErrKrb5ConfigFileDoesNotExist); !ok {
+			return err
+		}
+		krbLoginParams.loginMethod = usernameAndPassword
+		return nil
+
+	//using a keytab file
+	case krbLoginParams.KeytabFile != "":
+		if krbLoginParams.UserName == "" {
+			return ErrUsernameRequiredWithKeytab
+		}
+		if krbLoginParams.Realm == "" {
+			return ErrRealmRequiredWithKeytab
+		}
+		if krbLoginParams.Krb5ConfigFile == "" {
+			return ErrKrb5ConfigFileRequiredWithKeytab
+		}
+		if ok, err := fileExists(krbLoginParams.Krb5ConfigFile, ErrKrb5ConfigFileDoesNotExist); !ok {
+			return err
+		}
+		if ok, err := fileExists(krbLoginParams.KeytabFile, ErrKeytabFileDoesNotExist); !ok {
+			return err
+		}
+		krbLoginParams.loginMethod = keyTabFile
+		return nil
+
+	// using a credential cache file
+	case krbLoginParams.CredCacheFile != "":
+		if krbLoginParams.Krb5ConfigFile == "" {
+			return ErrKrb5ConfigFileRequiredWithCredCache
+		}
+		if ok, err := fileExists(krbLoginParams.Krb5ConfigFile, ErrKrb5ConfigFileDoesNotExist); !ok {
+			return err
+		}
+		if ok, err := fileExists(krbLoginParams.CredCacheFile, ErrCredCacheFileDoesNotExist); !ok {
+			return err
+		}
+		krbLoginParams.loginMethod = cachedCredentialsFile
+		return nil
 	default:
-		return nil, errors.New("invalid ServerSPN")
+		return ErrRequiredParametersMissing
 	}
-
-	return &krb5Auth{
-		username:   config.User,
-		password:   config.Password,
-		serverSPN:  serviceStr,
-		port:       port,
-		realm:      realm,
-		krb5Config: krb.Config,
-		krbKeytab:  krb.Keytab,
-		krbCache:   krb.Cache,
-	}, nil
 }
 
-func (auth *krb5Auth) InitialBytes() ([]byte, error) {
-	var cl *client.Client
-	var err error
-	// Init keytab from conf
-	if auth.username != "" && auth.password != "" {
-		cl = client.NewWithPassword(auth.username, auth.realm, auth.password, auth.krb5Config)
-	} else if auth.krbKeytab != nil {
-		// 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
-		}
+func fileExistsOS(filename string, errWhenFileNotFound error) (bool, error) {
+	_, err := os.Stat(filename)
+	if err == nil {
+		return true, nil
 	}
-	auth.krb5Client = cl
-	auth.state = initiatorStart
-	tkt, sessionKey, err := cl.GetServiceTicket(auth.serverSPN)
-	if err != nil {
-		return []byte{}, err
+	if errors.Is(err, os.ErrNotExist) {
+		return false, errWhenFileNotFound
 	}
+	return false, fmt.Errorf("%v : %w", errWhenFileNotFound, err)
+}
 
-	negTok, err := spnego.NewNegTokenInitKRB5(auth.krb5Client, tkt, sessionKey)
+// krbAuth implements the integratedauth.IntegratedAuthenticator interface. It is responsible for kerberos Service Provider Negotiation.
+type krbAuth struct {
+	krb5Config   *krb5Login
+	spnegoClient *spnego.SPNEGO
+	krb5Client   *client.Client
+}
+
+func (k *krbAuth) InitialBytes() ([]byte, error) {
+	krbClient, err := getKrb5Client(k.krb5Config)
 	if err != nil {
-		return []byte{}, err
+		return nil, err
 	}
 
-	outToken, err := negTok.Marshal()
+	err = krbClient.Login()
 	if err != nil {
-		return []byte{}, err
+		return nil, err
 	}
-	auth.state = initiatorWaitForMutal
-	return outToken, nil
-}
 
-func (auth *krb5Auth) Free() {
-	auth.krb5Client.Destroy()
-}
+	k.krb5Client = krbClient
+	k.spnegoClient = spnego.SPNEGOClient(k.krb5Client, canonicalize(k.krb5Config.ServerSPN))
 
-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
+	tkn, err := k.spnegoClient.InitSecContext()
+	if err != nil {
+		return nil, err
 	}
-	auth.state = initiatorReady
-	return []byte{}, nil
+	return tkn.Marshal()
 }
 
-func readKrb5Config(config msdsn.Config) (Kerberos, error) {
-	krb := Kerberos{}
-	var err error
-
-	krbConfig, ok := config.Parameters["krb5conffile"]
-	if !ok {
-		return krb, fmt.Errorf("krb5 config file is required")
+func (k *krbAuth) NextBytes(bytes []byte) ([]byte, error) {
+	var resp spnego.SPNEGOToken
+	if err := resp.Unmarshal(bytes); err != nil {
+		return nil, err
 	}
 
-	krb.Config, err = SetKrbConfig(krbConfig)
-	if err != nil {
-		return krb, err
+	ok, status := resp.Verify()
+	if ok { // we're ok, done
+		return nil, nil
 	}
 
-	missingParam := validateKerbConfig(config.Parameters)
-	if missingParam != "" {
-		return krb, fmt.Errorf("missing parameter:%s", missingParam)
+	switch status.Code {
+	case gssapi.StatusContinueNeeded:
+		return nil, nil
+	default:
+		return nil, fmt.Errorf("bad status: %+v", status)
 	}
+}
 
-	if realm, ok := config.Parameters["realm"]; ok {
-		krb.Realm = realm
+func (k *krbAuth) Free() {
+	if k.krb5Client != nil {
+		k.krb5Client.Destroy()
+		k.krb5Client = nil
 	}
+}
 
-	if krbCache, ok := config.Parameters["krbcache"]; ok {
-		krb.Cache, err = SetKrbCache(krbCache)
-		if err != nil {
-			return krb, err
-		}
+func getKrb5Client(krbLoginParams *krb5Login) (*client.Client, error) {
+	cfg, err := newKrb5ConfigFromFile(krbLoginParams)
+	if err != nil {
+		return nil, err
 	}
 
-	if keytabfile, ok := config.Parameters["keytabfile"]; ok {
-		krb.Keytab, err = SetKrbKeytab(keytabfile)
-		if err != nil {
-			return krb, err
-		}
+	switch krbLoginParams.loginMethod {
+	case usernameAndPassword:
+		return clientFromUsernameAndPassword(krbLoginParams, cfg)
+	case keyTabFile:
+		return clientFromKeytab(krbLoginParams, cfg)
+	case cachedCredentialsFile:
+		return clientFromCredentialCache(krbLoginParams, cfg)
+	default:
+		return nil, ErrRequiredParametersMissing
 	}
-
-	return krb, nil
 }
 
-func validateKerbConfig(c map[string]string) (missingParam string) {
-	if c["keytabfile"] != "" {
-		if c["realm"] == "" {
-			missingParam = "realm"
-			return
-		}
+func newKrb5ConfigFromFile(krb5Login *krb5Login) (*config.Config, error) {
+	f, err := os.Open(krb5Login.Krb5ConfigFile)
+	if err != nil {
+		return nil, err
 	}
-	if c["krbcache"] == "" && c["keytabfile"] == "" {
-		missingParam = "atleast krbcache or keytab is required"
-		return
+	defer f.Close()
+
+	cfg, err := config.NewFromReader(f)
+	if err != nil {
+		return nil, err
 	}
-	return
+	cfg.LibDefaults.DNSLookupKDC = krb5Login.DNSLookupKDC
+	cfg.LibDefaults.UDPPreferenceLimit = krb5Login.UDPPreferenceLimit
+
+	return cfg, nil
+}
+
+// creates a client from hardcoded user id & password credentials in the connection string in addition to realm
+func clientFromUsernameAndPassword(krb5Login *krb5Login, cfg *config.Config) (*client.Client, error) {
+	return client.NewWithPassword(krb5Login.UserName, krb5Login.Realm, krb5Login.Password, cfg, client.DisablePAFXFAST(true)), nil
 }
 
-func setupKerbConfig(krb5configPath string) (*config.Config, error) {
-	krb5CnfFile, err := os.Open(krb5configPath)
+// loads keytab file specified in keytabFile and creates a client from its content, username and realm
+func clientFromKeytab(krb5Login *krb5Login, cfg *config.Config) (*client.Client, error) {
+	data, err := ioutil.ReadFile(krb5Login.KeytabFile)
 	if err != nil {
 		return nil, err
 	}
-	c, err := config.NewFromReader(krb5CnfFile)
+	var kt = &keytab.Keytab{}
+	err = kt.Unmarshal(data)
 	if err != nil {
 		return nil, err
 	}
-	return c, nil
+
+	return client.NewWithKeytab(krb5Login.UserName, krb5Login.Realm, kt, cfg, client.DisablePAFXFAST(true)), nil
 }
 
-func setupKerbCache(kerbCCahePath string) (*credentials.CCache, error) {
-	cache, err := credentials.LoadCCache(kerbCCahePath)
+// loads credential cache file specified in credCacheFile parameter and creates a client
+func clientFromCredentialCache(krb5Login *krb5Login, cfg *config.Config) (*client.Client, error) {
+	cache, err := credentials.LoadCCache(krb5Login.CredCacheFile)
 	if err != nil {
 		return nil, err
 	}
-	return cache, nil
+
+	return client.NewFromCCache(cache, cfg, client.DisablePAFXFAST(true))
 }
 
-func setupKerbKeytab(keytabFilePath string) (*keytab.Keytab, error) {
-	var kt = &keytab.Keytab{}
-	keytabConf, err := ioutil.ReadFile(keytabFilePath)
+// responsible for transforming network CNames into their actual Hostname.
+// For cases where service tickets can only be bound to hostnames, not cnames.
+func canonicalize(service string) string {
+	parts := strings.SplitAfterN(service, "/", 2)
+	if len(parts) != 2 {
+		return service
+	}
+	host, port, err := net.SplitHostPort(parts[1])
 	if err != nil {
-		return nil, err
+		return service
 	}
-	if err = kt.Unmarshal([]byte(keytabConf)); err != nil {
-		return nil, err
+	cname, err := net.LookupCNAME(strings.ToLower(host))
+	if err != nil {
+		return service
 	}
-	return kt, nil
+	// Put service back together with cname (stripped of trailing .) and port
+	return parts[0] + net.JoinHostPort(cname[:len(cname)-1], port)
 }


Test patch:
diff --git a/integratedauth/krb5/krb5_test.go b/integratedauth/krb5/krb5_test.go
index 5da6596..ad1264b 100644
--- a/integratedauth/krb5/krb5_test.go
+++ b/integratedauth/krb5/krb5_test.go
@@ -1,215 +1,377 @@
-//go:build !windows && go1.13
-// +build !windows,go1.13
-
 package krb5
 
 import (
-	"io/ioutil"
-	"reflect"
+	"strings"
 	"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"
-	"github.com/microsoft/go-mssqldb/integratedauth"
 	"github.com/microsoft/go-mssqldb/msdsn"
 )
 
-func TestGetAuth(t *testing.T) {
-	kerberos := getKerberos()
-	var err error
-	configParams := msdsn.Config{
-		User:      "",
-		ServerSPN: "MSSQLSvc/mssql.domain.com:1433",
-		Port:      1433,
+func TestReadKrb5ConfigHappyPath(t *testing.T) {
+	config := msdsn.Config{
+		User:      "username",
+		Password:  "password",
+		ServerSPN: "serverspn",
 		Parameters: map[string]string{
-			"krb5conffile": "krb5conffile",
-			"keytabfile":   "keytabfile",
-			"krbcache":     "krbcache",
-			"realm":        "domain.com",
+			"krb5-configfile":         "krb5-configfile",
+			"krb5-keytabfile":         "krb5-keytabfile",
+			"krb5-credcachefile":      "krb5-credcachefile",
+			"krb5-realm":              "krb5-realm",
+			"krb5-dnslookupkdc":       "false",
+			"krb5-udppreferencelimit": "1234",
 		},
 	}
 
-	SetKrbConfig = func(krb5configPath string) (*config.Config, error) {
-		return &config.Config{}, nil
-	}
-	SetKrbKeytab = func(keytabFilePath string) (*keytab.Keytab, error) {
-		return &keytab.Keytab{}, nil
+	actual, err := readKrb5Config(config)
+
+	if err != nil {
+		t.Errorf("Unexpected error %v", err)
 	}
-	SetKrbCache = func(kerbCCahePath string) (*credentials.CCache, error) {
-		return &credentials.CCache{}, nil
+
+	if actual.Krb5ConfigFile != config.Parameters[keytabConfigFile] {
+		t.Errorf("Expected Krb5ConfigFile %v, found %v", config.Parameters[keytabConfigFile], actual.Krb5ConfigFile)
 	}
 
-	got, err := getAuth(configParams)
-	if err != nil {
-		t.Errorf("failed:%v", err)
+	if actual.KeytabFile != config.Parameters[keytabFile] {
+		t.Errorf("Expected KeytabFile %v, found %v", config.Parameters[keytabFile], actual.KeytabFile)
 	}
-	kt := &krb5Auth{username: "",
-		realm:      "domain.com",
-		serverSPN:  "MSSQLSvc/mssql.domain.com:1433",
-		port:       1433,
-		krb5Config: kerberos.Config,
-		krbKeytab:  kerberos.Keytab,
-		krbCache:   kerberos.Cache,
-		state:      0}
 
-	res := reflect.DeepEqual(got, kt)
-	if !res {
-		t.Errorf("Failed to get correct krb5Auth object\nExpected:%v\nRecieved:%v", kt, got)
+	if actual.CredCacheFile != config.Parameters[credCacheFile] {
+		t.Errorf("Expected CredCacheFile %v, found %v", config.Parameters[credCacheFile], actual.CredCacheFile)
 	}
 
-	configParams.ServerSPN = "MSSQLSvc/mssql.domain.com"
+	if actual.Realm != config.Parameters[realm] {
+		t.Errorf("Expected Realm %v, found %v", config.Parameters[realm], actual.Realm)
+	}
 
-	_, val := getAuth(configParams)
-	if val == nil {
-		t.Errorf("Failed to get correct krb5Auth object: no port defined")
+	if actual.UserName != config.User {
+		t.Errorf("Expected username %v, found %v", config.User, actual.UserName)
 	}
 
-	configParams.ServerSPN = "MSSQLSvc/mssql.domain.com:1433@DOMAIN.COM"
+	if actual.Password != config.Password {
+		t.Errorf("Expected password %v, found %v", config.Password, actual.Password)
+	}
 
-	got, _ = getAuth(configParams)
-	kt = &krb5Auth{username: "",
-		realm:      "DOMAIN.COM",
-		serverSPN:  "MSSQLSvc/mssql.domain.com:1433",
-		port:       1433,
-		krb5Config: kerberos.Config,
-		krbKeytab:  kerberos.Keytab,
-		krbCache:   kerberos.Cache,
-		state:      0}
+	if actual.ServerSPN != config.ServerSPN {
+		t.Errorf("Expected serverSpn %v, found %v", config.ServerSPN, actual.ServerSPN)
+	}
 
-	res = reflect.DeepEqual(got, kt)
-	if !res {
-		t.Errorf("Failed to get correct krb5Auth object\nExpected:%v\nRecieved:%v", kt, got)
+	if actual.DNSLookupKDC != false {
+		t.Errorf("Expected DNSLookupKDC %v, found %v", false, actual.DNSLookupKDC)
 	}
 
-	configParams.ServerSPN = "MSSQLSvc/mssql.domain.com:1433@domain.com@test"
-	_, val = getAuth(configParams)
-	if val == nil {
-		t.Errorf("Failed to get correct krb5Auth object due to incorrect serverSPN name")
+	if actual.UDPPreferenceLimit != 1234 {
+		t.Errorf("Expected UDPPreferenceLimit %v, found %v", 1234, actual.UDPPreferenceLimit)
 	}
+}
+
+func TestReadKrb5ConfigErrorCases(t *testing.T) {
 
-	configParams.ServerSPN = "MSSQLSvc/mssql.domain.com:port@domain.com"
-	_, val = getAuth(configParams)
-	if val == nil {
-		t.Errorf("Failed to get correct krb5Auth object due to incorrect port")
+	tests := []struct {
+		name               string
+		dnslookup          string
+		udpPreferenceLimit string
+		expectedError      string
+	}{
+
+		{
+			name:               "invalid dnslookupkdc",
+			dnslookup:          "a",
+			udpPreferenceLimit: "1234",
+			expectedError:      "invalid 'krb5-dnslookupkdc' parameter 'a': strconv.ParseBool: parsing \"a\": invalid syntax",
+		},
+		{
+			name:               "invalid udpPreferenceLimit",
+			dnslookup:          "true",
+			udpPreferenceLimit: "a",
+			expectedError:      "invalid 'krb5-udppreferencelimit' parameter 'a': strconv.Atoi: parsing \"a\": invalid syntax",
+		},
 	}
 
-	configParams.ServerSPN = "MSSQLSvc/mssql.domain.com:port"
-	_, val = getAuth(configParams)
-	if val == nil {
-		t.Errorf("Failed to get correct krb5Auth object due to incorrect port")
+	for _, tt := range tests {
+		config := msdsn.Config{
+			Parameters: map[string]string{
+				"krb5-dnslookupkdc":       tt.dnslookup,
+				"krb5-udppreferencelimit": tt.udpPreferenceLimit,
+			},
+		}
+
+		actual, err := readKrb5Config(config)
+
+		if actual != nil {
+			t.Errorf("Unexpected return value expected nil, found %v", actual)
+			continue
+		}
+
+		if err == nil {
+			t.Errorf("Expected error '%v', found nil", tt.expectedError)
+			continue
+		}
+
+		if err.Error() != tt.expectedError {
+			t.Errorf("Expected error %v, found %v", tt.expectedError, err)
+		}
 	}
 }
 
-func TestInitialBytes(t *testing.T) {
-	kerberos := getKerberos()
-	krbObj := &krb5Auth{username: "",
-		realm:      "domain.com",
-		serverSPN:  "MSSQLSvc/mssql.domain.com:1433",
-		port:       1433,
-		krb5Config: kerberos.Config,
-		krbKeytab:  kerberos.Keytab,
-		krbCache:   kerberos.Cache,
-		state:      0,
+func TestValidateKrb5LoginParams(t *testing.T) {
+
+	tests := []struct {
+		name                string
+		input               *krb5Login
+		expectedLoginMethod loginMethod
+		expectedError       error
+	}{
+
+		{
+			name: "happy username and password",
+			input: &krb5Login{
+				Krb5ConfigFile: "exists",
+				Realm:          "realm",
+				UserName:       "username",
+				Password:       "password",
+			},
+			expectedLoginMethod: usernameAndPassword,
+			expectedError:       nil,
+		},
+		{
+			name: "username and password, missing realm",
+			input: &krb5Login{
+				Krb5ConfigFile: "exists",
+				Realm:          "",
+				UserName:       "username",
+				Password:       "password",
+			},
+			expectedLoginMethod: none,
+			expectedError:       ErrRealmRequiredWithUsernameAndPassword,
+		},
+		{
+			name: "username and password, missing Krb5ConfigFile",
+			input: &krb5Login{
+				Krb5ConfigFile: "",
+				Realm:          "realm",
+				UserName:       "username",
+				Password:       "password",
+			},
+			expectedLoginMethod: none,
+			expectedError:       ErrKrb5ConfigFileRequiredWithUsernameAndPassword,
+		},
+		{
+			name: "username and password, Krb5ConfigFile file not found",
+			input: &krb5Login{
+				Krb5ConfigFile: "missing",
+				Realm:          "realm",
+				UserName:       "username",
+				Password:       "password",
+			},
+			expectedLoginMethod: none,
+			expectedError:       ErrKrb5ConfigFileDoesNotExist,
+		},
+		{
+			name: "happy keytab",
+			input: &krb5Login{
+				KeytabFile:     "exists",
+				Krb5ConfigFile: "exists",
+				Realm:          "realm",
+				UserName:       "username",
+			},
+			expectedLoginMethod: keyTabFile,
+			expectedError:       nil,
+		},
+		{
+			name: "keytab, missing username",
+			input: &krb5Login{
+				KeytabFile:     "exists",
+				Krb5ConfigFile: "exists",
+				Realm:          "realm",
+				UserName:       "",
+			},
+			expectedLoginMethod: none,
+			expectedError:       ErrUsernameRequiredWithKeytab,
+		},
+		{
+			name: "keytab, missing realm",
+			input: &krb5Login{
+				KeytabFile:     "exists",
+				Krb5ConfigFile: "exists",
+				Realm:          "",
+				UserName:       "username",
+			},
+			expectedLoginMethod: none,
+			expectedError:       ErrRealmRequiredWithKeytab,
+		},
+		{
+			name: "keytab, missing Krb5ConfigFile",
+			input: &krb5Login{
+				KeytabFile:     "exists",
+				Krb5ConfigFile: "",
+				Realm:          "realm",
+				UserName:       "username",
+			},
+			expectedLoginMethod: none,
+			expectedError:       ErrKrb5ConfigFileRequiredWithKeytab,
+		},
+		{
+			name: "keytab, Krb5ConfigFile file not found",
+			input: &krb5Login{
+				KeytabFile:     "exists",
+				Krb5ConfigFile: "missing",
+				Realm:          "realm",
+				UserName:       "username",
+			},
+			expectedLoginMethod: none,
+			expectedError:       ErrKrb5ConfigFileDoesNotExist,
+		},
+		{
+			name: "keytab, KeytabFile file not found",
+			input: &krb5Login{
+				KeytabFile:     "missing",
+				Krb5ConfigFile: "exists",
+				Realm:          "realm",
+				UserName:       "username",
+			},
+			expectedLoginMethod: none,
+			expectedError:       ErrKeytabFileDoesNotExist,
+		},
+		{
+			name: "happy credential cache",
+			input: &krb5Login{
+				CredCacheFile:  "exists",
+				Krb5ConfigFile: "exists",
+			},
+			expectedLoginMethod: cachedCredentialsFile,
+			expectedError:       nil,
+		},
+		{
+			name: "credential cache, missing Krb5ConfigFile",
+			input: &krb5Login{
+				CredCacheFile:  "exists",
+				Krb5ConfigFile: "",
+			},
+			expectedLoginMethod: none,
+			expectedError:       ErrKrb5ConfigFileRequiredWithCredCache,
+		},
+		{
+			name: "credential cache, Krb5ConfigFile file not found",
+			input: &krb5Login{
+				CredCacheFile:  "exists",
+				Krb5ConfigFile: "missing",
+			},
+			expectedLoginMethod: none,
+			expectedError:       ErrKrb5ConfigFileDoesNotExist,
+		},
+		{
+			name: "credential cache, CredCacheFile file not found",
+			input: &krb5Login{
+				CredCacheFile:  "missing",
+				Krb5ConfigFile: "exists",
+			},
+			expectedLoginMethod: none,
+			expectedError:       ErrCredCacheFileDoesNotExist,
+		},
+		{
+			name:                "no login method math",
+			input:               &krb5Login{},
+			expectedLoginMethod: none,
+			expectedError:       ErrRequiredParametersMissing,
+		},
 	}
 
-	_, err := krbObj.InitialBytes()
-	if err == nil {
-		t.Errorf("Initial Bytes expected to fail but it didn't")
+	revert := mockFileExists()
+	defer revert()
+
+	for _, tt := range tests {
+		tt.input.loginMethod = none
+		err := validateKrb5LoginParams(tt.input)
+
+		if err != nil && tt.expectedError == nil {
+			t.Errorf("Unexpected error %v, expected nil", err)
+		}
+
+		if err == nil && tt.expectedError != nil {
+			t.Errorf("Expected error %v, found nil", tt.expectedError)
+		}
+
+		if err != tt.expectedError {
+			t.Errorf("Expected error %v, found %v", tt.expectedError, err)
+		}
+
+		if tt.input.loginMethod != tt.expectedLoginMethod {
+			t.Errorf("Expected loginMethod %v, found %v", tt.expectedLoginMethod, tt.input.loginMethod)
+		}
 	}
+}
 
-	krbObj.krbKeytab = nil
-	_, err = krbObj.InitialBytes()
-	if err == nil {
-		t.Errorf("Initial Bytes expected to fail but it didn't")
+func mockFileExists() func() {
+	fileExists = func(filename string, errWhenFileNotFound error) (bool, error) {
+		if strings.Contains(filename, "exists") {
+			return true, nil
+		}
+
+		return false, errWhenFileNotFound
 	}
 
+	return func() { fileExists = fileExistsOS }
 }
 
-func TestNextBytes(t *testing.T) {
-	ans := []byte{}
-	kerberos := getKerberos()
+func TestGetAuth(t *testing.T) {
+	config := msdsn.Config{
+		User:      "username",
+		Password:  "password",
+		ServerSPN: "serverspn",
+		Parameters: map[string]string{
+			"krb5-configfile":         "exists",
+			"krb5-keytabfile":         "exists",
+			"krb5-keytabcachefile":    "exists",
+			"krb5-realm":              "krb5-realm",
+			"krb5-dnslookupkdc":       "false",
+			"krb5-udppreferencelimit": "1234",
+		},
+	}
 
-	var krbObj integratedauth.IntegratedAuthenticator = &krb5Auth{username: "",
-		realm:      "domain.com",
-		serverSPN:  "MSSQLSvc/mssql.domain.com:1433",
-		port:       1433,
-		krb5Config: kerberos.Config,
-		krbKeytab:  kerberos.Keytab,
-		krbCache:   kerberos.Cache,
-		state:      0}
+	revert := mockFileExists()
+	defer revert()
 
-	_, err := krbObj.NextBytes(ans)
-	if err == nil {
-		t.Errorf("Next Byte expected to fail but it didn't")
+	a, err := getAuth(config)
+	if err != nil {
+		t.Errorf("Unexpected error %v", err)
 	}
-}
 
-func TestFree(t *testing.T) {
-	kerberos := getKerberos()
-	kt := &keytab.Keytab{}
-	c := &config.Config{}
+	actual := a.(*krbAuth)
 
-	cl := client.NewWithKeytab("Administrator", "DOMAIN.COM", kt, c, client.DisablePAFXFAST(true))
+	if actual.krb5Config.Krb5ConfigFile != config.Parameters[keytabConfigFile] {
+		t.Errorf("Expected Krb5ConfigFile %v, found %v", config.Parameters[keytabConfigFile], actual.krb5Config.Krb5ConfigFile)
+	}
 
-	var krbObj integratedauth.IntegratedAuthenticator = &krb5Auth{username: "",
-		realm:      "domain.com",
-		serverSPN:  "MSSQLSvc/mssql.domain.com:1433",
-		port:       1433,
-		krb5Config: kerberos.Config,
-		krbKeytab:  kerberos.Keytab,
-		krbCache:   kerberos.Cache,
-		state:      0,
-		krb5Client: cl,
+	if actual.krb5Config.KeytabFile != config.Parameters[keytabFile] {
+		t.Errorf("Expected KeytabFile %v, found %v", config.Parameters[keytabFile], actual.krb5Config.KeytabFile)
 	}
-	krbObj.Free()
-	cacheEntries := len(kerberos.Cache.GetEntries())
-	if cacheEntries != 0 {
-		t.Errorf("Client not destroyed")
+
+	if actual.krb5Config.CredCacheFile != config.Parameters[credCacheFile] {
+		t.Errorf("Expected CredCacheFile %v, found %v", config.Parameters[credCacheFile], actual.krb5Config.CredCacheFile)
 	}
-}
 
-func TestSetKrbConfig(t *testing.T) {
-	krb5conffile := createTempFile(t, "krb5conffile")
-	_, err := setupKerbConfig(krb5conffile)
-	if err != nil {
-		t.Errorf("Failed to read krb5 config file")
+	if actual.krb5Config.Realm != config.Parameters[realm] {
+		t.Errorf("Expected Realm %v, found %v", config.Parameters[realm], actual.krb5Config.Realm)
 	}
-}
 
-func TestSetKrbKeytab(t *testing.T) {
-	krbkeytab := createTempFile(t, "keytabfile")
-	_, err := setupKerbKeytab(krbkeytab)
-	if err == nil {
-		t.Errorf("Failed to read keytab file")
+	if actual.krb5Config.UserName != config.User {
+		t.Errorf("Expected username %v, found %v", config.User, actual.krb5Config.UserName)
 	}
-}
 
-func TestSetKrbCache(t *testing.T) {
-	krbcache := createTempFile(t, "krbcache")
-	_, err := setupKerbCache(krbcache)
-	if err == nil {
-		t.Errorf("Failed to read cache file")
+	if actual.krb5Config.Password != config.Password {
+		t.Errorf("Expected password %v, found %v", config.Password, actual.krb5Config.Password)
 	}
-}
 
-func getKerberos() (krbParams *Kerberos) {
-	krbParams = &Kerberos{
-		Config: &config.Config{},
-		Keytab: &keytab.Keytab{},
-		Cache:  &credentials.CCache{},
+	if actual.krb5Config.ServerSPN != config.ServerSPN {
+		t.Errorf("Expected serverSpn %v, found %v", config.ServerSPN, actual.krb5Config.ServerSPN)
 	}
-	return
-}
 
-func createTempFile(t *testing.T, filename string) string {
-	file, err := ioutil.TempFile("", "test-"+filename+".txt")
-	if err != nil {
-		t.Fatalf("Failed to create a temp file:%v", err)
+	if actual.krb5Config.DNSLookupKDC != false {
+		t.Errorf("Expected DNSLookupKDC %v, found %v", false, actual.krb5Config.DNSLookupKDC)
 	}
-	if _, err := file.Write([]byte("This is a test file\n")); err != nil {
-		t.Fatalf("Failed to write file:%v", err)
+
+	if actual.krb5Config.UDPPreferenceLimit != 1234 {
+		t.Errorf("Expected UDPPreferenceLimit %v, found %v", 1234, actual.krb5Config.UDPPreferenceLimit)
 	}
-	return file.Name()
 }