From aeb1d212885a073083652fe1b2959ee602711f9a Mon Sep 17 00:00:00 2001 From: kt Date: Sun, 12 Apr 2020 15:15:31 -0700 Subject: [PATCH] mssql_database - support the `threat_detection_policy` property (#6437) --- .../internal/services/mssql/client/client.go | 47 ++-- .../mssql/data_source_mssql_database.go | 2 +- .../mssql/resource_arm_mssql_database.go | 224 +++++++++++++++++- .../resource_arm_mssql_virtual_machine.go | 20 +- .../tests/resource_arm_mssql_database_test.go | 72 ++++++ .../resource_arm_policy_remediation_test.go | 2 +- website/docs/r/mssql_database.html.markdown | 15 +- 7 files changed, 337 insertions(+), 45 deletions(-) diff --git a/azurerm/internal/services/mssql/client/client.go b/azurerm/internal/services/mssql/client/client.go index fe5cfb274661..8818b8364104 100644 --- a/azurerm/internal/services/mssql/client/client.go +++ b/azurerm/internal/services/mssql/client/client.go @@ -8,6 +8,7 @@ import ( type Client struct { DatabasesClient *sql.DatabasesClient + DatabaseThreatDetectionPoliciesClient *sql.DatabaseThreatDetectionPoliciesClient ElasticPoolsClient *sql.ElasticPoolsClient DatabaseVulnerabilityAssessmentRuleBaselinesClient *sql.DatabaseVulnerabilityAssessmentRuleBaselinesClient ServersClient *sql.ServersClient @@ -17,34 +18,38 @@ type Client struct { } func NewClient(o *common.ClientOptions) *Client { - DatabasesClient := sql.NewDatabasesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) - o.ConfigureClient(&DatabasesClient.Client, o.ResourceManagerAuthorizer) + databasesClient := sql.NewDatabasesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&databasesClient.Client, o.ResourceManagerAuthorizer) - ElasticPoolsClient := sql.NewElasticPoolsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) - o.ConfigureClient(&ElasticPoolsClient.Client, o.ResourceManagerAuthorizer) + databaseThreatDetectionPoliciesClient := sql.NewDatabaseThreatDetectionPoliciesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&databaseThreatDetectionPoliciesClient.Client, o.ResourceManagerAuthorizer) - DatabaseVulnerabilityAssessmentRuleBaselinesClient := sql.NewDatabaseVulnerabilityAssessmentRuleBaselinesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) - o.ConfigureClient(&DatabaseVulnerabilityAssessmentRuleBaselinesClient.Client, o.ResourceManagerAuthorizer) + databaseVulnerabilityAssessmentRuleBaselinesClient := sql.NewDatabaseVulnerabilityAssessmentRuleBaselinesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&databaseVulnerabilityAssessmentRuleBaselinesClient.Client, o.ResourceManagerAuthorizer) - ServerSecurityAlertPoliciesClient := sql.NewServerSecurityAlertPoliciesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) - o.ConfigureClient(&ServerSecurityAlertPoliciesClient.Client, o.ResourceManagerAuthorizer) + elasticPoolsClient := sql.NewElasticPoolsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&elasticPoolsClient.Client, o.ResourceManagerAuthorizer) - ServersClient := sql.NewServersClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) - o.ConfigureClient(&ServersClient.Client, o.ResourceManagerAuthorizer) + serverSecurityAlertPoliciesClient := sql.NewServerSecurityAlertPoliciesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&serverSecurityAlertPoliciesClient.Client, o.ResourceManagerAuthorizer) - ServerVulnerabilityAssessmentsClient := sql.NewServerVulnerabilityAssessmentsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) - o.ConfigureClient(&ServerVulnerabilityAssessmentsClient.Client, o.ResourceManagerAuthorizer) + serversClient := sql.NewServersClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&serversClient.Client, o.ResourceManagerAuthorizer) - SQLVirtualMachinesClient := sqlvirtualmachine.NewSQLVirtualMachinesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) - o.ConfigureClient(&SQLVirtualMachinesClient.Client, o.ResourceManagerAuthorizer) + serverVulnerabilityAssessmentsClient := sql.NewServerVulnerabilityAssessmentsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&serverVulnerabilityAssessmentsClient.Client, o.ResourceManagerAuthorizer) + + sqlVirtualMachinesClient := sqlvirtualmachine.NewSQLVirtualMachinesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&sqlVirtualMachinesClient.Client, o.ResourceManagerAuthorizer) return &Client{ - DatabasesClient: &DatabasesClient, - ElasticPoolsClient: &ElasticPoolsClient, - DatabaseVulnerabilityAssessmentRuleBaselinesClient: &DatabaseVulnerabilityAssessmentRuleBaselinesClient, - ServersClient: &ServersClient, - ServerSecurityAlertPoliciesClient: &ServerSecurityAlertPoliciesClient, - ServerVulnerabilityAssessmentsClient: &ServerVulnerabilityAssessmentsClient, - VirtualMachinesClient: &SQLVirtualMachinesClient, + DatabasesClient: &databasesClient, + DatabaseThreatDetectionPoliciesClient: &databaseThreatDetectionPoliciesClient, + DatabaseVulnerabilityAssessmentRuleBaselinesClient: &databaseVulnerabilityAssessmentRuleBaselinesClient, + ElasticPoolsClient: &elasticPoolsClient, + ServersClient: &serversClient, + ServerSecurityAlertPoliciesClient: &serverSecurityAlertPoliciesClient, + ServerVulnerabilityAssessmentsClient: &serverVulnerabilityAssessmentsClient, + VirtualMachinesClient: &sqlVirtualMachinesClient, } } diff --git a/azurerm/internal/services/mssql/data_source_mssql_database.go b/azurerm/internal/services/mssql/data_source_mssql_database.go index b3d57ae76b4f..a658dc6fafce 100644 --- a/azurerm/internal/services/mssql/data_source_mssql_database.go +++ b/azurerm/internal/services/mssql/data_source_mssql_database.go @@ -99,7 +99,7 @@ func dataSourceArmMsSqlDatabaseRead(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("Database %q (Resource Group %q, SQL Server %q) was not found", name, serverId.ResourceGroup, serverId.Name) } - return fmt.Errorf("Failure in making Read request on AzureRM Database %s (Resource Group %q, SQL Server %q): %+v", name, serverId.ResourceGroup, serverId.Name, err) + return fmt.Errorf("making Read request on AzureRM Database %s (Resource Group %q, SQL Server %q): %+v", name, serverId.ResourceGroup, serverId.Name, err) } if id := resp.ID; id != nil { diff --git a/azurerm/internal/services/mssql/resource_arm_mssql_database.go b/azurerm/internal/services/mssql/resource_arm_mssql_database.go index f9f8cf27c227..8f7e8cd92f12 100644 --- a/azurerm/internal/services/mssql/resource_arm_mssql_database.go +++ b/azurerm/internal/services/mssql/resource_arm_mssql_database.go @@ -3,6 +3,7 @@ package mssql import ( "fmt" "log" + "strings" "time" "github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/v3.0/sql" @@ -176,6 +177,92 @@ func resourceArmMsSqlDatabase() *schema.Resource { Computed: true, }, + "threat_detection_policy": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "disabled_alerts": { + Type: schema.TypeSet, + Optional: true, + Set: schema.HashString, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + "Sql_Injection", + "Sql_Injection_Vulnerability", + "Access_Anomaly", + }, true), + }, + }, + + "email_account_admins": { + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: suppress.CaseDifference, + Default: string(sql.SecurityAlertPolicyEmailAccountAdminsDisabled), + ValidateFunc: validation.StringInSlice([]string{ + string(sql.SecurityAlertPolicyEmailAccountAdminsDisabled), + string(sql.SecurityAlertPolicyEmailAccountAdminsEnabled), + }, true), + }, + + "email_addresses": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Set: schema.HashString, + }, + + "retention_days": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(0), + }, + + "state": { + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: suppress.CaseDifference, + Default: string(sql.SecurityAlertPolicyStateDisabled), + ValidateFunc: validation.StringInSlice([]string{ + string(sql.SecurityAlertPolicyStateDisabled), + string(sql.SecurityAlertPolicyStateEnabled), + string(sql.SecurityAlertPolicyStateNew), + }, true), + }, + + "storage_account_access_key": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "storage_endpoint": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "use_server_default": { + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: suppress.CaseDifference, + Default: string(sql.SecurityAlertPolicyUseServerDefaultDisabled), + ValidateFunc: validation.StringInSlice([]string{ + string(sql.SecurityAlertPolicyUseServerDefaultDisabled), + string(sql.SecurityAlertPolicyUseServerDefaultEnabled), + }, true), + }, + }, + }, + }, + "tags": tags.Schema(), }, } @@ -184,6 +271,7 @@ func resourceArmMsSqlDatabase() *schema.Resource { func resourceArmMsSqlDatabaseCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*clients.Client).MSSQL.DatabasesClient serverClient := meta.(*clients.Client).MSSQL.ServersClient + threatClient := meta.(*clients.Client).MSSQL.DatabaseThreatDetectionPoliciesClient ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) defer cancel() @@ -208,7 +296,7 @@ func resourceArmMsSqlDatabaseCreateUpdate(d *schema.ResourceData, meta interface serverResp, err := serverClient.Get(ctx, serverId.ResourceGroup, serverId.Name) if err != nil { - return fmt.Errorf("Failure in making Read request on MsSql Server %q (Resource Group %q): %s", serverId.Name, serverId.ResourceGroup, err) + return fmt.Errorf("making Read request on MsSql Server %q (Resource Group %q): %s", serverId.Name, serverId.ResourceGroup, err) } location := *serverResp.Location @@ -272,16 +360,16 @@ func resourceArmMsSqlDatabaseCreateUpdate(d *schema.ResourceData, meta interface future, err := client.CreateOrUpdate(ctx, serverId.ResourceGroup, serverId.Name, name, params) if err != nil { - return fmt.Errorf("Failure in creating MsSql Database %q (Sql Server %q / Resource Group %q): %+v", name, serverId.Name, serverId.ResourceGroup, err) + return fmt.Errorf("creating MsSql Database %q (Sql Server %q / Resource Group %q): %+v", name, serverId.Name, serverId.ResourceGroup, err) } if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("Failure in waiting for creation of MsSql Database %q (MsSql Server Name %q / Resource Group %q): %+v", name, serverId.Name, serverId.ResourceGroup, err) + return fmt.Errorf("waiting for creation of MsSql Database %q (MsSql Server Name %q / Resource Group %q): %+v", name, serverId.Name, serverId.ResourceGroup, err) } read, err := client.Get(ctx, serverId.ResourceGroup, serverId.Name, name) if err != nil { - return fmt.Errorf("Failure in retrieving MsSql Database %q (MsSql Server Name %q / Resource Group %q): %+v", name, serverId.Name, serverId.ResourceGroup, err) + return fmt.Errorf("retrieving MsSql Database %q (MsSql Server Name %q / Resource Group %q): %+v", name, serverId.Name, serverId.ResourceGroup, err) } if read.ID == nil || *read.ID == "" { @@ -290,35 +378,40 @@ func resourceArmMsSqlDatabaseCreateUpdate(d *schema.ResourceData, meta interface d.SetId(*read.ID) + if _, err = threatClient.CreateOrUpdate(ctx, serverId.ResourceGroup, serverId.Name, name, *expandArmMsSqlServerThreatDetectionPolicy(d, location)); err != nil { + return fmt.Errorf("setting database threat detection policy: %+v", err) + } + return resourceArmMsSqlDatabaseRead(d, meta) } func resourceArmMsSqlDatabaseRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*clients.Client).MSSQL.DatabasesClient + threatClient := meta.(*clients.Client).MSSQL.DatabaseThreatDetectionPoliciesClient ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() - databaseId, err := parse.MsSqlDatabaseID(d.Id()) + id, err := parse.MsSqlDatabaseID(d.Id()) if err != nil { return err } - resp, err := client.Get(ctx, databaseId.ResourceGroup, databaseId.MsSqlServer, databaseId.Name) + resp, err := client.Get(ctx, id.ResourceGroup, id.MsSqlServer, id.Name) if err != nil { if utils.ResponseWasNotFound(resp.Response) { d.SetId("") return nil } - return fmt.Errorf("Failure in reading MsSql Database %s (MsSql Server Name %q / Resource Group %q): %s", databaseId.Name, databaseId.MsSqlServer, databaseId.ResourceGroup, err) + return fmt.Errorf("reading MsSql Database %s (MsSql Server Name %q / Resource Group %q): %s", id.Name, id.MsSqlServer, id.ResourceGroup, err) } d.Set("name", resp.Name) serverClient := meta.(*clients.Client).MSSQL.ServersClient - serverResp, err := serverClient.Get(ctx, databaseId.ResourceGroup, databaseId.MsSqlServer) + serverResp, err := serverClient.Get(ctx, id.ResourceGroup, id.MsSqlServer) if err != nil || *serverResp.ID == "" { - return fmt.Errorf("Failure in making Read request on MsSql Server %q (Resource Group %q): %s", databaseId.MsSqlServer, databaseId.ResourceGroup, err) + return fmt.Errorf("making Read request on MsSql Server %q (Resource Group %q): %s", id.MsSqlServer, id.ResourceGroup, err) } d.Set("server_id", serverResp.ID) @@ -341,6 +434,13 @@ func resourceArmMsSqlDatabaseRead(d *schema.ResourceData, meta interface{}) erro d.Set("zone_redundant", props.ZoneRedundant) } + threat, err := threatClient.Get(ctx, id.ResourceGroup, id.MsSqlServer, id.Name) + if err == nil { + if err := d.Set("threat_detection_policy", flattenArmMsSqlServerThreatDetectionPolicy(d, threat)); err != nil { + return fmt.Errorf("setting `threat_detection_policy`: %+v", err) + } + } + return tags.FlattenAndSet(d, resp.Tags) } @@ -356,15 +456,117 @@ func resourceArmMsSqlDatabaseDelete(d *schema.ResourceData, meta interface{}) er future, err := client.Delete(ctx, id.ResourceGroup, id.MsSqlServer, id.Name) if err != nil { - return fmt.Errorf("Failure in deleting MsSql Database %q ( MsSql Server %q / Resource Group %q): %+v", id.Name, id.MsSqlServer, id.ResourceGroup, err) + return fmt.Errorf("deleting MsSql Database %q ( MsSql Server %q / Resource Group %q): %+v", id.Name, id.MsSqlServer, id.ResourceGroup, err) } if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { if response.WasNotFound(future.Response()) { return nil } - return fmt.Errorf("Failure in waiting for MsSql Database %q ( MsSql Server %q / Resource Group %q) to be deleted: %+v", id.Name, id.MsSqlServer, id.ResourceGroup, err) + return fmt.Errorf("waiting for MsSql Database %q ( MsSql Server %q / Resource Group %q) to be deleted: %+v", id.Name, id.MsSqlServer, id.ResourceGroup, err) } return nil } + +func flattenArmMsSqlServerThreatDetectionPolicy(d *schema.ResourceData, policy sql.DatabaseSecurityAlertPolicy) []interface{} { + // The SQL database threat detection API always returns the default value even if never set. + // If the values are on their default one, threat it as not set. + properties := policy.DatabaseSecurityAlertPolicyProperties + if properties == nil { + return []interface{}{} + } + + threatDetectionPolicy := make(map[string]interface{}) + + threatDetectionPolicy["state"] = string(properties.State) + threatDetectionPolicy["email_account_admins"] = string(properties.EmailAccountAdmins) + threatDetectionPolicy["use_server_default"] = string(properties.UseServerDefault) + + if disabledAlerts := properties.DisabledAlerts; disabledAlerts != nil { + flattenedAlerts := schema.NewSet(schema.HashString, []interface{}{}) + if v := *disabledAlerts; v != "" { + parsedAlerts := strings.Split(v, ";") + for _, a := range parsedAlerts { + flattenedAlerts.Add(a) + } + } + threatDetectionPolicy["disabled_alerts"] = flattenedAlerts + } + if emailAddresses := properties.EmailAddresses; emailAddresses != nil { + flattenedEmails := schema.NewSet(schema.HashString, []interface{}{}) + if v := *emailAddresses; v != "" { + parsedEmails := strings.Split(*emailAddresses, ";") + for _, e := range parsedEmails { + flattenedEmails.Add(e) + } + } + threatDetectionPolicy["email_addresses"] = flattenedEmails + } + if properties.StorageEndpoint != nil { + threatDetectionPolicy["storage_endpoint"] = *properties.StorageEndpoint + } + if properties.RetentionDays != nil { + threatDetectionPolicy["retention_days"] = int(*properties.RetentionDays) + } + + // If storage account access key is in state read it to the new state, as the API does not return it for security reasons + if v, ok := d.GetOk("threat_detection_policy.0.storage_account_access_key"); ok { + threatDetectionPolicy["storage_account_access_key"] = v.(string) + } + + return []interface{}{threatDetectionPolicy} +} + +func expandArmMsSqlServerThreatDetectionPolicy(d *schema.ResourceData, location string) *sql.DatabaseSecurityAlertPolicy { + policy := sql.DatabaseSecurityAlertPolicy{ + Location: utils.String(location), + DatabaseSecurityAlertPolicyProperties: &sql.DatabaseSecurityAlertPolicyProperties{ + State: sql.SecurityAlertPolicyStateDisabled, + }, + } + properties := policy.DatabaseSecurityAlertPolicyProperties + + td, ok := d.GetOk("threat_detection_policy") + if !ok { + return &policy + } + + if tdl := td.([]interface{}); len(tdl) > 0 { + threatDetection := tdl[0].(map[string]interface{}) + + properties.State = sql.SecurityAlertPolicyState(threatDetection["state"].(string)) + properties.EmailAccountAdmins = sql.SecurityAlertPolicyEmailAccountAdmins(threatDetection["email_account_admins"].(string)) + properties.UseServerDefault = sql.SecurityAlertPolicyUseServerDefault(threatDetection["use_server_default"].(string)) + + if v, ok := threatDetection["disabled_alerts"]; ok { + alerts := v.(*schema.Set).List() + expandedAlerts := make([]string, len(alerts)) + for i, a := range alerts { + expandedAlerts[i] = a.(string) + } + properties.DisabledAlerts = utils.String(strings.Join(expandedAlerts, ";")) + } + if v, ok := threatDetection["email_addresses"]; ok { + emails := v.(*schema.Set).List() + expandedEmails := make([]string, len(emails)) + for i, e := range emails { + expandedEmails[i] = e.(string) + } + properties.EmailAddresses = utils.String(strings.Join(expandedEmails, ";")) + } + if v, ok := threatDetection["retention_days"]; ok { + properties.RetentionDays = utils.Int32(int32(v.(int))) + } + if v, ok := threatDetection["storage_account_access_key"]; ok { + properties.StorageAccountAccessKey = utils.String(v.(string)) + } + if v, ok := threatDetection["storage_endpoint"]; ok { + properties.StorageEndpoint = utils.String(v.(string)) + } + + return &policy + } + + return &policy +} diff --git a/azurerm/internal/services/mssql/resource_arm_mssql_virtual_machine.go b/azurerm/internal/services/mssql/resource_arm_mssql_virtual_machine.go index 6ba6e47c4243..d3a67a09e42b 100644 --- a/azurerm/internal/services/mssql/resource_arm_mssql_virtual_machine.go +++ b/azurerm/internal/services/mssql/resource_arm_mssql_virtual_machine.go @@ -193,7 +193,7 @@ func resourceArmMsSqlVirtualMachineCreateUpdate(d *schema.ResourceData, meta int existing, err := client.Get(ctx, id.ResourceGroup, id.Name, "*") if err != nil { if !utils.ResponseWasNotFound(existing.Response) { - return fmt.Errorf("Failure in checking for present of existing Sql Virtual Machine (Sql Virtual Machine Name %q / Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + return fmt.Errorf("checking for present of existing Sql Virtual Machine (Sql Virtual Machine Name %q / Resource Group %q): %+v", id.Name, id.ResourceGroup, err) } } if existing.ID != nil && *existing.ID != "" { @@ -204,7 +204,7 @@ func resourceArmMsSqlVirtualMachineCreateUpdate(d *schema.ResourceData, meta int // get location from vm respvm, err := vmclient.Get(ctx, id.ResourceGroup, id.Name, "") if err != nil { - return fmt.Errorf("Failure in making Read request on Azure Virtual Machine %s: %+v", id.Name, err) + return fmt.Errorf("making Read request on Azure Virtual Machine %s: %+v", id.Name, err) } if *respvm.Location == "" { @@ -236,15 +236,15 @@ func resourceArmMsSqlVirtualMachineCreateUpdate(d *schema.ResourceData, meta int future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, parameters) if err != nil { - return fmt.Errorf("Failure in creating Sql Virtual Machine (Sql Virtual Machine Name %q / Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + return fmt.Errorf("creating Sql Virtual Machine (Sql Virtual Machine Name %q / Resource Group %q): %+v", id.Name, id.ResourceGroup, err) } if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("Failure in waiting for creation of Sql Virtual Machine (Sql Virtual Machine Name %q / Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + return fmt.Errorf("waiting for creation of Sql Virtual Machine (Sql Virtual Machine Name %q / Resource Group %q): %+v", id.Name, id.ResourceGroup, err) } resp, err := client.Get(ctx, id.ResourceGroup, id.Name, "*") if err != nil { - return fmt.Errorf("Failure in retrieving Sql Virtual Machine (Sql Virtual Machine Name %q / Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + return fmt.Errorf("retrieving Sql Virtual Machine (Sql Virtual Machine Name %q / Resource Group %q): %+v", id.Name, id.ResourceGroup, err) } if resp.ID == nil { return fmt.Errorf("Cannot read Sql Virtual Machine (Sql Virtual Machine Name %q / Resource Group %q) ID", id.Name, id.ResourceGroup) @@ -271,18 +271,18 @@ func resourceArmMsSqlVirtualMachineRead(d *schema.ResourceData, meta interface{} d.SetId("") return nil } - return fmt.Errorf("Failure in reading Sql Virtual Machine (Sql Virtual Machine Name %q / Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + return fmt.Errorf("reading Sql Virtual Machine (Sql Virtual Machine Name %q / Resource Group %q): %+v", id.Name, id.ResourceGroup, err) } if props := resp.Properties; props != nil { d.Set("virtual_machine_id", props.VirtualMachineResourceID) d.Set("sql_license_type", string(props.SQLServerLicenseType)) if err := d.Set("auto_patching", flattenArmSqlVirtualMachineAutoPatching(props.AutoPatchingSettings)); err != nil { - return fmt.Errorf("Failure in setting `auto_patching`: %+v", err) + return fmt.Errorf("setting `auto_patching`: %+v", err) } if err := d.Set("key_vault_credential", flattenArmSqlVirtualMachineKeyVaultCredential(props.KeyVaultCredentialSettings, d)); err != nil { - return fmt.Errorf("Failure in setting `key_vault_credential`: %+v", err) + return fmt.Errorf("setting `key_vault_credential`: %+v", err) } if mgmtSettings := props.ServerConfigurationsManagementSettings; mgmtSettings != nil { @@ -310,12 +310,12 @@ func resourceArmMsSqlVirtualMachineDelete(d *schema.ResourceData, meta interface future, err := client.Delete(ctx, id.ResourceGroup, id.Name) if err != nil { - return fmt.Errorf("Failure in deleting Sql Virtual Machine (Sql Virtual Machine Name %q / Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + return fmt.Errorf("deleting Sql Virtual Machine (Sql Virtual Machine Name %q / Resource Group %q): %+v", id.Name, id.ResourceGroup, err) } if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { if !response.WasNotFound(future.Response()) { - return fmt.Errorf("Failure in waiting for deleting Sql Virtual Machine (Sql Virtual Machine Name %q / Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + return fmt.Errorf("waiting for deleting Sql Virtual Machine (Sql Virtual Machine Name %q / Resource Group %q): %+v", id.Name, id.ResourceGroup, err) } } diff --git a/azurerm/internal/services/mssql/tests/resource_arm_mssql_database_test.go b/azurerm/internal/services/mssql/tests/resource_arm_mssql_database_test.go index 7b14ffe64a9c..c714384e5165 100644 --- a/azurerm/internal/services/mssql/tests/resource_arm_mssql_database_test.go +++ b/azurerm/internal/services/mssql/tests/resource_arm_mssql_database_test.go @@ -300,6 +300,39 @@ func TestAccAzureRMMsSqlDatabase_createSecondaryMode(t *testing.T) { }) } +func TestAccAzureRMMsSqlDatabase_threatDetectionPolicy(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_mssql_database", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMMsSqlDatabaseDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMMsSqlDatabase_threatDetectionPolicy(data, "Enabled"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlDatabaseExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "threat_detection_policy.#", "1"), + resource.TestCheckResourceAttr(data.ResourceName, "threat_detection_policy.0.state", "Enabled"), + resource.TestCheckResourceAttr(data.ResourceName, "threat_detection_policy.0.retention_days", "15"), + resource.TestCheckResourceAttr(data.ResourceName, "threat_detection_policy.0.disabled_alerts.#", "1"), + resource.TestCheckResourceAttr(data.ResourceName, "threat_detection_policy.0.email_account_admins", "Enabled"), + ), + }, + data.ImportStep("sample_name", "threat_detection_policy.0.storage_account_access_key"), + { + Config: testAccAzureRMMsSqlDatabase_threatDetectionPolicy(data, "Disabled"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlDatabaseExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "threat_detection_policy.#", "1"), + resource.TestCheckResourceAttr(data.ResourceName, "threat_detection_policy.0.state", "Disabled"), + ), + }, + data.ImportStep("sample_name", "threat_detection_policy.0.storage_account_access_key"), + }, + }) +} + func testCheckAzureRMMsSqlDatabaseExists(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { client := acceptance.AzureProvider.Meta().(*clients.Client).MSSQL.DatabasesClient @@ -632,3 +665,42 @@ resource "azurerm_mssql_database" "secondary" { } `, template, data.RandomInteger, data.Locations.Secondary) } + +func testAccAzureRMMsSqlDatabase_threatDetectionPolicy(data acceptance.TestData, state string) string { + template := testAccAzureRMMsSqlDatabase_template(data) + return fmt.Sprintf(` +%[1]s + +resource "azurerm_storage_account" "test" { + name = "test%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "GRS" +} + +resource "azurerm_mssql_database" "test" { + name = "acctest-db-%[2]d" + server_id = azurerm_sql_server.test.id + collation = "SQL_AltDiction_CP850_CI_AI" + license_type = "BasePrice" + max_size_gb = 1 + sample_name = "AdventureWorksLT" + sku_name = "GP_Gen4_2" + + threat_detection_policy { + retention_days = 15 + state = "%[3]s" + disabled_alerts = ["Sql_Injection"] + email_account_admins = "Enabled" + storage_account_access_key = azurerm_storage_account.test.primary_access_key + storage_endpoint = azurerm_storage_account.test.primary_blob_endpoint + use_server_default = "Disabled" + } + + tags = { + ENV = "Test" + } +} +`, template, data.RandomInteger, state) +} diff --git a/azurerm/internal/services/policy/tests/resource_arm_policy_remediation_test.go b/azurerm/internal/services/policy/tests/resource_arm_policy_remediation_test.go index 588618680fab..ad64d3d43438 100644 --- a/azurerm/internal/services/policy/tests/resource_arm_policy_remediation_test.go +++ b/azurerm/internal/services/policy/tests/resource_arm_policy_remediation_test.go @@ -298,7 +298,7 @@ provider "azurerm" { data "azurerm_subscription" "current" {} resource "azurerm_policy_set_definition" "test" { - name = "acctest-policy-%[1]d" + name = "testPolicySet-%[1]d" policy_type = "Custom" display_name = "Test Policy Set" diff --git a/website/docs/r/mssql_database.html.markdown b/website/docs/r/mssql_database.html.markdown index 8e5dc1567ec5..ad27f10f3605 100644 --- a/website/docs/r/mssql_database.html.markdown +++ b/website/docs/r/mssql_database.html.markdown @@ -61,6 +61,8 @@ The following arguments are supported: * `create_mode` - (Optional) The create mode of the database. Possible values are `Copy`, `Default`, `OnlineSecondary`, `PointInTimeRestore`, `Restore`, `RestoreExternalBackup`, `RestoreExternalBackupSecondary`, `RestoreLongTermRetentionBackup` and `Secondary`. +* `creation_source_database_id` - (Optional) The id of the source database to be referred to create the new database. This should only be used for databases with `create_mode` values that use another database as reference. Changing this forces a new resource to be created. + * `collation` - (Optional) Specifies the collation of the database. Changing this forces a new resource to be created. * `elastic_pool_id` - (Optional) Specifies the ID of the elastic pool containing this database. Changing this forces a new resource to be created. @@ -83,12 +85,23 @@ The following arguments are supported: ~> **NOTE** The default sku_name value may differ between Azure locations depending on local availability of Gen4/Gen5 capacity. -* `creation_source_database_id` - (Optional) The id of the source database to be referred to create the new database. This should only be used for databases with `create_mode` values that use another database as reference. Changing this forces a new resource to be created. +* `threat_detection_policy` - (Optional) Threat detection policy configuration. The `threat_detection_policy` block supports fields documented below. * `zone_redundant` - (Optional) Whether or not this database is zone redundant, which means the replicas of this database will be spread across multiple availability zones. This property is only settable for Premium and Business Critical databases. * `tags` - (Optional) A mapping of tags to assign to the resource. +a `threat_detection_policy` block supports the following: + +* `state` - (Required) The State of the Policy. Possible values are `Enabled`, `Disabled` or `New`. +* `disabled_alerts` - (Optional) Specifies a list of alerts which should be disabled. Possible values include `Access_Anomaly`, `Sql_Injection` and `Sql_Injection_Vulnerability`. +* `email_account_admins` - (Optional) Should the account administrators be emailed when this alert is triggered? +* `email_addresses` - (Optional) A list of email addresses which alerts should be sent to. +* `retention_days` - (Optional) Specifies the number of days to keep in the Threat Detection audit logs. +* `storage_account_access_key` - (Optional) Specifies the identifier key of the Threat Detection audit storage account. Required if `state` is `Enabled`. +* `storage_endpoint` - (Optional) Specifies the blob storage endpoint (e.g. https://MyAccount.blob.core.windows.net). This blob storage will hold all Threat Detection audit logs. Required if `state` is `Enabled`. +* `use_server_default` - (Optional) Should the default server policy be used? Defaults to `Disabled`. + ## Attributes Reference