Skip to content

Commit

Permalink
Add azure config provider block (#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
rneacsu authored May 8, 2024
1 parent aa94599 commit 349b210
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 10 deletions.
106 changes: 98 additions & 8 deletions mysql/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"database/sql"
"encoding/json"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"log"
"net"
"net/url"
Expand All @@ -20,6 +19,7 @@ import (
"github.com/go-sql-driver/mysql"
"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"

Expand All @@ -28,6 +28,7 @@ import (

"cloud.google.com/go/cloudsqlconn"
cloudsql "cloud.google.com/go/cloudsqlconn/mysql/mysql"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
)
Expand All @@ -37,6 +38,10 @@ const (
nativePasswords = "native"
userNotFoundErrCode = 1133
unknownUserErrCode = 1396
azEnvPublic = "public"
azEnvChina = "china"
azEnvGerman = "german"
azEnvUSGovernment = "usgovernment"
)

type OneConnection struct {
Expand Down Expand Up @@ -106,7 +111,7 @@ func Provider() *schema.Provider {
"ALL_PROXY",
"all_proxy",
}, nil),
ValidateFunc: validation.StringMatch(regexp.MustCompile("^socks5h?://.*:\\d+$"), "The proxy URL is not a valid socks url."),
ValidateFunc: validation.StringMatch(regexp.MustCompile(`^socks5h?://.*:\d+$`), "The proxy URL is not a valid socks url."),
},

"tls": {
Expand Down Expand Up @@ -186,6 +191,53 @@ func Provider() *schema.Provider {
Optional: true,
Default: false,
},
"azure_config": {
Type: schema.TypeList,
Optional: true,
Default: nil,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"tenant_id": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
"AZURE_TENANT_ID",
"ARM_TENANT_ID",
}, nil),
},
"client_id": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
"AZURE_CLIENT_ID",
"ARM_CLIENT_ID",
}, nil),
},
"client_secret": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
"AZURE_CLIENT_SECRET",
"ARM_CLIENT_SECRET",
}, nil),
},
"environment": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
azEnvPublic,
azEnvChina,
azEnvGerman,
azEnvUSGovernment,
}, false),
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
"AZURE_ENVIRONMENT",
"ARM_ENVIRONMENT",
}, nil),
},
},
},
},
},

