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

Add Azure configuration block #148

Merged
merged 1 commit into from
May 8, 2024
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
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 (

cloudsqlconn "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"
azidentity "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`.
Loading