Skip to content

Commit

Permalink
azurerm_mysql_server - support for threat_detection_policy (#7156)
Browse files Browse the repository at this point in the history
Copy from PostgreSQL server
Fixes #5629
  • Loading branch information
pearcec authored Jul 8, 2020
1 parent ba42ccf commit d57d204
Show file tree
Hide file tree
Showing 4 changed files with 297 additions and 15 deletions.
25 changes: 15 additions & 10 deletions azurerm/internal/services/mysql/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import (
)

type Client struct {
ConfigurationsClient *mysql.ConfigurationsClient
DatabasesClient *mysql.DatabasesClient
FirewallRulesClient *mysql.FirewallRulesClient
ServersClient *mysql.ServersClient
VirtualNetworkRulesClient *mysql.VirtualNetworkRulesClient
ConfigurationsClient *mysql.ConfigurationsClient
DatabasesClient *mysql.DatabasesClient
FirewallRulesClient *mysql.FirewallRulesClient
ServersClient *mysql.ServersClient
ServerSecurityAlertPoliciesClient *mysql.ServerSecurityAlertPoliciesClient
VirtualNetworkRulesClient *mysql.VirtualNetworkRulesClient
}

func NewClient(o *common.ClientOptions) *Client {
Expand All @@ -26,14 +27,18 @@ func NewClient(o *common.ClientOptions) *Client {
ServersClient := mysql.NewServersClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&ServersClient.Client, o.ResourceManagerAuthorizer)

serverSecurityAlertPoliciesClient := mysql.NewServerSecurityAlertPoliciesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&serverSecurityAlertPoliciesClient.Client, o.ResourceManagerAuthorizer)

VirtualNetworkRulesClient := mysql.NewVirtualNetworkRulesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&VirtualNetworkRulesClient.Client, o.ResourceManagerAuthorizer)

return &Client{
ConfigurationsClient: &ConfigurationsClient,
DatabasesClient: &DatabasesClient,
FirewallRulesClient: &FirewallRulesClient,
ServersClient: &ServersClient,
VirtualNetworkRulesClient: &VirtualNetworkRulesClient,
ConfigurationsClient: &ConfigurationsClient,
DatabasesClient: &DatabasesClient,
FirewallRulesClient: &FirewallRulesClient,
ServersClient: &ServersClient,
ServerSecurityAlertPoliciesClient: &serverSecurityAlertPoliciesClient,
VirtualNetworkRulesClient: &VirtualNetworkRulesClient,
}
}
196 changes: 196 additions & 0 deletions azurerm/internal/services/mysql/mysql_server_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,70 @@ func resourceArmMySqlServer() *schema.Resource {
},
},

"threat_detection_policy": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Type: schema.TypeBool,
Optional: true,
},

"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",
"Data_Exfiltration",
"Unsafe_Action",
}, false),
},
},

"email_account_admins": {
Type: schema.TypeBool,
Optional: true,
},

"email_addresses": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
// todo email validation in code
},
Set: schema.HashString,
},

"retention_days": {
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntAtLeast(0),
},

"storage_account_access_key": {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
ValidateFunc: validation.StringIsNotEmpty,
},

"storage_endpoint": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringIsNotEmpty,
},
},
},
},

"tags": tags.Schema(),

"version": {
Expand Down Expand Up @@ -300,6 +364,7 @@ func resourceArmMySqlServer() *schema.Resource {

func resourceArmMySqlServerCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).MySQL.ServersClient
securityClient := meta.(*clients.Client).MySQL.ServerSecurityAlertPoliciesClient
ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d)
defer cancel()

Expand Down Expand Up @@ -448,11 +513,26 @@ func resourceArmMySqlServerCreate(d *schema.ResourceData, meta interface{}) erro

d.SetId(*read.ID)

if v, ok := d.GetOk("threat_detection_policy"); ok {
alert := expandSecurityAlertPolicy(v)
if alert != nil {
future, err := securityClient.CreateOrUpdate(ctx, resourceGroup, name, *alert)
if err != nil {
return fmt.Errorf("error updataing mysql server security alert policy: %v", err)
}

if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("error waiting for creation/update of mysql server security alert policy (server %q, resource group %q): %+v", name, resourceGroup, err)
}
}
}

return resourceArmMySqlServerRead(d, meta)
}

func resourceArmMySqlServerUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).MySQL.ServersClient
securityClient := meta.(*clients.Client).MySQL.ServerSecurityAlertPoliciesClient
ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d)
defer cancel()

Expand Down Expand Up @@ -514,11 +594,26 @@ func resourceArmMySqlServerUpdate(d *schema.ResourceData, meta interface{}) erro

d.SetId(*read.ID)

if v, ok := d.GetOk("threat_detection_policy"); ok {
alert := expandSecurityAlertPolicy(v)
if alert != nil {
future, err := securityClient.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, *alert)
if err != nil {
return fmt.Errorf("error updataing mysql server security alert policy: %v", err)
}

if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("error waiting for creation/update of mysql server security alert policy (server %q, resource group %q): %+v", id.Name, id.ResourceGroup, err)
}
}
}

return resourceArmMySqlServerRead(d, meta)
}

func resourceArmMySqlServerRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).MySQL.ServersClient
securityClient := meta.(*clients.Client).MySQL.ServerSecurityAlertPoliciesClient
ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
defer cancel()

Expand All @@ -545,8 +640,10 @@ func resourceArmMySqlServerRead(d *schema.ResourceData, meta interface{}) error
d.Set("location", azure.NormalizeLocation(*location))
}

tier := mysql.Basic
if sku := resp.Sku; sku != nil {
d.Set("sku_name", sku.Name)
tier = sku.Tier
}

if props := resp.ServerProperties; props != nil {
Expand All @@ -573,6 +670,21 @@ func resourceArmMySqlServerRead(d *schema.ResourceData, meta interface{}) error
d.Set("fqdn", props.FullyQualifiedDomainName)
}

// the basic does not support threat detection policies
if tier == mysql.GeneralPurpose || tier == mysql.MemoryOptimized {
secResp, err := securityClient.Get(ctx, id.ResourceGroup, id.Name)
if err != nil && !utils.ResponseWasNotFound(secResp.Response) {
return fmt.Errorf("error making read request to mysql server security alert policy: %+v", err)
}

if !utils.ResponseWasNotFound(secResp.Response) {
block := flattenSecurityAlertPolicy(secResp.SecurityAlertPolicyProperties, d.Get("threat_detection_policy.0.storage_account_access_key").(string))
if err := d.Set("threat_detection_policy", block); err != nil {
return fmt.Errorf("setting `threat_detection_policy`: %+v", err)
}
}
}

return tags.FlattenAndSet(d, resp.Tags)
}

Expand Down Expand Up @@ -691,3 +803,87 @@ func flattenMySQLStorageProfile(resp *mysql.StorageProfile) []interface{} {

return []interface{}{values}
}

func expandSecurityAlertPolicy(i interface{}) *mysql.ServerSecurityAlertPolicy {
slice := i.([]interface{})
if len(slice) == 0 {
return nil
}

block := slice[0].(map[string]interface{})

state := mysql.ServerSecurityAlertPolicyStateEnabled
if !block["enabled"].(bool) {
state = mysql.ServerSecurityAlertPolicyStateDisabled
}

props := &mysql.SecurityAlertPolicyProperties{
State: state,
}

if v, ok := block["disabled_alerts"]; ok {
props.DisabledAlerts = utils.ExpandStringSlice(v.(*schema.Set).List())
}

if v, ok := block["email_addresses"]; ok {
props.EmailAddresses = utils.ExpandStringSlice(v.(*schema.Set).List())
}

if v, ok := block["email_account_admins"]; ok {
props.EmailAccountAdmins = utils.Bool(v.(bool))
}

if v, ok := block["retention_days"]; ok {
props.RetentionDays = utils.Int32(int32(v.(int)))
}

if v, ok := block["storage_account_access_key"]; ok && v.(string) != "" {
props.StorageAccountAccessKey = utils.String(v.(string))
}

if v, ok := block["storage_endpoint"]; ok && v.(string) != "" {
props.StorageEndpoint = utils.String(v.(string))
}

return &mysql.ServerSecurityAlertPolicy{
SecurityAlertPolicyProperties: props,
}
}

