Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Export auth interface to allow external implementations e.g. krb5 - V1-Candidate #15

Merged
merged 14 commits into from
Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ install:
- go env
- go get -u github.com/golang-sql/civil
- go get -u github.com/golang-sql/sqlexp

- go get -u golang.org/x/crypto/md4
build_script:
- go build

Expand Down
15 changes: 15 additions & 0 deletions auth_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// +build !windows

package mssql

import (
"github.com/microsoft/go-mssqldb/integratedauth"
// nolint importing the ntlm package causes it to be registered as an available authentication provider
_ "github.com/microsoft/go-mssqldb/integratedauth/ntlm"
)

func init() {
// we set the default authentication provider name here, rather than within each imported package,
// to force a known default. Go will order execution of init() calls but it is better to be explicit.
integratedauth.DefaultProviderName = "ntlm"
}
18 changes: 18 additions & 0 deletions auth_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// +build windows

package mssql

import (
"github.com/microsoft/go-mssqldb/integratedauth"

// nolint importing the ntlm package causes it to be registered as an available authentication provider
_ "github.com/microsoft/go-mssqldb/integratedauth/ntlm"
// nolint importing the winsspi package causes it to be registered as an available authentication provider
_ "github.com/microsoft/go-mssqldb/integratedauth/winsspi"
)