DataSourcesMap: map[string]*schema.Resource{
Expand Down Expand Up @@ -303,18 +355,56 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}
}

} else if strings.HasPrefix(endpoint, "azure://") {
var azCredential azcore.TokenCredential
var azTenantId, azClientId, azClientSecret, azEnvironment string
var err error

azEnvironment = os.Getenv("AZURE_ENVIRONMENT")
if azEnvironment == "" {
azEnvironment = os.Getenv("ARM_ENVIRONMENT")
}

azAuthList := d.Get("azure_config").([]interface{})
if len(azAuthList) > 0 {
azAuthMap := azAuthList[0].(map[string]interface{})
if azAuthMap["tenant_id"] != nil {
azTenantId = azAuthMap["tenant_id"].(string)
}
if azAuthMap["client_id"] != nil {
azClientId = azAuthMap["client_id"].(string)
}
if azAuthMap["client_secret"] != nil {
azClientSecret = azAuthMap["client_secret"].(string)
}
if azAuthMap["environment"] != nil {
azEnvironment = azAuthMap["environment"].(string)
}
}

if azTenantId != "" && azClientId != "" && azClientSecret != "" {
log.Printf("[DEBUG] Using Azure Client Secret Credentials: client_id = %s, tenant_id = %s", azClientId, azTenantId)
azCredential, err = azidentity.NewClientSecretCredential(azTenantId, azClientId, azClientSecret, nil)
} else {
log.Printf("[DEBUG] Using Azure Default Credentials")
azCredential, err = azidentity.NewDefaultAzureCredential(nil)
}
// Azure AD does not support native password authentication but go-sql-driver/mysql
// has to be configured only with ?allowClearTextPasswords=true not with allowNativePasswords=false in this case
allowClearTextPasswords = true
azCredential, err := azidentity.NewDefaultAzureCredential(nil)
endpoint = strings.ReplaceAll(endpoint, "azure://", "")
azScope := "https://ossrdbms-aad.database.windows.net"
if os.Getenv("ARM_ENVIRONMENT") == "china" {

var azScope string
switch azEnvironment {
case azEnvChina:
azScope = "https://ossrdbms-aad.database.chinacloudapi.cn"
} else if os.Getenv("ARM_ENVIRONMENT") == "german" {
case azEnvGerman:
azScope = "https://ossrdbms-aad.database.chinacloudapi.de"
} else if os.Getenv("ARM_ENVIRONMENT") == "usgovernment" {
case azEnvUSGovernment:
azScope = "https://ossrdbms-aad.database.usgovcloudapi.net"
case azEnvPublic:
fallthrough
default:
azScope = "https://ossrdbms-aad.database.windows.net"
}

if err != nil {
Expand All @@ -327,7 +417,7 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}
)

if err != nil {
return nil, diag.Errorf("failed to get token from Azure AD %v", err)
return nil, diag.Errorf("failed to get token from Azure AD: %v", err)
}

password = azToken.Token
Expand Down
25 changes: 23 additions & 2 deletions website/docs/index.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,6 @@ See also: [Authentication at Google](https://cloud.google.com/docs/authenticatio

### Azure MySQL server with AzureAD auth enabled connection

For connections to Azure MySQL server with AzureAD auth enabled, the provider connects using DefaultAzureCredential from the Azure SDK for Go.

To use this authentication, add `azure://` to the endpoint. This will lead to ignore `password` field which would be replaced by Azure AD
token of currently obtained identity. You have to use `username` as stated in Azure documentation.

Expand All @@ -157,6 +155,24 @@ provider "mysql" {
}
```

By default the provider will connect using DefaultAzureCredential from the Azure SDK for Go. The credentials can be provided by setting the `AZURE_*` environment variables, using a workload identity or a managed identity present on the host.

You can also further configure the Azure connection using the `azure_config` block:

```hcl
# Configure the MySQL provider for Azure Mysql Server with specific credentials
provider "mysql" {
endpoint = "azure://your-azure-instance-name.mysql.database.azure.com"
username = "username@yourtenant.onmicrosoft.com"
azure_config {
tenant_id = "your-tenant-id"
client_id = "your-client-id"
client_secret = var.client_secret
}
}
```

See also: [Azure Active Directory authentication for MySQL](https://learn.microsoft.com/en-us/azure/mysql/flexible-server/how-to-azure-ad).

## SOCKS5 Proxy Support
Expand Down Expand Up @@ -187,3 +203,8 @@ The following arguments are supported:
* `authentication_plugin` - (Optional) Sets the authentication plugin, it can be one of the following: `native` or `cleartext`. Defaults to `native`.
* `iam_database_authentication` - (Optional) For Cloud SQL databases, it enabled the use of IAM authentication. Make sure to declare the `password` field with a temporary OAuth2 token of the user that will connect to the MySQL server.
* `private_ip` - (Optional) Whether to use a connection to an instance with a private ip. Defaults to `false`. This argument only applies to CloudSQL and is ignored elsewhere.
* `azure_config` - (Optional) Sets the Azure configuration for the connection. This is a block containing the following arguments:
* `client_id` - (Optional) The client ID for the Azure AD application. Can also be sourced from the `AZURE_CLIENT_ID` or `ARM_CLIENT_ID` environment variables.
* `client_secret` - (Optional) The client secret for the Azure AD application. Can also be sourced from the `AZURE_CLIENT_SECRET` or `ARM_CLIENT_SECRET` environment variables.
* `tenant_id` - (Optional) The tenant ID for the Azure AD application. Can also be sourced from the `AZURE_TENANT_ID` or `ARM_TENANT_ID` environment variables.
* `environment` - (Optional) The Azure environment to use. Can also be sourced from the `AZURE_ENVIRONMENT` or `ARM_ENVIRONMENT` environment variables. Possible values are `public`, `china`, `german`, `usgovernment`. Defaults to `public`.

0 comments on commit 349b210

Please sign in to comment.