Repository: microsoft/go-mssqldb
Pull request #35 (2022-07-08 12:05:56)

add Kerberos auth
Have added support for kerberos authentication for go-mssqldb using the [GOKRB5](https://github.com/jcmturner/gokrb5) package.

To enable the support for krb the users need to pass the connection string as the following

1. To use the keytab file

`server=server;user id=user;port=1345;realm=domain;trustservercertificate=true;krb5conffile=/etc/krb5.conf;keytabfile=/path/to/keytabFile.keytab`

2. To use kerberos cache file

`"server=server;port=1345;realm=domain;trustservercertificate=true;krb5conffile=/etc/krb5.conf;krbcache=/tmp/krb5cc_1000"` 

```
krb5conffile        : path to the krb5 configuration file, defaults to "/etc/krb5.conf"
krbcache            : path to the kerberos cache file, required if initkrbwithkeytab = false
keytabfile          : path to the keytab file, required if initkrbwithkeytab = true
realm               : domain name, required when using kerberos cache

```



microsoft-cla[bot]: [![CLA assistant check](https://cla.opensource.microsoft.com/pull/badge/not_signed)](https://cla.opensource.microsoft.com/microsoft/go-mssqldb?pullRequest=35) <br/>Thank you for your submission, we really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla.opensource.microsoft.com/microsoft/go-mssqldb?pullRequest=35) before we can accept your contribution.<br/><br/>:x: chandanjainn [sign now](https://cla.opensource.microsoft.com/microsoft/go-mssqldb?pullRequest=35)<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla.opensource.microsoft.com/check/microsoft/go-mssqldb?pullRequest=35) it.</sub>
shueybubbles: Can you separate the krb5 code into non-Windows .go files and stub the implementation in a Windows .go file so Windows apps don't need to pick up this code?
shueybubbles: @dzsquared @chandanjainn @stuartpa the krb code requires go 1.13 and higher. 
Should Chandan put the new code in .go files with a 1.13 and higher build directive (and linux only), or should we go ahead and remove 1.12 and prior versions from the appveyor tests?
chandanjainn: > Can you separate the krb5 code into non-Windows .go files and stub the implementation in a Windows .go file so Windows apps don't need to pick up this code?

apologies for the late response @shueybubbles working on this. the only issue at my end is lack of a windows system to test out the changes. 
chandanjainn: @shueybubbles have added stub files for windows so that krb is not added as a dependency for windows based systems.
Please review.
shueybubbles: you can also add a `go113` directive to the files with `!windows` directives so pre-go 1.13 won't try to use it.
chandanjainn: @shueybubbles done with the suggested changes. please review
PeteBassettBet365: Hi. There appear to be some logic issue and opportunities for more thorough error checking here. 

For instance, I'm attempting to connect using a keytab. I dont supply a KrbCache param in the connection string, which I believe is valid and correct. However the code panics inside auth.go func getKrbParams in the dereference of :

`krb["KrbCache"].(string)`
in
`krbParams["Cache"], err = setupKerbCache(krb["KrbCache"].(string))`

Aside from the unchecked type assertion, the function as a whole enforces the connection string contain both a KrbCache and KeytabFile parameter. Their use should be mutually exclusive.

The conn string is user supplied and as such can be mangled entirely, a sensible error message should tell the caller of the issue.
chandanjainn: @PeteBassettBet365 have fixed all assertion based issues by using structs everywhere and made cache and keytab mutually exclusive with proper error messages. All of this was was there in the earlier commits but got lost while removing the dependencies for windows system in the last couple of commits.

Regarding the ability to connect using username and password, am not sure what you are expecting here? Why would we use password in the connection string along with kerberos config?

 if you see the code at https://github.com/microsoft/go-mssqldb/pull/35/files#diff-1f48700829d7e0cdba7f3729d6a7c1c5df3aa11f2aeeed583c7e1e39aa7b66d1R21 , it falls back to username/password based authN given that the user is not sending any krb related config in the conn string and if the user does we'll get relevant errors thrown.
PeteBassettBet365: Hi again @chandanjainn thanks for sorting the few issues!

Sorry, I'm not talking about sqlserver auth fallback, this is kerberos auth. The reason I'd like to support supplying the kerberos username and password is because, for certain use cases as I said previously, it is easier/faster than distribution of new keytabs to servers.  github.com/jcmturner/gokrb5/v8/client supports this via NewWithPassword

chandanjainn: Reply to [1225995716](https://github.com/microsoft/go-mssqldb/pull/35#issuecomment-1225995716)

@PeteBassettBet365 got it. thanks for the feedback. will incorporate the changes for NewWithPassword in the upcoming commits.

@shueybubbles will add the changes after integration with Pete's changes by today or tomorrow EOD max. Have the changes on a separate branch, testing them out. 
codecov-commenter: # [Codecov](https://codecov.io/gh/microsoft/go-mssqldb/pull/35?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft) Report
> Merging [#35](https://codecov.io/gh/microsoft/go-mssqldb/pull/35?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft) (cde874d) into [main](https://codecov.io/gh/microsoft/go-mssqldb/commit/ba5a4a01858c9783ba0b75456d09d577e23240e7?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft) (ba5a4a0) will **decrease** coverage by `0.26%`.
> The diff coverage is `n/a`.

```diff
@@            Coverage Diff             @@
##             main      #35      +/-   ##
==========================================
- Coverage   73.08%   72.81%   -0.27%     
==========================================
  Files          25       23       -2     
  Lines        5458     5445      -13     
==========================================
- Hits         3989     3965      -24     
- Misses       1230     1238       +8     
- Partials      239      242       +3     
```


| [Impacted Files](https://codecov.io/gh/microsoft/go-mssqldb/pull/35?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft) | Coverage Δ | |
|---|---|---|
| [tds.go](https://codecov.io/gh/microsoft/go-mssqldb/pull/35/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft#diff-dGRzLmdv) | `66.26% <0.00%> (-0.74%)` | :arrow_down: |
| [mssql.go](https://codecov.io/gh/microsoft/go-mssqldb/pull/35/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: |
| [token.go](https://codecov.io/gh/microsoft/go-mssqldb/pull/35/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft#diff-dG9rZW4uZ28=) | `64.29% <0.00%> (-0.15%)` | :arrow_down: |
| [ucs22str\_32bit.go](https://codecov.io/gh/microsoft/go-mssqldb/pull/35/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft#diff-dWNzMjJzdHJfMzJiaXQuZ28=) | | |
| [mssql\_go118.go](https://codecov.io/gh/microsoft/go-mssqldb/pull/35/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=microsoft#diff-bXNzcWxfZ28xMTguZ28=) | | |

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)

shueybubbles: I'd like to see my questions about how apps will need to manage integrated auth connection strings, protocol fallback behavior, etc answered in the README.md at least so it's clear what the limitations are about connection string reuse across Windows and Linux for integrated auth.
Maybe a single connection string for all environments isn't achievable, but let's document it appropriately. If there are potentially ways to enable protocol negotiation in the future, please open an Issue to discuss what that would look like.
thx!
LessTravelledWay: Activity on this has stalled - what do we need to do to get this moving again ?

@shueybubbles  - Have all your points been addressed ?

@chandanjainn - @PeteBassettBet365 is available to assist with any blockers.
PeteBassettBet365: Hi. I would add one point, I think the server name needs to be canonicalized with a call to `net.LookupCNAME`. I'll confirm in the morning.
shueybubbles: Should we merge this as-is then @PeteBassettBet365 could follow up with any tweaks after testing? I don't have a working kerberos Linux setup to test with.
It shouldn't break anybody to merge it, and I don't plan to add a tag until some others confirm it is suitable for production.

---
In reply to: [1274962421](https://github.com/microsoft/go-mssqldb/pull/35#issuecomment-1274962421) [](http://example.com/codeflow?ancestors=1274962421,1274962421)
PeteBassettBet365: Understood. I can give it a test this end.

Patch:
diff --git a/README.md b/README.md
index 89a490e..e42219d 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,7 @@ Other supported formats are listed below.
   * `false` - Data sent between client and server is not encrypted beyond the login packet. (Default)
   * `true` - Data sent between client and server is encrypted.
 * `app name` - The application name (default is go-mssqldb)
+* `authenticator` - Can be used to specify use of a registered authentication provider. (e.g. ntlm, winsspi (on windows) or krb5 (on linux))
 
 ### Connection parameters for ODBC and ADO style connection strings
 
@@ -59,6 +60,32 @@ Other supported formats are listed below.
 * `Workstation ID` - The workstation name (default is the host name)
 * `ApplicationIntent` - Can be given the value `ReadOnly` to initiate a read-only connection to an Availability Group listener. The `database` must be specified when connecting with `Application Intent` set to `ReadOnly`.
 
+### Kerberos Active Directory authentication outside Windows
+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
+
+* 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
+
+* 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;
+
+### 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: 
+  * <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>
+
 ### The connection string can be specified in one of three formats
 
 1. URL: with `sqlserver` scheme. username and password appears before the host. Any instance appears as
@@ -88,11 +115,17 @@ Other supported formats are listed below.
 
     ```
 
+* `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`
+
 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`
+
 
     ADO strings support synonyms for database, app name, user id, and server
     * server <= addr, address, network address, data source
@@ -112,6 +145,8 @@ Other supported formats are listed below.
     * `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`
 
 ### Azure Active Directory authentication
 
@@ -322,6 +357,7 @@ db.QueryContext(ctx, `select * from t2 where user_name = @p1;`, mssql.VarChar(na
 * Supports Single-Sign-On on Windows
 * Supports connections to AlwaysOn Availability Group listeners, including re-direction to read-only replicas.
 * Supports query notifications
+* Supports Kerberos Authentication
 
 ## Tests
 
diff --git a/go.mod b/go.mod
index 92a265c..f9de173 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,7 @@ require (
 	github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe
 	github.com/golang-sql/sqlexp v0.1.0
 	github.com/google/uuid v1.3.0 // indirect
+	github.com/jcmturner/gokrb5/v8 v8.4.2
 	github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
 	golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88
 	golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7 // indirect
diff --git a/go.sum b/go.sum
index 853a21f..ff200c9 100644
--- a/go.sum
+++ b/go.sum
@@ -25,6 +25,24 @@ github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EO
 github.com/google/uuid v1.1.1/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/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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
 github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
@@ -35,13 +53,17 @@ github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzL
 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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 h1:Tgea0cVUD0ivh5ADBX4WwuI12DUd2to3nCYe2eayMIw=
 golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
@@ -68,6 +90,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
 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.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.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/integratedauth/krb5/krb5.go b/integratedauth/krb5/krb5.go
new file mode 100644
index 0000000..6d96871
--- /dev/null
+++ b/integratedauth/krb5/krb5.go
@@ -0,0 +1,268 @@
+//go:build !windows && go1.13
+// +build !windows,go1.13
+
+package krb5
+
+import (
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"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"
+	"github.com/microsoft/go-mssqldb/integratedauth"
+	"github.com/microsoft/go-mssqldb/msdsn"
+)
+
+var (
+	SetKrbConfig = setupKerbConfig
+	SetKrbKeytab = setupKerbKeytab
+	SetKrbCache  = setupKerbCache
+)
+
+// 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 (
+	_                integratedauth.IntegratedAuthenticator = (*krb5Auth)(nil)
+	AuthProviderFunc integratedauth.Provider                = integratedauth.ProviderFunc(getAuth)
+)
+
+func init() {
+	err := integratedauth.SetIntegratedAuthenticationProvider("krb5", AuthProviderFunc)
+	if err != nil {
+		panic(err)
+	}
+}
+
+func getAuth(config msdsn.Config) (integratedauth.IntegratedAuthenticator, error) {
+	var port uint64
+	var realm, serviceStr string
+	var err error
+
+	krb, err := readKrb5Config(config)
+	if err != nil {
+		return &krb5Auth{}, err
+	}
+	params1 := strings.Split(config.ServerSPN, ":")
+	if len(params1) != 2 {
+		return nil, errors.New("invalid ServerSPN")
+	}
+
+	params2 := strings.Split(params1[1], "@")
+	switch len(params2) {
+	case 1:
+		port, err = strconv.ParseUint(params1[1], 10, 16)
+		if err != nil {
+			return nil, err
+		}
+	case 2:
+		port, err = strconv.ParseUint(params2[0], 10, 16)
+		if err != nil {
+			return nil, err
+		}
+	default:
+		return nil, errors.New("invalid ServerSPN")
+	}
+
+	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]
+	default:
+		return nil, errors.New("invalid ServerSPN")
+	}
+
+	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
+		}
+	}
+	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
+}
+
+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")
+	}
+
+	krb.Config, err = SetKrbConfig(krbConfig)
+	if err != nil {
+		return krb, err
+	}
+
+	missingParam := validateKerbConfig(config.Parameters)
+	if missingParam != "" {
+		return krb, fmt.Errorf("missing parameter:%s", missingParam)
+	}
+
+	if realm, ok := config.Parameters["realm"]; ok {
+		krb.Realm = realm
+	}
+
+	if krbCache, ok := config.Parameters["krbcache"]; ok {
+		krb.Cache, err = SetKrbCache(krbCache)
+		if err != nil {
+			return krb, err
+		}
+	}
+
+	if keytabfile, ok := config.Parameters["keytabfile"]; ok {
+		krb.Keytab, err = SetKrbKeytab(keytabfile)
+		if err != nil {
+			return krb, err
+		}
+	}
+
+	return krb, nil
+}
+
+func validateKerbConfig(c map[string]string) (missingParam string) {
+	if c["keytabfile"] != "" {
+		if c["realm"] == "" {
+			missingParam = "realm"
+			return
+		}
+	}
+	if c["krbcache"] == "" && c["keytabfile"] == "" {
+		missingParam = "atleast krbcache or keytab is required"
+		return
+	}
+	return
+}
+
+func setupKerbConfig(krb5configPath string) (*config.Config, error) {
+	krb5CnfFile, err := os.Open(krb5configPath)
+	if err != nil {
+		return nil, err
+	}
+	c, err := config.NewFromReader(krb5CnfFile)
+	if err != nil {
+		return nil, err
+	}
+	return c, nil
+}
+
+func setupKerbCache(kerbCCahePath string) (*credentials.CCache, error) {
+	cache, err := credentials.LoadCCache(kerbCCahePath)
+	if err != nil {
+		return nil, err
+	}
+	return cache, nil
+}
+
+func setupKerbKeytab(keytabFilePath string) (*keytab.Keytab, error) {
+	var kt = &keytab.Keytab{}
+	keytabConf, err := ioutil.ReadFile(keytabFilePath)
+	if err != nil {
+		return nil, err
+	}
+	if err = kt.Unmarshal([]byte(keytabConf)); err != nil {
+		return nil, err
+	}
+	return kt, nil
+}


Test patch:
diff --git a/integratedauth/krb5/krb5_test.go b/integratedauth/krb5/krb5_test.go
new file mode 100644
index 0000000..5da6596
--- /dev/null
+++ b/integratedauth/krb5/krb5_test.go
@@ -0,0 +1,215 @@
+//go:build !windows && go1.13
+// +build !windows,go1.13
+
+package krb5
+
+import (
+	"io/ioutil"
+	"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"
+	"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,
+		Parameters: map[string]string{
+			"krb5conffile": "krb5conffile",
+			"keytabfile":   "keytabfile",
+			"krbcache":     "krbcache",
+			"realm":        "domain.com",
+		},
+	}
+
+	SetKrbConfig = func(krb5configPath string) (*config.Config, error) {
+		return &config.Config{}, nil
+	}
+	SetKrbKeytab = func(keytabFilePath string) (*keytab.Keytab, error) {
+		return &keytab.Keytab{}, nil
+	}
+	SetKrbCache = func(kerbCCahePath string) (*credentials.CCache, error) {
+		return &credentials.CCache{}, nil
+	}
+
+	got, err := getAuth(configParams)
+	if err != nil {
+		t.Errorf("failed:%v", err)
+	}
+	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)
+	}
+
+	configParams.ServerSPN = "MSSQLSvc/mssql.domain.com"
+
+	_, val := getAuth(configParams)
+	if val == nil {
+		t.Errorf("Failed to get correct krb5Auth object: no port defined")
+	}
+
+	configParams.ServerSPN = "MSSQLSvc/mssql.domain.com:1433@DOMAIN.COM"
+
+	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}
+
+	res = reflect.DeepEqual(got, kt)
+	if !res {
+		t.Errorf("Failed to get correct krb5Auth object\nExpected:%v\nRecieved:%v", kt, got)
+	}
+
+	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")
+	}
+
+	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")
+	}
+
+	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")
+	}
+}
+
+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,
+	}
+
+	_, err := krbObj.InitialBytes()
+	if err == nil {
+		t.Errorf("Initial Bytes expected to fail but it didn't")
+	}
+
+	krbObj.krbKeytab = nil
+	_, err = krbObj.InitialBytes()
+	if err == nil {
+		t.Errorf("Initial Bytes expected to fail but it didn't")
+	}
+
+}
+
+func TestNextBytes(t *testing.T) {
+	ans := []byte{}
+	kerberos := getKerberos()
+
+	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}
+
+	_, err := krbObj.NextBytes(ans)
+	if err == nil {
+		t.Errorf("Next Byte expected to fail but it didn't")
+	}
+}
+
+func TestFree(t *testing.T) {
+	kerberos := getKerberos()
+	kt := &keytab.Keytab{}
+	c := &config.Config{}
+
+	cl := client.NewWithKeytab("Administrator", "DOMAIN.COM", kt, c, client.DisablePAFXFAST(true))
+
+	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,
+	}
+	krbObj.Free()
+	cacheEntries := len(kerberos.Cache.GetEntries())
+	if cacheEntries != 0 {
+		t.Errorf("Client not destroyed")
+	}
+}
+
+func TestSetKrbConfig(t *testing.T) {
+	krb5conffile := createTempFile(t, "krb5conffile")
+	_, err := setupKerbConfig(krb5conffile)
+	if err != nil {
+		t.Errorf("Failed to read krb5 config file")
+	}
+}
+
+func TestSetKrbKeytab(t *testing.T) {
+	krbkeytab := createTempFile(t, "keytabfile")
+	_, err := setupKerbKeytab(krbkeytab)
+	if err == nil {
+		t.Errorf("Failed to read keytab file")
+	}
+}
+
+func TestSetKrbCache(t *testing.T) {
+	krbcache := createTempFile(t, "krbcache")
+	_, err := setupKerbCache(krbcache)
+	if err == nil {
+		t.Errorf("Failed to read cache file")
+	}
+}
+
+func getKerberos() (krbParams *Kerberos) {
+	krbParams = &Kerberos{
+		Config: &config.Config{},
+		Keytab: &keytab.Keytab{},
+		Cache:  &credentials.CCache{},
+	}
+	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 _, err := file.Write([]byte("This is a test file\n")); err != nil {
+		t.Fatalf("Failed to write file:%v", err)
+	}
+	return file.Name()
+}