func init() {
// we set the default authentication provider name here, rather than within each imported package,
// to force a known default. Go will order execution of init() calls but it is better to be explicit.
integratedauth.DefaultProviderName = "winsspi"
}
4 changes: 2 additions & 2 deletions azuread/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ type azureFedAuthConfig struct {

// parse returns a config based on an msdsn-style connection string
func parse(dsn string) (*azureFedAuthConfig, error) {
mssqlConfig, params, err := msdsn.Parse(dsn)
mssqlConfig, err := msdsn.Parse(dsn)
if err != nil {
return nil, err
}
Expand All @@ -62,7 +62,7 @@ func parse(dsn string) (*azureFedAuthConfig, error) {
mssqlConfig: mssqlConfig,
}

err = config.validateParameters(params)
err = config.validateParameters(mssqlConfig.Parameters)
if err != nil {
return nil, err
}
Expand Down
256 changes: 128 additions & 128 deletions azuread/configuration_test.go
Original file line number Diff line number Diff line change
@@ -1,128 +1,128 @@
//go:build go1.18
// +build go1.18
package azuread
import (
"testing"
mssql "github.com/microsoft/go-mssqldb"
"github.com/microsoft/go-mssqldb/msdsn"
)
func TestValidateParameters(t *testing.T) {
passphrase := "somesecret"
certificatepath := "/user/cert/cert.pfx"
appid := "applicationclientid=someguid"
certprop := "clientcertpath=" + certificatepath
tests := []struct {
name string
dsn string
expected *azureFedAuthConfig
}{
{
name: "no fed auth configured",
dsn: "server=someserver",
expected: &azureFedAuthConfig{fedAuthLibrary: mssql.FedAuthLibraryReserved},
},
{
name: "application with cert/key",
dsn: `sqlserver://service-principal-id%40tenant-id:somesecret@someserver.database.windows.net?fedauth=ActiveDirectoryApplication&` + certprop + "&" + appid,
expected: &azureFedAuthConfig{
fedAuthLibrary: mssql.FedAuthLibraryADAL,
clientID: "service-principal-id",
tenantID: "tenant-id",
certificatePath: certificatepath,
clientSecret: passphrase,
adalWorkflow: mssql.FedAuthADALWorkflowPassword,
fedAuthWorkflow: ActiveDirectoryApplication,
applicationClientID: "someguid",
},
},
{
name: "application with cert/key missing tenant id",
dsn: "server=someserver.database.windows.net;fedauth=ActiveDirectoryApplication;user id=service-principal-id;password=somesecret;" + certprop + ";" + appid,
expected: &azureFedAuthConfig{
fedAuthLibrary: mssql.FedAuthLibraryADAL,
clientID: "service-principal-id",
certificatePath: certificatepath,
clientSecret: passphrase,
adalWorkflow: mssql.FedAuthADALWorkflowPassword,
fedAuthWorkflow: ActiveDirectoryApplication,
applicationClientID: "someguid",
},
},
{
name: "application with secret",
dsn: "server=someserver.database.windows.net;fedauth=ActiveDirectoryServicePrincipal;user id=service-principal-id@tenant-id;password=somesecret;",
expected: &azureFedAuthConfig{
clientID: "service-principal-id",
tenantID: "tenant-id",
clientSecret: passphrase,
adalWorkflow: mssql.FedAuthADALWorkflowPassword,
fedAuthWorkflow: ActiveDirectoryServicePrincipal,
},
},
{
name: "user with password",
dsn: "server=someserver.database.windows.net;fedauth=ActiveDirectoryPassword;user id=azure-ad-user@example.com;password=somesecret;" + appid,
expected: &azureFedAuthConfig{
adalWorkflow: mssql.FedAuthADALWorkflowPassword,
user: "azure-ad-user@example.com",
password: passphrase,
applicationClientID: "someguid",
fedAuthWorkflow: ActiveDirectoryPassword,
},
},
{
name: "managed identity without client id",
dsn: "server=someserver.database.windows.net;fedauth=ActiveDirectoryMSI",
expected: &azureFedAuthConfig{
adalWorkflow: mssql.FedAuthADALWorkflowMSI,
fedAuthWorkflow: ActiveDirectoryMSI,
},
},
{
name: "managed identity with client id",
dsn: "server=someserver.database.windows.net;fedauth=ActiveDirectoryManagedIdentity;user id=identity-client-id",
expected: &azureFedAuthConfig{
adalWorkflow: mssql.FedAuthADALWorkflowMSI,
clientID: "identity-client-id",
fedAuthWorkflow: ActiveDirectoryManagedIdentity,
},
},
{
name: "managed identity with resource id",
dsn: "server=someserver.database.windows.net;fedauth=ActiveDirectoryManagedIdentity;resource id=/subscriptions/{guid}/resourceGroups/{resource-group-name}/{resource-provider-namespace}/{resource-type}/{resource-name}",
expected: &azureFedAuthConfig{
adalWorkflow: mssql.FedAuthADALWorkflowMSI,
resourceID: "/subscriptions/{guid}/resourceGroups/{resource-group-name}/{resource-provider-namespace}/{resource-type}/{resource-name}",
fedAuthWorkflow: ActiveDirectoryManagedIdentity,
},
},
}
for _, tst := range tests {
config, err := parse(tst.dsn)
if tst.expected == nil {
if err == nil {
t.Errorf("No error returned when error expected in test case '%s'", tst.name)
}
continue
}
if err != nil {
t.Errorf("Error returned when none expected in test case '%s': %v", tst.name, err)
continue
}
if tst.expected.fedAuthLibrary != mssql.FedAuthLibraryReserved {
if tst.expected.fedAuthLibrary == 0 {
tst.expected.fedAuthLibrary = mssql.FedAuthLibraryADAL
}
}
// mssqlConfig is not idempotent due to pointers in it, plus we aren't testing its correctness here
config.mssqlConfig = msdsn.Config{}
if *config != *tst.expected {
t.Errorf("Captured parameters do not match in test case '%s'. Expected:%+v, Actual:%+v", tst.name, tst.expected, config)
}
}
}
//go:build go1.18
// +build go1.18

package azuread

import (
"reflect"
"testing"

mssql "github.com/microsoft/go-mssqldb"
"github.com/microsoft/go-mssqldb/msdsn"
)

func TestValidateParameters(t *testing.T) {
passphrase := "somesecret"
certificatepath := "/user/cert/cert.pfx"
appid := "applicationclientid=someguid"
certprop := "clientcertpath=" + certificatepath
tests := []struct {
name string
dsn string
expected *azureFedAuthConfig
}{
{
name: "no fed auth configured",
dsn: "server=someserver",
expected: &azureFedAuthConfig{fedAuthLibrary: mssql.FedAuthLibraryReserved},
},
{
name: "application with cert/key",
dsn: `sqlserver://service-principal-id%40tenant-id:somesecret@someserver.database.windows.net?fedauth=ActiveDirectoryApplication&` + certprop + "&" + appid,
expected: &azureFedAuthConfig{
fedAuthLibrary: mssql.FedAuthLibraryADAL,
clientID: "service-principal-id",
tenantID: "tenant-id",
certificatePath: certificatepath,
clientSecret: passphrase,
adalWorkflow: mssql.FedAuthADALWorkflowPassword,
fedAuthWorkflow: ActiveDirectoryApplication,
applicationClientID: "someguid",
},
},
{
name: "application with cert/key missing tenant id",
dsn: "server=someserver.database.windows.net;fedauth=ActiveDirectoryApplication;user id=service-principal-id;password=somesecret;" + certprop + ";" + appid,
expected: &azureFedAuthConfig{
fedAuthLibrary: mssql.FedAuthLibraryADAL,
clientID: "service-principal-id",
certificatePath: certificatepath,
clientSecret: passphrase,
adalWorkflow: mssql.FedAuthADALWorkflowPassword,
fedAuthWorkflow: ActiveDirectoryApplication,
applicationClientID: "someguid",
},
},
{
name: "application with secret",
dsn: "server=someserver.database.windows.net;fedauth=ActiveDirectoryServicePrincipal;user id=service-principal-id@tenant-id;password=somesecret;",
expected: &azureFedAuthConfig{
clientID: "service-principal-id",
tenantID: "tenant-id",
clientSecret: passphrase,
adalWorkflow: mssql.FedAuthADALWorkflowPassword,
fedAuthWorkflow: ActiveDirectoryServicePrincipal,
},
},
{
name: "user with password",
dsn: "server=someserver.database.windows.net;fedauth=ActiveDirectoryPassword;user id=azure-ad-user@example.com;password=somesecret;" + appid,
expected: &azureFedAuthConfig{
adalWorkflow: mssql.FedAuthADALWorkflowPassword,
user: "azure-ad-user@example.com",
password: passphrase,
applicationClientID: "someguid",
fedAuthWorkflow: ActiveDirectoryPassword,
},
},
{
name: "managed identity without client id",
dsn: "server=someserver.database.windows.net;fedauth=ActiveDirectoryMSI",
expected: &azureFedAuthConfig{
adalWorkflow: mssql.FedAuthADALWorkflowMSI,
fedAuthWorkflow: ActiveDirectoryMSI,
},
},
{
name: "managed identity with client id",
dsn: "server=someserver.database.windows.net;fedauth=ActiveDirectoryManagedIdentity;user id=identity-client-id",
expected: &azureFedAuthConfig{
adalWorkflow: mssql.FedAuthADALWorkflowMSI,
clientID: "identity-client-id",
fedAuthWorkflow: ActiveDirectoryManagedIdentity,
},
},
{
name: "managed identity with resource id",
dsn: "server=someserver.database.windows.net;fedauth=ActiveDirectoryManagedIdentity;resource id=/subscriptions/{guid}/resourceGroups/{resource-group-name}/{resource-provider-namespace}/{resource-type}/{resource-name}",
expected: &azureFedAuthConfig{
adalWorkflow: mssql.FedAuthADALWorkflowMSI,
resourceID: "/subscriptions/{guid}/resourceGroups/{resource-group-name}/{resource-provider-namespace}/{resource-type}/{resource-name}",
fedAuthWorkflow: ActiveDirectoryManagedIdentity,
},
},
}
for _, tst := range tests {
config, err := parse(tst.dsn)
if tst.expected == nil {
if err == nil {
t.Errorf("No error returned when error expected in test case '%s'", tst.name)
}
continue
}
if err != nil {
t.Errorf("Error returned when none expected in test case '%s': %v", tst.name, err)
continue
}
if tst.expected.fedAuthLibrary != mssql.FedAuthLibraryReserved {
if tst.expected.fedAuthLibrary == 0 {
tst.expected.fedAuthLibrary = mssql.FedAuthLibraryADAL
}
}
// mssqlConfig is not idempotent due to pointers in it, plus we aren't testing its correctness here
config.mssqlConfig = msdsn.Config{}
if !reflect.DeepEqual(config, tst.expected) {
t.Errorf("Captured parameters do not match in test case '%s'. Expected:%+v, Actual:%+v", tst.name, tst.expected, config)
}
}
}
73 changes: 73 additions & 0 deletions integratedauth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package integratedauth

import (
"errors"
"fmt"

"github.com/microsoft/go-mssqldb/msdsn"
)

var (
providers map[string]Provider
DefaultProviderName string

ErrProviderCannotBeNil = errors.New("provider cannot be nil")
ErrProviderNameMustBePopulated = errors.New("provider name must be populated")
)

func init() {
providers = make(map[string]Provider)
}

// GetIntegratedAuthenticator calls the authProvider specified in the 'authenticator' connection string parameter, if supplied.
// Otherwise fails back to the DefaultProviderName implementation for the platform.
func GetIntegratedAuthenticator(config msdsn.Config) (IntegratedAuthenticator, error) {
authenticatorName, ok := config.Parameters["authenticator"]
if !ok {
provider, err := getProvider(DefaultProviderName)
if err != nil {
return nil, err
}

p, err := provider.GetIntegratedAuthenticator(config)
// we ignore the error in this case to force a fallback to sqlserver authentication.
// this preserves the original behaviour
if err != nil {
return nil, nil
}

return p, nil
}

provider, err := getProvider(authenticatorName)
if err != nil {
return nil, err
}

return provider.GetIntegratedAuthenticator(config)
}

func getProvider(name string) (Provider, error) {
provider, ok := providers[name]

if !ok {
return nil, fmt.Errorf("provider %v not found", name)
}

return provider, nil
}

// SetIntegratedAuthenticationProvider stores a named authentication provider. It should be called before any connections are created.
func SetIntegratedAuthenticationProvider(providerName string, p Provider) error {
if p == nil {
return ErrProviderCannotBeNil
}

if providerName == "" {
return ErrProviderNameMustBePopulated
}

providers[providerName] = p

return nil
}
Loading