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

azurerm_postgres_server - support for threat_detection_policy #6721

Merged
merged 11 commits into from May 14, 2020
Expand Up @@ -14,6 +14,8 @@ import (
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

// todo 3.0 - this may want to be put into the mssql_server resource now that it exists.

func resourceArmMssqlServerSecurityAlertPolicy() *schema.Resource {
return &schema.Resource{
Create: resourceArmMssqlServerSecurityAlertPolicyCreateUpdate,
Expand Down
Expand Up @@ -184,9 +184,9 @@ resource "azurerm_sql_server" "test" {
resource "azurerm_storage_account" "test" {
name = "accsa%d"
resource_group_name = azurerm_resource_group.test.name
location = "%s"
location = azurerm_resource_group.test.location
account_tier = "Standard"
account_replication_type = "GRS"
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.Locations.Primary)
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger)
}
25 changes: 15 additions & 10 deletions azurerm/internal/services/postgres/client/client.go
Expand Up @@ -6,11 +6,12 @@ import (
)

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

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

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

virtualNetworkRulesClient := postgresql.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,
}
}
204 changes: 193 additions & 11 deletions azurerm/internal/services/postgres/postgresql_server_resource.go
Expand Up @@ -277,6 +277,70 @@ func resourceArmPostgreSQLServer() *schema.Resource {
DiffSuppressFunc: suppress.CaseDifference,
},

"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,
},
},
},
},

"fqdn": {
Type: schema.TypeString,
Computed: true,
Expand All @@ -289,6 +353,7 @@ func resourceArmPostgreSQLServer() *schema.Resource {

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

Expand Down Expand Up @@ -339,7 +404,7 @@ func resourceArmPostgreSQLServerCreate(d *schema.ResourceData, meta interface{})
ssl = postgresql.SslEnforcementEnumDisabled
}

storage := expandAzureRmPostgreSQLStorageProfile(d)
storage := expandPostgreSQLStorageProfile(d)

var props postgresql.BasicServerPropertiesForCreate
switch mode {
Expand Down Expand Up @@ -440,11 +505,26 @@ func resourceArmPostgreSQLServerCreate(d *schema.ResourceData, meta interface{})

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 postgres server security alert policy: %v", err)
}

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

return resourceArmPostgreSQLServerRead(d, meta)
}

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

Expand Down Expand Up @@ -478,7 +558,7 @@ func resourceArmPostgreSQLServerUpdate(d *schema.ResourceData, meta interface{})
AdministratorLoginPassword: utils.String(d.Get("administrator_login_password").(string)),
PublicNetworkAccess: publicAccess,
SslEnforcement: ssl,
StorageProfile: expandAzureRmPostgreSQLStorageProfile(d),
StorageProfile: expandPostgreSQLStorageProfile(d),
Version: postgresql.ServerVersion(d.Get("version").(string)),
},
Sku: sku,
Expand All @@ -494,21 +574,26 @@ func resourceArmPostgreSQLServerUpdate(d *schema.ResourceData, meta interface{})
return fmt.Errorf("waiting for update of PostgreSQL Server %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err)
}

read, err := client.Get(ctx, id.ResourceGroup, id.Name)
if err != nil {
return fmt.Errorf("retrieving PostgreSQL Server %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err)
}
if read.ID == nil {
return fmt.Errorf("Cannot read PostgreSQL Server %s (resource group %s) ID", id.Name, id.ResourceGroup)
}
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 mssql server security alert policy: %v", err)
}

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

return resourceArmPostgreSQLServerRead(d, meta)
}

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

Expand Down Expand Up @@ -563,6 +648,19 @@ func resourceArmPostgreSQLServerRead(d *schema.ResourceData, meta interface{}) e
// Computed
d.Set("fqdn", props.FullyQualifiedDomainName)
}

secResp, err := securityClient.Get(ctx, id.ResourceGroup, id.Name)
if err != nil && !utils.ResponseWasNotFound(secResp.Response) {
return fmt.Errorf("error making read request to postgres 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 @@ -627,7 +725,7 @@ func expandServerSkuName(skuName string) (*postgresql.Sku, error) {
}, nil
}

func expandAzureRmPostgreSQLStorageProfile(d *schema.ResourceData) *postgresql.StorageProfile {
func expandPostgreSQLStorageProfile(d *schema.ResourceData) *postgresql.StorageProfile {
storage := postgresql.StorageProfile{}
if v, ok := d.GetOk("storage_profile"); ok {
storageprofile := v.([]interface{})[0].(map[string]interface{})
Expand Down Expand Up @@ -682,3 +780,87 @@ func flattenPostgreSQLStorageProfile(resp *postgresql.StorageProfile) []interfac

return []interface{}{values}
}

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

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

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

props := &postgresql.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 &postgresql.ServerSecurityAlertPolicy{
SecurityAlertPolicyProperties: props,
}
}

func flattenSecurityAlertPolicy(props *postgresql.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 == postgresql.ServerSecurityAlertPolicyStateDisabled {
return nil
}

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

block["enabled"] = props.State == postgresql.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}
}