func flattenSecurityAlertPolicy(props *mysql.SecurityAlertPolicyProperties, accessKey string) interface{} {
if props == nil {
return nil
}

// check if its an empty block as in its never been set before
if props.DisabledAlerts != nil && len(*props.DisabledAlerts) == 1 && (*props.DisabledAlerts)[0] == "" &&
props.EmailAddresses != nil && len(*props.EmailAddresses) == 1 && (*props.EmailAddresses)[0] == "" &&
props.StorageAccountAccessKey != nil && *props.StorageAccountAccessKey == "" &&
props.StorageEndpoint != nil && *props.StorageEndpoint == "" &&
props.RetentionDays != nil && *props.RetentionDays == 0 &&
props.EmailAccountAdmins != nil && !*props.EmailAccountAdmins &&
props.State == mysql.ServerSecurityAlertPolicyStateDisabled {
return nil
}

block := map[string]interface{}{}

block["enabled"] = props.State == mysql.ServerSecurityAlertPolicyStateEnabled

block["disabled_alerts"] = utils.FlattenStringSlice(props.DisabledAlerts)
block["email_addresses"] = utils.FlattenStringSlice(props.EmailAddresses)

if v := props.EmailAccountAdmins; v != nil {
block["email_account_admins"] = *v
}
if v := props.RetentionDays; v != nil {
block["retention_days"] = *v
}
if v := props.StorageEndpoint; v != nil {
block["storage_endpoint"] = *v
}

block["storage_account_access_key"] = accessKey

return []interface{}{block}
}
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,13 @@ func TestAccAzureRMMySQLServer_update(t *testing.T) {
),
},
data.ImportStep("administrator_login_password"), // not returned as sensitive
{
Config: testAccAzureRMMySQLServer_complete2(data, mysqlVersion),
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMMySQLServerExists(data.ResourceName),
),
},
data.ImportStep("administrator_login_password", "threat_detection_policy.0.storage_account_access_key"),
{
Config: testAccAzureRMMySQLServer_basic(data, mysqlVersion),
Check: resource.ComposeTestCheckFunc(
Expand Down Expand Up @@ -449,12 +456,12 @@ provider "azurerm" {
}
resource "azurerm_resource_group" "test" {
name = "acctestRG-%d"
location = "%s"
name = "acctestRG-%[1]d"
location = "%[2]s"
}
resource "azurerm_mysql_server" "test" {
name = "acctestmysqlsvr-%d"
name = "acctestmysqlsvr-%[1]d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
sku_name = "GP_Gen5_2"
Expand All @@ -466,9 +473,62 @@ resource "azurerm_mysql_server" "test" {
geo_redundant_backup_enabled = false
ssl_enforcement_enabled = true
storage_mb = 51200
version = "%s"
version = "%[3]s"
threat_detection_policy {
enabled = true
disabled_alerts = ["Sql_Injection", "Data_Exfiltration"]
email_account_admins = true
email_addresses = ["[email protected]", "[email protected]"]
retention_days = 7
}
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, version)
`, data.RandomInteger, data.Locations.Primary, version)
}

func testAccAzureRMMySQLServer_complete2(data acceptance.TestData, version string) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "test" {
name = "acctestRG-mysql-%[1]d"
location = "%[2]s"
}
resource "azurerm_storage_account" "test" {
name = "accsa%[1]d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
account_tier = "Standard"
account_replication_type = "GRS"
}
resource "azurerm_mysql_server" "test" {
name = "acctestmysqlsvr-%[1]d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
sku_name = "GP_Gen5_2"
administrator_login = "acctestun"
administrator_login_password = "H@Sh1CoR3!updated"
auto_grow_enabled = true
backup_retention_days = 7
create_mode = "Default"
geo_redundant_backup_enabled = false
ssl_enforcement_enabled = true
storage_mb = 51200
version = "%[3]s"
threat_detection_policy {
enabled = true
disabled_alerts = ["Sql_Injection"]
email_account_admins = true
email_addresses = ["[email protected]"]
retention_days = 7
storage_endpoint = azurerm_storage_account.test.primary_blob_endpoint
storage_account_access_key = azurerm_storage_account.test.primary_access_key
}
}
`, data.RandomInteger, data.Locations.Primary, version)
}

func testAccAzureRMMySQLServer_completeDeprecated(data acceptance.TestData, version string) string { // remove in v3.0
Expand Down
Loading

0 comments on commit d57d204

Please sign in to comment.