-
Notifications
You must be signed in to change notification settings - Fork 502
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update documentation for Azure AD authentication and add examples
- Loading branch information
1 parent
2b05e75
commit df5b6c5
Showing
10 changed files
with
982 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,10 +54,11 @@ Other supported formats are listed below. | |
* true - Server certificate is not checked. Default is true if encrypt is not specified. If trust server certificate is true, driver accepts any certificate presented by the server and any host name in that certificate. In this mode, TLS is susceptible to man-in-the-middle attacks. This should be used only for testing. | ||
* `certificate` - The file that contains the public key certificate of the CA that signed the SQL Server certificate. The specified certificate overrides the go platform specific CA certificates. | ||
* `hostNameInCertificate` - Specifies the Common Name (CN) in the server certificate. Default value is the server host. | ||
* `ServerSPN` - The kerberos SPN (Service Principal Name) for the server. Default is MSSQLSvc/host:port. | ||
* `ServerSPN` - The Kerberos SPN (Service Principal Name) for the server. Default is MSSQLSvc/host:port. | ||
* `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`. | ||
|
||
|
||
### The connection string can be specified in one of three formats: | ||
|
||
|
||
|
@@ -106,25 +107,88 @@ 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}` // Escaped `} with `}}`, password is "foo}bar" | ||
|
||
### Azure Active Directory authentication - preview | ||
### Azure Active Directory authentication | ||
|
||
The configuration of functionality might change in the future. | ||
Azure Active Directory authentication uses temporary authentication tokens to authenticate. | ||
The `mssql` package does not provide an implementation to obtain tokens: instead, import the | ||
`azuread` package and use driver name `azuresql`. This driver uses the | ||
[Active Directory Authentication Library for Go](https://github.com/Azure/go-autorest/tree/master/autorest/adal) | ||
to obtain Azure Active Directory authentication tokens. | ||
|
||
Azure Active Directory (AAD) access tokens are relatively short lived and need to be | ||
valid when a new connection is made. Authentication is supported using a callback func that | ||
provides a fresh and valid token using a connector: | ||
``` golang | ||
conn, err := mssql.NewAccessTokenConnector( | ||
"Server=test.database.windows.net;Database=testdb", | ||
tokenProvider) | ||
if err != nil { | ||
// handle errors in DSN | ||
Authentication using Active Directory is enabled using the `fedauth` connection parameter, | ||
in combination with the `user id` and `password` (or URL username and password). | ||
|
||
* `fedauth=ActiveDirectoryApplication` - authenticates using an Azure Active Directory application client ID and client secret or certificate. | ||
|
||
Set the `user id` to `clientID@tenantID` for your service principal. If using a client secret, set the `password` to the client secret. If using client certificates, provide the path to the PEM file containing the certificate concatenated with the RSA private key in the `clientcertpath` parameter, and set the `password` to the passphrase needed to decrypt the RSA private key (omit or leave blank if unencrypted). | ||
|
||
* `fedauth=ActiveDirectoryMSI` - authenticates using the managed service identity (MSI) attached to the VM (system identity), or a specific user-assigned identity. | ||
|
||
To select a user-assigned identity, specify a client ID in the `user id` parameter. For the system-assigned identity, leave the `user id` empty. | ||
|
||
* `fedauth=ActiveDirectoryPassword` - authenticates an Azure Active Directory user account. | ||
|
||
Set the `user id` to `[email protected]` and the `password`. This method is not recommended for general use and does not support multi-factor authentication for accounts. | ||
|
||
|
||
```golang | ||
import ( | ||
"database/sql" | ||
"net/url" | ||
|
||
// Import the Azure AD driver module (also imports the regular driver package) | ||
"github.com/denisenkom/go-mssqldb/azuread" | ||
) | ||
|
||
func ConnectWithMSI() (*sql.DB, error) { | ||
return sql.Open(azuread.DriverName, "sqlserver://azuresql.database.windows.net?database=yourdb&fedauth=ActiveDirectoryMSI") | ||
} | ||
``` | ||
|
||
As an alternative, you can select the federated authentication library and Active Directory | ||
using the connection string parameters, but then implement your own routine for obtaining | ||
tokens. The second example shows how this could be used to add in a token for the Azure AD | ||
Integrated authentication scenario. | ||
|
||
```golang | ||
import ( | ||
"context" | ||
"database/sql" | ||
"net/url" | ||
|
||
// Import the driver | ||
"github.com/denisenkom/go-mssqldb" | ||
) | ||
|
||
func ConnectWithSecurityToken() (*sql.DB, error) { | ||
conn, err := mssql.NewSecurityTokenConnector( | ||
"sqlserver://azuresql.database.windows.net?database=yourdb", | ||
func(ctx context.Context) (string, error) { | ||
return "the token", nil | ||
}, | ||
) | ||
if err != nil { | ||
// handle errors in DSN | ||
} | ||
|
||
return sql.OpenDB(conn), nil | ||
} | ||
|
||
func ConnectWithADIntegrated() (*sql.DB, error) { | ||
conn, err := mssql.NewActiveDirectoryTokenConnector( | ||
"sqlserver://azuresq;.database.windows.net?database=yourdb", | ||
2, // Active Directory workflow: 1 = user/password, 2 = integrated, 3 = MSI | ||
func(ctx context.Context, serverSPN, stsURL string) (string, error) { | ||
return "the token", nil | ||
}, | ||
) | ||
if err != nil { | ||
// handle errors in DSN | ||
} | ||
|
||
return sql.OpenDB(conn), nil | ||
} | ||
db := sql.OpenDB(conn) | ||
``` | ||
Where `tokenProvider` is a function that returns a fresh access token or an error. None of these statements | ||
actually trigger the retrieval of a token, this happens when the first statment is issued and a connection | ||
is created. | ||
|
||
## Executing Stored Procedures | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
# How to test Azure AD authentication | ||
|
||
To test Azure AD authentication requires an Azure SQL server configured with an | ||
[Active Directory administrator](https://docs.microsoft.com/en-us/azure/sql-database/sql-database-aad-authentication-configure). | ||
To test managed identity authentication, an Azure virtual machine configured with | ||
[system-assigned and/or user-assigned identities](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/qs-configure-portal-windows-vm) | ||
is also required. | ||
|
||
The necessary resources can be set up through any means including the | ||
[Azure Portal](https://portal.azure.com/), the Azure CLI, the Azure PowerShell cmdlets or | ||
[Terraform](https://terraform.io/). To support these instructions, use the Terraform script at | ||
[examples/azuread/testing.tf](../examples/azuread/testing.tf). | ||
|
||
## Create Azure infrastructure | ||
|
||
Download [Terraform](https://terraform.io/) to a location on your PATH. | ||
|
||
Log in to Azure using the Azure CLI. | ||
|
||
```console | ||
you@workstation:~$ az login | ||
you@workstation:~$ az account show | ||
``` | ||
|
||
If your Azure account has access to multiple subscriptions, use | ||
`az account set --subscription <name-or-ID>` to choose the correct one. You will need to have at | ||
least Contributor access to the portal and permissions in Azure Active Directory to create users | ||
and grants. | ||
|
||
Check out this source repository (if you haven't already), change to the `examples/azuread` | ||
directory and run Terraform: | ||
|
||
```console | ||
you@workstation:~$ git clone -b azure-auth https://github.com/wrosenuance/go-mssqldb.git | ||
you@workstation:~$ cd go-mssqldb/examples/azuread | ||
you@workstation:azuread$ terraform init | ||
you@workstation:azuread$ terraform apply | ||
``` | ||
|
||
This will create an Azure resource group, a SQL server with a database, a virtual machine with a | ||
system-assigned identity and user-assigned identity. Resources are named based on a random | ||
prefix: to specify the prefix, use `terraform apply -var prefix=<alphanumeric-and-hyphens-ok>`. | ||
|
||
Upon successful completion, Terraform will display some key details of the infrastructure that has | ||
been created. This includes the SSH key to access the VM, the administrator account and password | ||
for the Azure SQL server, and all the relevant resource names. | ||
|
||
Save the settings to a JSON file: | ||
|
||
```console | ||
you@workstation:azuread$ terraform output -json > settings.json | ||
``` | ||
|
||
Save the SSH private key to a file: | ||
|
||
```console | ||
you@workstation:azuread$ terraform output vm_user_ssh_private_key > ssh-identity | ||
``` | ||
|
||
Copy the `settings.json` to the new VM: | ||
|
||
```console | ||
you@workstation:azuread$ eval "VM_ADMIN_NAME=$(terraform output vm_admin_name)" | ||
you@workstation:azuread$ eval "VM_IP_ADDRESS=$(terraform output vm_ip_address)" | ||
you@workstation:azuread$ scp -i ssh-identity settings.json "${VM_ADMIN_NAME}@${VM_IP_ADDRESS}:" | ||
``` | ||
|
||
## Set up Azure Virtual Machine for testing | ||
|
||
SSH to the new VM to continue setup: | ||
|
||
```console | ||
you@workstation:azuread$ ssh -i ssh-identity "${VM_ADMIN_NAME}@${VM_IP_ADDRESS}" | ||
``` | ||
|
||
Once on the VM, update the system and install some basic packages: | ||
|
||
```console | ||
azureuser@azure-vm:~$ sudo apt update -y | ||
azureuser@azure-vm:~$ sudo apt upgrade -y | ||
azureuser@azure-vm:~$ sudo apt install -y git openssl jq build-essential | ||
azureuser@azure-vm:~$ sudo snap install go --classic | ||
``` | ||
|
||
Install the Azure CLI using the script as shown below, or follow the | ||
[manual install instructions](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-apt): | ||
|
||
```console | ||
azureuser@azure-vm:~$ curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash | ||
``` | ||
|
||
## Generate service principal certificate file | ||
|
||
Log in to Azure on the VM and set the subscription: | ||
|
||
```console | ||
azureuser@azure-vm:~$ az login | ||
azureuser@azure-vm:~$ az account set --subscription "$(jq -r '.subscription_id.value' settings.json)" | ||
``` | ||
|
||
Use OpenSSL to create a new certificate and key in PEM format, using the : | ||
|
||
```console | ||
azureuser@azure-vm:~$ openssl rand -writerand ~/.rnd | ||
azureuser@azure-vm:~$ openssl req -x509 -nodes -newkey rsa:4096 -keyout client.key -out client.crt \ | ||
-subj "/C=US/ST=MA/L=Boston/O=Global Security/OU=IT Department/CN=AD-SP" | ||
azureuser@azure-vm:~$ openssl rsa -out client.pem -in client.key -aes256 \ | ||
-passout "pass:$(jq -r '.app_sp_client_secret.value' settings.json)" | ||
azureuser@azure-vm:~$ cat client.crt >> client.pem | ||
azureuser@azure-vm:~$ export APP_SP_CLIENT_CERT="$PWD/client.pem" | ||
``` | ||
|
||
Use the Azure CLI to add the client certificate to the application service principal: | ||
|
||
```console | ||
azureuser@azure-vm:~$ az ad sp credential reset --append --cert @client.crt \ | ||
--name "$(jq -r '.app_sp_client_id.value' settings.json)" | ||
``` | ||
|
||
## Build source code and authorize users in database | ||
|
||
Clone this repository, build and run the `examples/azuread` helper that verifies the database | ||
exists and sets up access for the system-assigned and user-assigned identities. | ||
|
||
```console | ||
azureuser@azure-vm:~$ git clone -b azure-auth https://github.com/wrosenuance/go-mssqldb.git | ||
azureuser@azure-vm:~$ cd go-mssqldb | ||
azureuser@azure-vm:go-mssqldb$ (cd ./examples/azuread; go build -o ../../azuread-example .) | ||
azureuser@azure-vm:go-mssqldb$ eval "$(jq -r -f examples/azuread/environment-settings.jq ../settings.json)" | ||
azureuser@azure-vm:go-mssqldb$ ./azuread-example -fedauth ActiveDirectoryPassword | ||
``` | ||
|
||
For some basic connectivity tests, use the `examples/simple` helper. Run these commands on the | ||
Azure VM so that identity authentication is possible. | ||
|
||
```console | ||
azureuser@azure-vm:go-mssqldb$ eval "$(jq -r --arg certpath "$(realpath ../client.pem)" -f examples/azuread/dsn-variables.jq ../settings.json)" | ||
azureuser@azure-vm:go-mssqldb$ go build -o simple ./examples/simple | ||
azureuser@azure-vm:go-mssqldb$ ./simple -debug -dsn "$AD_APP_CERT_DSN" | ||
azureuser@azure-vm:go-mssqldb$ ./simple -debug -dsn "$AD_APP_PWD_DSN" | ||
azureuser@azure-vm:go-mssqldb$ ./simple -debug -dsn "$AD_MSI_SYS_DSN" | ||
azureuser@azure-vm:go-mssqldb$ ./simple -debug -dsn "$AD_MSI_USER_DSN" | ||
azureuser@azure-vm:go-mssqldb$ ./simple -debug -dsn "$AD_USER_PWD_DSN" | ||
azureuser@azure-vm:go-mssqldb$ ./simple -debug -dsn "$SQL_USER_PWD_DSN" | ||
``` | ||
|
||
## Running the integration tests | ||
|
||
Now that your environment is configured, you can run `go test`: | ||
|
||
```console | ||
azureuser@azure-vm:go-mssqldb$ export SQLSERVER_DSN="$AD_APP_CERT_DSN" | ||
azureuser@azure-vm:go-mssqldb$ go test -coverprofile=coverage.out . ./azuread ./batch ./internal/... | ||
azureuser@azure-vm:go-mssqldb$ go tool cover -html=coverage.out -o coverage.html | ||
``` | ||
|
||
## Tear down environment | ||
|
||
After you complete your testing, use Terraform to destroy the infrastructure you created. | ||
|
||
```console | ||
you@workstation:azuread$ terraform destroy | ||
``` | ||
|
||
## Troubleshooting | ||
|
||
After Terraform runs you should be able to see resources that were created in the | ||
[Azure Portal](https://portal.azure.com/). | ||
|
||
If the Azure SQL server is successfully created you can connect to it using the AD admin user | ||
and password in SSMS. SSMS will prompt you to create firewall rules if they are missing. You | ||
can read the AD admin user and password from the `settings.json`, or run: | ||
|
||
```console | ||
you@workstation:azuread$ terraform output sql_ad_admin_user | ||
you@workstation:azuread$ terraform output sql_ad_admin_password | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
settings.json | ||
ssh-identity |
Oops, something went wrong.