diff --git a/azurerm/internal/services/mssql/client/client.go b/azurerm/internal/services/mssql/client/client.go index 7588bcdc51ee..9c3f834b3823 100644 --- a/azurerm/internal/services/mssql/client/client.go +++ b/azurerm/internal/services/mssql/client/client.go @@ -12,6 +12,7 @@ type Client struct { DatabaseThreatDetectionPoliciesClient *sql.DatabaseThreatDetectionPoliciesClient ElasticPoolsClient *sql.ElasticPoolsClient DatabaseVulnerabilityAssessmentRuleBaselinesClient *sql.DatabaseVulnerabilityAssessmentRuleBaselinesClient + ServerAzureADAdministratorsClient *sql.ServerAzureADAdministratorsClient ServersClient *sql.ServersClient ServerExtendedBlobAuditingPoliciesClient *sql.ExtendedServerBlobAuditingPoliciesClient ServerConnectionPoliciesClient *sql.ServerConnectionPoliciesClient @@ -45,6 +46,9 @@ func NewClient(o *common.ClientOptions) *Client { serverVulnerabilityAssessmentsClient := sql.NewServerVulnerabilityAssessmentsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&serverVulnerabilityAssessmentsClient.Client, o.ResourceManagerAuthorizer) + serverAzureADAdministratorsClient := sql.NewServerAzureADAdministratorsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&serverAzureADAdministratorsClient.Client, o.ResourceManagerAuthorizer) + serversClient := sql.NewServersClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&serversClient.Client, o.ResourceManagerAuthorizer) @@ -60,6 +64,7 @@ func NewClient(o *common.ClientOptions) *Client { DatabaseThreatDetectionPoliciesClient: &databaseThreatDetectionPoliciesClient, DatabaseVulnerabilityAssessmentRuleBaselinesClient: &databaseVulnerabilityAssessmentRuleBaselinesClient, ElasticPoolsClient: &elasticPoolsClient, + ServerAzureADAdministratorsClient: &serverAzureADAdministratorsClient, ServersClient: &serversClient, ServerExtendedBlobAuditingPoliciesClient: &serverExtendedBlobAuditingPoliciesClient, ServerConnectionPoliciesClient: &serverConnectionPoliciesClient, diff --git a/azurerm/internal/services/mssql/mssql_server_resource.go b/azurerm/internal/services/mssql/mssql_server_resource.go index 24f91f67a219..e27645b54c3a 100644 --- a/azurerm/internal/services/mssql/mssql_server_resource.go +++ b/azurerm/internal/services/mssql/mssql_server_resource.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/go-azure-helpers/response" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + uuid "github.com/satori/go.uuid" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" @@ -71,6 +72,35 @@ func resourceArmMsSqlServer() *schema.Resource { Sensitive: true, }, + "azuread_administrator": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + MinItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "login_username": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "object_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.IsUUID, + }, + + "tenant_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.IsUUID, + }, + }, + }, + }, + "connection_policy": { Type: schema.TypeString, Optional: true, @@ -129,6 +159,7 @@ func resourceArmMsSqlServerCreateUpdate(d *schema.ResourceData, meta interface{} client := meta.(*clients.Client).MSSQL.ServersClient auditingClient := meta.(*clients.Client).MSSQL.ServerExtendedBlobAuditingPoliciesClient connectionClient := meta.(*clients.Client).MSSQL.ServerConnectionPoliciesClient + adminClient := meta.(*clients.Client).MSSQL.ServerAzureADAdministratorsClient ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) defer cancel() @@ -198,6 +229,28 @@ func resourceArmMsSqlServerCreateUpdate(d *schema.ResourceData, meta interface{} d.SetId(*resp.ID) + if d.HasChange("azuread_administrator") { + adminDelFuture, err := adminClient.Delete(ctx, resGroup, name) + if err != nil { + return fmt.Errorf("deleting SQL Server %q AAD admin (Resource Group %q): %+v", name, resGroup, err) + } + + if err = adminDelFuture.WaitForCompletionRef(ctx, adminClient.Client); err != nil { + return fmt.Errorf("waiting for SQL Server %q AAD admin (Resource Group %q) to be deleted: %+v", name, resGroup, err) + } + + if adminParams := expandAzureRmMsSqlServerAdministrator(d.Get("azuread_administrator").([]interface{})); adminParams != nil { + adminFuture, err := adminClient.CreateOrUpdate(ctx, resGroup, name, *adminParams) + if err != nil { + return fmt.Errorf("creating SQL Server %q AAD admin (Resource Group %q): %+v", name, resGroup, err) + } + + if err = adminFuture.WaitForCompletionRef(ctx, adminClient.Client); err != nil { + return fmt.Errorf("waiting for creation of SQL Server %q AAD admin (Resource Group %q): %+v", name, resGroup, err) + } + } + } + connection := sql.ServerConnectionPolicy{ ServerConnectionPolicyProperties: &sql.ServerConnectionPolicyProperties{ ConnectionType: sql.ServerConnectionType(d.Get("connection_policy").(string)), @@ -210,10 +263,16 @@ func resourceArmMsSqlServerCreateUpdate(d *schema.ResourceData, meta interface{} auditingProps := sql.ExtendedServerBlobAuditingPolicy{ ExtendedServerBlobAuditingPolicyProperties: helper.ExpandAzureRmSqlServerBlobAuditingPolicies(d.Get("extended_auditing_policy").([]interface{})), } - if _, err = auditingClient.CreateOrUpdate(ctx, resGroup, name, auditingProps); err != nil { + + auditingFuture, err := auditingClient.CreateOrUpdate(ctx, resGroup, name, auditingProps) + if err != nil { return fmt.Errorf("Error issuing create/update request for SQL Server %q Blob Auditing Policies(Resource Group %q): %+v", name, resGroup, err) } + if err = auditingFuture.WaitForCompletionRef(ctx, auditingClient.Client); err != nil { + return fmt.Errorf("waiting for creation of SQL Server %q Blob Auditing Policies(Resource Group %q): %+v", name, resGroup, err) + } + return resourceArmMsSqlServerRead(d, meta) } @@ -221,6 +280,7 @@ func resourceArmMsSqlServerRead(d *schema.ResourceData, meta interface{}) error client := meta.(*clients.Client).MSSQL.ServersClient auditingClient := meta.(*clients.Client).MSSQL.ServerExtendedBlobAuditingPoliciesClient connectionClient := meta.(*clients.Client).MSSQL.ServerConnectionPoliciesClient + adminClient := meta.(*clients.Client).MSSQL.ServerAzureADAdministratorsClient ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() @@ -260,6 +320,17 @@ func resourceArmMsSqlServerRead(d *schema.ResourceData, meta interface{}) error d.Set("public_network_access_enabled", props.PublicNetworkAccess == sql.ServerPublicNetworkAccessEnabled) } + adminResp, err := adminClient.Get(ctx, resGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(adminResp.Response) { + return fmt.Errorf("Error reading SQL Server %s AAD admin: %v", name, err) + } + } else { + if err := d.Set("azuread_administrator", flatternAzureRmMsSqlServerAdministrator(adminResp)); err != nil { + return fmt.Errorf("setting `azuread_administrator`: %+v", err) + } + } + connection, err := connectionClient.Get(ctx, resGroup, name) if err != nil { return fmt.Errorf("Error reading SQL Server %s Blob Connection Policy: %v ", name, err) @@ -328,3 +399,50 @@ func flattenAzureRmSqlServerIdentity(identity *sql.ResourceIdentity) []interface return []interface{}{result} } + +func expandAzureRmMsSqlServerAdministrator(input []interface{}) *sql.ServerAzureADAdministrator { + if len(input) == 0 || input[0] == nil { + return nil + } + + admin := input[0].(map[string]interface{}) + sid, _ := uuid.FromString(admin["object_id"].(string)) + + adminParams := sql.ServerAzureADAdministrator{ + AdministratorProperties: &sql.AdministratorProperties{ + AdministratorType: utils.String("ActiveDirectory"), + Login: utils.String(admin["login_username"].(string)), + Sid: &sid, + }, + } + + if v, ok := admin["tenant_id"]; ok && v != "" { + tid, _ := uuid.FromString(v.(string)) + adminParams.TenantID = &tid + } + + return &adminParams +} + +func flatternAzureRmMsSqlServerAdministrator(admin sql.ServerAzureADAdministrator) []interface{} { + var login, sid, tid string + if admin.Login != nil { + login = *admin.Login + } + + if admin.Sid != nil { + sid = admin.Sid.String() + } + + if admin.TenantID != nil { + tid = admin.TenantID.String() + } + + return []interface{}{ + map[string]interface{}{ + "login_username": login, + "object_id": sid, + "tenant_id": tid, + }, + } +} diff --git a/azurerm/internal/services/mssql/tests/mssql_server_resource_test.go b/azurerm/internal/services/mssql/tests/mssql_server_resource_test.go index 3c8c9bdc29ec..5b30efa30a29 100644 --- a/azurerm/internal/services/mssql/tests/mssql_server_resource_test.go +++ b/azurerm/internal/services/mssql/tests/mssql_server_resource_test.go @@ -2,6 +2,7 @@ package tests import ( "fmt" + "os" "testing" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" @@ -147,6 +148,79 @@ func TestAccAzureRMMsSqlServer_identity(t *testing.T) { }) } +func TestAccAzureRMMsSqlServer_azureadAdmin(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_mssql_server", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMMsSqlServerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMMsSqlServer_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlServerExists(data.ResourceName), + ), + }, + data.ImportStep("administrator_login_password"), + { + Config: testAccAzureRMMsSqlServer_azureadAdmin(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlServerExists(data.ResourceName), + ), + }, + data.ImportStep("administrator_login_password"), + { + Config: testAccAzureRMMsSqlServer_azureadAdminUpdate(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlServerExists(data.ResourceName), + ), + }, + data.ImportStep("administrator_login_password"), + { + Config: testAccAzureRMMsSqlServer_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlServerExists(data.ResourceName), + ), + }, + data.ImportStep("administrator_login_password"), + }, + }) +} + +func TestAccAzureRMMsSqlServer_blobAuditingPolicies_withFirewall(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_mssql_server", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMMsSqlServerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMMsSqlServer_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlServerExists(data.ResourceName), + ), + }, + data.ImportStep("administrator_login_password", "extended_auditing_policy.0.storage_account_access_key"), + { + Config: testAccAzureRMMsSqlServer_blobAuditingPolicies_withFirewall(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlServerExists(data.ResourceName), + ), + }, + data.ImportStep("administrator_login_password", "extended_auditing_policy.0.storage_account_access_key"), + { + Config: testAccAzureRMMsSqlServer_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlServerExists(data.ResourceName), + ), + }, + data.ImportStep("administrator_login_password", "extended_auditing_policy.0.storage_account_access_key"), + }, + }) +} + func testCheckAzureRMMsSqlServerExists(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acceptance.AzureProvider.Meta().(*clients.Client).Sql.ServersClient @@ -376,3 +450,132 @@ resource "azurerm_mssql_server" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } + +func testAccAzureRMMsSqlServer_azureadAdmin(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-mssql-%[1]d" + location = "%[2]s" +} + +data "azuread_service_principal" "test" { + application_id = "%[3]s" +} + +resource "azurerm_mssql_server" "test" { + name = "acctestsqlserver%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + version = "12.0" + administrator_login = "missadministrator" + administrator_login_password = "thisIsKat11" + + azuread_administrator { + login_username = "AzureAD Admin" + object_id = data.azuread_service_principal.test.id + } +} +`, data.RandomInteger, data.Locations.Primary, os.Getenv("ARM_CLIENT_ID")) +} + +func testAccAzureRMMsSqlServer_azureadAdminUpdate(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-mssql-%[1]d" + location = "%[2]s" +} + +data "azuread_service_principal" "test" { + application_id = "%[3]s" +} + +resource "azurerm_mssql_server" "test" { + name = "acctestsqlserver%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + version = "12.0" + administrator_login = "missadministrator" + administrator_login_password = "thisIsKat11" + + azuread_administrator { + login_username = "AzureAD Admin2" + object_id = data.azuread_service_principal.test.id + } +} +`, data.RandomInteger, data.Locations.Primary, os.Getenv("ARM_CLIENT_ID")) +} + +func testAccAzureRMMsSqlServer_blobAuditingPolicies_withFirewall(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-mssql-%[1]d" + location = "%[2]s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctestvirtnet%[1]d" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet" "test" { + name = "acctestsubnet%[1]d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefix = "10.0.2.0/24" + service_endpoints = ["Microsoft.Storage"] +} + +resource "azurerm_storage_account" "test" { + name = "unlikely23exst2acct%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" + + network_rules { + default_action = "Allow" + ip_rules = ["127.0.0.1"] + virtual_network_subnet_ids = [azurerm_subnet.test.id] + } +} + +data "azuread_service_principal" "test" { + application_id = "%[4]s" +} + +resource "azurerm_mssql_server" "test" { + name = "acctestsqlserver%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + version = "12.0" + administrator_login = "missadministrator" + administrator_login_password = "thisIsKat11" + + azuread_administrator { + login_username = "AzureAD Admin2" + object_id = data.azuread_service_principal.test.id + } + + extended_auditing_policy { + storage_account_access_key = azurerm_storage_account.test.primary_access_key + storage_endpoint = azurerm_storage_account.test.primary_blob_endpoint + storage_account_access_key_is_secondary = true + retention_in_days = 6 + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, os.Getenv("ARM_CLIENT_ID")) +} diff --git a/website/docs/r/mssql_server.html.markdown b/website/docs/r/mssql_server.html.markdown index 430a07f9ec40..74fce80e497d 100644 --- a/website/docs/r/mssql_server.html.markdown +++ b/website/docs/r/mssql_server.html.markdown @@ -38,6 +38,11 @@ resource "azurerm_mssql_server" "example" { administrator_login = "missadministrator" administrator_login_password = "thisIsKat11" + azuread_administrator { + login_username = "AzureAD Admin" + object_id = "00000000-0000-0000-0000-000000000000" + } + extended_auditing_policy { storage_endpoint = azurerm_storage_account.example.primary_blob_endpoint storage_account_access_key = azurerm_storage_account.example.primary_access_key @@ -66,6 +71,8 @@ The following arguments are supported: * `administrator_login_password` - (Required) The password associated with the `administrator_login` user. Needs to comply with Azure's [Password Policy](https://msdn.microsoft.com/library/ms161959.aspx) +* `azuread_administrator` - (Optional) An `azuread_administrator` block as defined below. + * `connection_policy` - (Optional) The connection policy the server will use. Possible values are `Default`, `Proxy`, and `Redirect`. Defaults to `Default`. * `identity` - (Optional) An `identity` block as defined below. @@ -102,6 +109,16 @@ The following attributes are exported: --- +A `azuread_administrator` block supports the following: + +* `login_username` - (Required) The login username of the Azure AD Administrator of this SQL Server. + +* `object_id` - (Required) The object id of the Azure AD Administrator of this SQL Server. + +* `tenant_id` - (Optional) The tenant id of the Azure AD Administrator of this SQL Server. + +--- + A `extended_auditing_policy` block supports the following: * `storage_account_access_key` - (Required) Specifies the access key to use for the auditing storage account.