From 710e0ceb1f6d97e178402d8054b4de6dd2929e23 Mon Sep 17 00:00:00 2001 From: Sam Fox Date: Wed, 16 Jun 2021 14:42:36 -0700 Subject: [PATCH 01/14] MSI Auth for SQL Server Add MSI Auth option to SQL Server connection. Default if no password is provided in connection string. --- database/sqlserver/sqlserver.go | 45 ++++++++++++++++++++++++++++++--- go.mod | 1 + go.sum | 5 ++++ 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/database/sqlserver/sqlserver.go b/database/sqlserver/sqlserver.go index b90619ff9..7337b4e13 100644 --- a/database/sqlserver/sqlserver.go +++ b/database/sqlserver/sqlserver.go @@ -8,6 +8,7 @@ import ( "io/ioutil" nurl "net/url" + "github.com/Azure/go-autorest/autorest/adal" mssql "github.com/denisenkom/go-mssqldb" // mssql support "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/database" @@ -116,16 +117,34 @@ func WithInstance(instance *sql.DB, config *Config) (database.Driver, error) { return ss, nil } -// Open a connection to the database +// Open a connection to the database. If no password is provided, MSI auth will be tried. func (ss *SQLServer) Open(url string) (database.Driver, error) { purl, err := nurl.Parse(url) if err != nil { return nil, err } - db, err := sql.Open("sqlserver", migrate.FilterCustomQuery(purl).String()) - if err != nil { - return nil, err + var db *sql.DB + if _, exist := purl.User.Password(); !exist { + tokenProvider, err := getMSITokenProvider(fmt.Sprintf("%s%s", "https://", purl.Hostname()[1:])) + if err != nil { + return nil, err + } + + connector, err := mssql.NewAccessTokenConnector( + migrate.FilterCustomQuery(purl).String(), tokenProvider) + + if err != nil { + return nil, err + } + + db = sql.OpenDB(connector) + + } else { + db, err = sql.Open("sqlserver", migrate.FilterCustomQuery(purl).String()) + if err != nil { + return nil, err + } } migrationsTable := purl.Query().Get("x-migrations-table") @@ -344,3 +363,21 @@ func (ss *SQLServer) ensureVersionTable() (err error) { return nil } + +func getMSITokenProvider(resource string) (func() (string, error), error) { + msiEndpoint, err := adal.GetMSIEndpoint() + if err != nil { + return nil, err + } + msi, err := adal.NewServicePrincipalTokenFromMSI( + msiEndpoint, resource) + if err != nil { + return nil, err + } + + return func() (string, error) { + msi.EnsureFresh() + token := msi.OAuthToken() + return token, nil + }, nil +} diff --git a/go.mod b/go.mod index 4e0a1ae89..b3b49e159 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/golang-migrate/migrate/v4 require ( cloud.google.com/go/spanner v1.18.0 cloud.google.com/go/storage v1.10.0 + github.com/Azure/go-autorest/autorest/adal v0.9.14 github.com/ClickHouse/clickhouse-go v1.4.3 github.com/apache/arrow/go/arrow v0.0.0-20210521153258-78c88a9f517b // indirect github.com/aws/aws-sdk-go v1.17.7 diff --git a/go.sum b/go.sum index 3517c1da9..73499125b 100644 --- a/go.sum +++ b/go.sum @@ -50,9 +50,13 @@ github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest/adal v0.9.2 h1:Aze/GQeAN1RRbGmnUJvUj+tFGBzFdIg3293/A9rbxC4= github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= +github.com/Azure/go-autorest/autorest/adal v0.9.14 h1:G8hexQdV5D4khOXrWG2YuLCFKhWYmWD8bHYaXN5ophk= +github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -506,6 +510,7 @@ golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= From 04c3f27e6ba3eee821798ae0ce6f84bf2b59ba59 Mon Sep 17 00:00:00 2001 From: Sam Fox Date: Thu, 17 Jun 2021 15:31:43 -0700 Subject: [PATCH 02/14] Parse resource endpoint from server for msi update host name parsing to get just the resource endpoint for msi --- database/mysql/mysql.go | 7 +------ database/sqlserver/sqlserver.go | 3 ++- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/database/mysql/mysql.go b/database/mysql/mysql.go index 586df2494..6c0262689 100644 --- a/database/mysql/mysql.go +++ b/database/mysql/mysql.go @@ -13,15 +13,10 @@ import ( nurl "net/url" "strconv" "strings" -) -import ( "github.com/go-sql-driver/mysql" - "github.com/hashicorp/go-multierror" -) - -import ( "github.com/golang-migrate/migrate/v4/database" + "github.com/hashicorp/go-multierror" ) func init() { diff --git a/database/sqlserver/sqlserver.go b/database/sqlserver/sqlserver.go index 7337b4e13..816045904 100644 --- a/database/sqlserver/sqlserver.go +++ b/database/sqlserver/sqlserver.go @@ -7,6 +7,7 @@ import ( "io" "io/ioutil" nurl "net/url" + "strings" "github.com/Azure/go-autorest/autorest/adal" mssql "github.com/denisenkom/go-mssqldb" // mssql support @@ -126,7 +127,7 @@ func (ss *SQLServer) Open(url string) (database.Driver, error) { var db *sql.DB if _, exist := purl.User.Password(); !exist { - tokenProvider, err := getMSITokenProvider(fmt.Sprintf("%s%s", "https://", purl.Hostname()[1:])) + tokenProvider, err := getMSITokenProvider(fmt.Sprintf("%s%s", "https://", strings.Join(strings.Split(purl.Hostname(), ".")[1:], "."))) if err != nil { return nil, err } From 2ff2841d544825900288890a7239b9b575ab6939 Mon Sep 17 00:00:00 2001 From: Sam Fox Date: Thu, 17 Jun 2021 17:33:06 -0700 Subject: [PATCH 03/14] Update go-mssqldb update go-mssqldb to resolve panic issue referenced here: https://github.com/denisenkom/go-mssqldb/pull/642 --- go.mod | 2 +- go.sum | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index b3b49e159..df6b3cca8 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/cenkalti/backoff/v4 v4.0.2 github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051 github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369 // indirect - github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec + github.com/denisenkom/go-mssqldb v0.10.0 github.com/dhui/dktest v0.3.4 github.com/docker/docker v17.12.0-ce-rc1.0.20210128214336-420b1d36250f+incompatible github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 // indirect diff --git a/go.sum b/go.sum index 73499125b..141223772 100644 --- a/go.sum +++ b/go.sum @@ -48,12 +48,12 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7O github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest/adal v0.9.2 h1:Aze/GQeAN1RRbGmnUJvUj+tFGBzFdIg3293/A9rbxC4= github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= github.com/Azure/go-autorest/autorest/adal v0.9.14 h1:G8hexQdV5D4khOXrWG2YuLCFKhWYmWD8bHYaXN5ophk= github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= @@ -140,9 +140,8 @@ github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec h1:NfhRXXFDPxcF5Cwo06DzeIaE7uuJtAUhsDwH3LNsjos= -github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/denisenkom/go-mssqldb v0.10.0 h1:QykgLZBorFE95+gO3u9esLd0BmbvpWp0/waNNZfHBM8= +github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dhui/dktest v0.3.4 h1:VbUEcaSP+U2/yUr9d2JhSThXYEnDlGabRSHe2rIE46E= github.com/dhui/dktest v0.3.4/go.mod h1:4m4n6lmXlmVfESth7mzdcv8nBI5mOb5UROPqjM02csU= From dba53bbfdf4b041e418732ead2ea78b0bd9f81c9 Mon Sep 17 00:00:00 2001 From: Sam Fox Date: Tue, 22 Jun 2021 10:48:18 -0700 Subject: [PATCH 04/14] Update sqlserver.go switch from deprecated methods. NewServicePrincipalTokenFromManagedIdentity calls the two methods that are deprecated for us --- database/sqlserver/sqlserver.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/database/sqlserver/sqlserver.go b/database/sqlserver/sqlserver.go index 816045904..0f05a549a 100644 --- a/database/sqlserver/sqlserver.go +++ b/database/sqlserver/sqlserver.go @@ -366,12 +366,7 @@ func (ss *SQLServer) ensureVersionTable() (err error) { } func getMSITokenProvider(resource string) (func() (string, error), error) { - msiEndpoint, err := adal.GetMSIEndpoint() - if err != nil { - return nil, err - } - msi, err := adal.NewServicePrincipalTokenFromMSI( - msiEndpoint, resource) + msi, err := adal.NewServicePrincipalTokenFromManagedIdentity(resource, nil) if err != nil { return nil, err } From 737f0268d720fc93be1ef4178273687a7d6c4e2c Mon Sep 17 00:00:00 2001 From: Sam Fox Date: Fri, 25 Jun 2021 09:50:57 -0700 Subject: [PATCH 05/14] Update sqlserver.go add useMsi param instead of looking for nil password --- database/sqlserver/sqlserver.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/database/sqlserver/sqlserver.go b/database/sqlserver/sqlserver.go index 0f05a549a..337dfab46 100644 --- a/database/sqlserver/sqlserver.go +++ b/database/sqlserver/sqlserver.go @@ -7,6 +7,7 @@ import ( "io" "io/ioutil" nurl "net/url" + "strconv" "strings" "github.com/Azure/go-autorest/autorest/adal" @@ -125,8 +126,13 @@ func (ss *SQLServer) Open(url string) (database.Driver, error) { return nil, err } + useMsi, err := strconv.ParseBool(purl.Query().Get("useMsi")) + if err != nil { + return nil, err + } + var db *sql.DB - if _, exist := purl.User.Password(); !exist { + if useMsi { tokenProvider, err := getMSITokenProvider(fmt.Sprintf("%s%s", "https://", strings.Join(strings.Split(purl.Hostname(), ".")[1:], "."))) if err != nil { return nil, err From 0c7286c6917a6f2c07a32c77c4830ba521fafb32 Mon Sep 17 00:00:00 2001 From: Sam Fox Date: Fri, 25 Jun 2021 11:30:02 -0700 Subject: [PATCH 06/14] Update sqlserver readme Update sqlserver readme for msi auth. make useMsi a bit safer --- database/sqlserver/README.md | 1 + database/sqlserver/sqlserver.go | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/database/sqlserver/README.md b/database/sqlserver/README.md index 86a0b79f7..0a81aca7a 100644 --- a/database/sqlserver/README.md +++ b/database/sqlserver/README.md @@ -16,6 +16,7 @@ | `dial+timeout` | | in seconds (default is 15), set to 0 for no timeout. | | `encrypt` | | `disable` - Data send between client and server is not encrypted. `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). | +| `useMsi` | | `true` - Use Azure MSI Authentication for connecting to Sql Server. Must be running from an Azure VM/an instance with MSI enabled. `false` - Use password authentication (Default). Azure MSI Auth details: https://docs.microsoft.com/en-us/azure/app-service/app-service-web-tutorial-connect-msi. See https://github.com/denisenkom/go-mssqldb for full parameter list. diff --git a/database/sqlserver/sqlserver.go b/database/sqlserver/sqlserver.go index 337dfab46..53be2cb8b 100644 --- a/database/sqlserver/sqlserver.go +++ b/database/sqlserver/sqlserver.go @@ -126,9 +126,13 @@ func (ss *SQLServer) Open(url string) (database.Driver, error) { return nil, err } - useMsi, err := strconv.ParseBool(purl.Query().Get("useMsi")) - if err != nil { - return nil, err + useMsiParam := purl.Query().Get("useMsi") + useMsi := false + if len(useMsiParam) > 0 { + useMsi, err = strconv.ParseBool(useMsiParam) + if err != nil { + return nil, err + } } var db *sql.DB From 78f2d54f25bc562100e7aa0ed68c1ed72ec2a9a8 Mon Sep 17 00:00:00 2001 From: Sam Fox Date: Fri, 25 Jun 2021 11:32:38 -0700 Subject: [PATCH 07/14] Update sqlserver.go remove comment --- database/sqlserver/sqlserver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/sqlserver/sqlserver.go b/database/sqlserver/sqlserver.go index 53be2cb8b..8dcbe630c 100644 --- a/database/sqlserver/sqlserver.go +++ b/database/sqlserver/sqlserver.go @@ -119,7 +119,7 @@ func WithInstance(instance *sql.DB, config *Config) (database.Driver, error) { return ss, nil } -// Open a connection to the database. If no password is provided, MSI auth will be tried. +// Open a connection to the database. func (ss *SQLServer) Open(url string) (database.Driver, error) { purl, err := nurl.Parse(url) if err != nil { From f7dc1228573ad1d5da52c607c6beba4ef8ad6526 Mon Sep 17 00:00:00 2001 From: Sam Fox Date: Mon, 28 Jun 2021 12:41:05 -0700 Subject: [PATCH 08/14] Update database/sqlserver/README.md Co-authored-by: Keegan Campbell --- database/sqlserver/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/sqlserver/README.md b/database/sqlserver/README.md index 0a81aca7a..900373f3b 100644 --- a/database/sqlserver/README.md +++ b/database/sqlserver/README.md @@ -16,7 +16,7 @@ | `dial+timeout` | | in seconds (default is 15), set to 0 for no timeout. | | `encrypt` | | `disable` - Data send between client and server is not encrypted. `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). | -| `useMsi` | | `true` - Use Azure MSI Authentication for connecting to Sql Server. Must be running from an Azure VM/an instance with MSI enabled. `false` - Use password authentication (Default). Azure MSI Auth details: https://docs.microsoft.com/en-us/azure/app-service/app-service-web-tutorial-connect-msi. +| `useMsi` | | `true` - Use Azure MSI Authentication for connecting to Sql Server. Must be running from an Azure VM/an instance with MSI enabled. `false` - Use password authentication (Default). See [here for Azure MSI Auth details](https://docs.microsoft.com/en-us/azure/app-service/app-service-web-tutorial-connect-msi). See https://github.com/denisenkom/go-mssqldb for full parameter list. From 138aa4d6f9d62c489deec5b499a0aa0f17ddb7dc Mon Sep 17 00:00:00 2001 From: Sam Fox Date: Tue, 29 Jun 2021 11:01:29 -0400 Subject: [PATCH 09/14] Update sqlserver.go refactor resource uri logic into its own method --- database/sqlserver/sqlserver.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/database/sqlserver/sqlserver.go b/database/sqlserver/sqlserver.go index 8dcbe630c..c90a9607f 100644 --- a/database/sqlserver/sqlserver.go +++ b/database/sqlserver/sqlserver.go @@ -137,14 +137,14 @@ func (ss *SQLServer) Open(url string) (database.Driver, error) { var db *sql.DB if useMsi { - tokenProvider, err := getMSITokenProvider(fmt.Sprintf("%s%s", "https://", strings.Join(strings.Split(purl.Hostname(), ".")[1:], "."))) + resource := getAADResourceFromServerUri(purl) + tokenProvider, err := getMSITokenProvider(resource) if err != nil { return nil, err } connector, err := mssql.NewAccessTokenConnector( migrate.FilterCustomQuery(purl).String(), tokenProvider) - if err != nil { return nil, err } @@ -387,3 +387,10 @@ func getMSITokenProvider(resource string) (func() (string, error), error) { return token, nil }, nil } + +// The sql server resource can change across clouds so get it +// dynamically based on the server uri. +// ex. .database.windows.net -> https://database.windows.net +func getAADResourceFromServerUri(purl *nurl.URL) string { + return fmt.Sprintf("%s%s", "https://", strings.Join(strings.Split(purl.Hostname(), ".")[1:], ".")) +} From c5844f88c163d8480a1ebcd51d65e4f72263a430 Mon Sep 17 00:00:00 2001 From: Sam Fox Date: Tue, 29 Jun 2021 11:57:54 -0400 Subject: [PATCH 10/14] Update sqlserver_test.go add tests for msi connection. can only test whether it fails with useMsi= true, or succeeds with useMsi=false --- database/sqlserver/sqlserver_test.go | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/database/sqlserver/sqlserver_test.go b/database/sqlserver/sqlserver_test.go index 7bf393759..0c9c69db0 100644 --- a/database/sqlserver/sqlserver_test.go +++ b/database/sqlserver/sqlserver_test.go @@ -38,6 +38,10 @@ func msConnectionString(host, port string) string { return fmt.Sprintf("sqlserver://sa:%v@%v:%v?database=master", saPassword, host, port) } +func msConnectionStringMsi(host, port string, useMsi bool) string { + return fmt.Sprintf("sqlserver://sa:%v@%v:%v?database=master&useMsi=%t", saPassword, host, port, useMsi) +} + func isReady(ctx context.Context, c dktest.ContainerInfo) bool { ip, port, err := c.Port(defaultPort) if err != nil { @@ -218,3 +222,43 @@ func TestLockWorks(t *testing.T) { } }) } + +func TestMsiTrue(t *testing.T) { + dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { + ip, port, err := c.Port(defaultPort) + if err != nil { + t.Fatal(err) + } + + addr := msConnectionStringMsi(ip, port, true) + p := &SQLServer{} + _, err = p.Open(addr) + if err == nil { + t.Fatal("MSI should fail when not running in an Azure context.") + } + }) +} + +func TestMsiFalse(t *testing.T) { + dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { + ip, port, err := c.Port(defaultPort) + if err != nil { + t.Fatal(err) + } + + addr := msConnectionStringMsi(ip, port, false) + p := &SQLServer{} + d, err := p.Open(addr) + if err != nil { + t.Fatalf("%v", err) + } + + defer func() { + if err := d.Close(); err != nil { + t.Error(err) + } + }() + + dt.Test(t, d, []byte("SELECT 1")) + }) +} From ff3903a312b80a05a3d019cece2127c42be5a8e3 Mon Sep 17 00:00:00 2001 From: Sam Fox Date: Tue, 29 Jun 2021 12:38:37 -0400 Subject: [PATCH 11/14] Update sqlserver.go check msi.EnsureFresh return value --- database/sqlserver/sqlserver.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/database/sqlserver/sqlserver.go b/database/sqlserver/sqlserver.go index dab2b8245..2aece332d 100644 --- a/database/sqlserver/sqlserver.go +++ b/database/sqlserver/sqlserver.go @@ -4,13 +4,14 @@ import ( "context" "database/sql" "fmt" - "go.uber.org/atomic" "io" "io/ioutil" nurl "net/url" "strconv" "strings" + "go.uber.org/atomic" + "github.com/Azure/go-autorest/autorest/adal" mssql "github.com/denisenkom/go-mssqldb" // mssql support "github.com/golang-migrate/migrate/v4" @@ -377,7 +378,10 @@ func getMSITokenProvider(resource string) (func() (string, error), error) { } return func() (string, error) { - msi.EnsureFresh() + err := msi.EnsureFresh() + if err != nil { + return "", err + } token := msi.OAuthToken() return token, nil }, nil From d8f36677bad54c43b97a9ea6add50335b3115ead Mon Sep 17 00:00:00 2001 From: Sam Fox Date: Thu, 1 Jul 2021 14:00:59 -0400 Subject: [PATCH 12/14] Return error for multiple auth and move query filter move migrate.FilterCustomQuery(purl).String() into one line out of if/else. return error if both useMsi=true and password are passed --- database/sqlserver/sqlserver.go | 19 +++++++++----- database/sqlserver/sqlserver_test.go | 37 +++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/database/sqlserver/sqlserver.go b/database/sqlserver/sqlserver.go index 2aece332d..024001871 100644 --- a/database/sqlserver/sqlserver.go +++ b/database/sqlserver/sqlserver.go @@ -27,10 +27,11 @@ func init() { var DefaultMigrationsTable = "schema_migrations" var ( - ErrNilConfig = fmt.Errorf("no config") - ErrNoDatabaseName = fmt.Errorf("no database name") - ErrNoSchema = fmt.Errorf("no schema") - ErrDatabaseDirty = fmt.Errorf("database is dirty") + ErrNilConfig = fmt.Errorf("no config") + ErrNoDatabaseName = fmt.Errorf("no database name") + ErrNoSchema = fmt.Errorf("no schema") + ErrDatabaseDirty = fmt.Errorf("database is dirty") + ErrMultipleAuthOptionsPassed = fmt.Errorf("both password and useMsi=true were passed.") ) var lockErrorMap = map[mssql.ReturnStatus]string{ @@ -137,6 +138,12 @@ func (ss *SQLServer) Open(url string) (database.Driver, error) { } } + if _, isPasswordSet := purl.User.Password(); useMsi && isPasswordSet { + return nil, ErrMultipleAuthOptionsPassed + } + + filteredURL := migrate.FilterCustomQuery(purl).String() + var db *sql.DB if useMsi { resource := getAADResourceFromServerUri(purl) @@ -146,7 +153,7 @@ func (ss *SQLServer) Open(url string) (database.Driver, error) { } connector, err := mssql.NewAccessTokenConnector( - migrate.FilterCustomQuery(purl).String(), tokenProvider) + filteredURL, tokenProvider) if err != nil { return nil, err } @@ -154,7 +161,7 @@ func (ss *SQLServer) Open(url string) (database.Driver, error) { db = sql.OpenDB(connector) } else { - db, err = sql.Open("sqlserver", migrate.FilterCustomQuery(purl).String()) + db, err = sql.Open("sqlserver", filteredURL) if err != nil { return nil, err } diff --git a/database/sqlserver/sqlserver_test.go b/database/sqlserver/sqlserver_test.go index 0c9c69db0..704b5b452 100644 --- a/database/sqlserver/sqlserver_test.go +++ b/database/sqlserver/sqlserver_test.go @@ -38,10 +38,14 @@ func msConnectionString(host, port string) string { return fmt.Sprintf("sqlserver://sa:%v@%v:%v?database=master", saPassword, host, port) } -func msConnectionStringMsi(host, port string, useMsi bool) string { +func msConnectionStringMsiWithPassword(host, port string, useMsi bool) string { return fmt.Sprintf("sqlserver://sa:%v@%v:%v?database=master&useMsi=%t", saPassword, host, port, useMsi) } +func msConnectionStringMsi(host, port string, useMsi bool) string { + return fmt.Sprintf("sqlserver://sa@%v:%v?database=master&useMsi=%t", host, port, useMsi) +} + func isReady(ctx context.Context, c dktest.ContainerInfo) bool { ip, port, err := c.Port(defaultPort) if err != nil { @@ -239,6 +243,37 @@ func TestMsiTrue(t *testing.T) { }) } +func TestOpenWithPasswordAndMSI(t *testing.T) { + dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { + ip, port, err := c.Port(defaultPort) + if err != nil { + t.Fatal(err) + } + + addr := msConnectionStringMsiWithPassword(ip, port, true) + p := &SQLServer{} + _, err = p.Open(addr) + if err == nil { + t.Fatal("Open should fail when both password and useMsi=true are passed.") + } + + addr = msConnectionStringMsiWithPassword(ip, port, false) + p = &SQLServer{} + d, err := p.Open(addr) + if err != nil { + t.Fatal(err) + } + + defer func() { + if err := d.Close(); err != nil { + t.Error(err) + } + }() + + dt.Test(t, d, []byte("SELECT 1")) + }) +} + func TestMsiFalse(t *testing.T) { dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { ip, port, err := c.Port(defaultPort) From a58db03bc7b01f2a0155f0d40b373583edaecf3d Mon Sep 17 00:00:00 2001 From: Sam Fox Date: Thu, 1 Jul 2021 14:03:18 -0400 Subject: [PATCH 13/14] Update README.md update readme with warning for useMsi --- database/sqlserver/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/sqlserver/README.md b/database/sqlserver/README.md index 900373f3b..c4ef5a3a3 100644 --- a/database/sqlserver/README.md +++ b/database/sqlserver/README.md @@ -16,7 +16,7 @@ | `dial+timeout` | | in seconds (default is 15), set to 0 for no timeout. | | `encrypt` | | `disable` - Data send between client and server is not encrypted. `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). | -| `useMsi` | | `true` - Use Azure MSI Authentication for connecting to Sql Server. Must be running from an Azure VM/an instance with MSI enabled. `false` - Use password authentication (Default). See [here for Azure MSI Auth details](https://docs.microsoft.com/en-us/azure/app-service/app-service-web-tutorial-connect-msi). +| `useMsi` | | `true` - Use Azure MSI Authentication for connecting to Sql Server. Must be running from an Azure VM/an instance with MSI enabled. `false` - Use password authentication (Default). See [here for Azure MSI Auth details](https://docs.microsoft.com/en-us/azure/app-service/app-service-web-tutorial-connect-msi). NOTE: Since this cannot be tested locally, this is not officially supported. See https://github.com/denisenkom/go-mssqldb for full parameter list. From ba583bf09a87e33d5ddf967dfdb67eff1dda9050 Mon Sep 17 00:00:00 2001 From: Sam Fox Date: Thu, 1 Jul 2021 15:04:06 -0400 Subject: [PATCH 14/14] Update sqlserver_test.go Update TestMsiFalse test case as now it should fail when useMsi=false and no password is provided --- database/sqlserver/sqlserver_test.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/database/sqlserver/sqlserver_test.go b/database/sqlserver/sqlserver_test.go index 704b5b452..ad0dc79ed 100644 --- a/database/sqlserver/sqlserver_test.go +++ b/database/sqlserver/sqlserver_test.go @@ -283,17 +283,9 @@ func TestMsiFalse(t *testing.T) { addr := msConnectionStringMsi(ip, port, false) p := &SQLServer{} - d, err := p.Open(addr) - if err != nil { - t.Fatalf("%v", err) + _, err = p.Open(addr) + if err == nil { + t.Fatal("Open should fail since no password was passed and useMsi is false.") } - - defer func() { - if err := d.Close(); err != nil { - t.Error(err) - } - }() - - dt.Test(t, d, []byte("SELECT 1")) }) }