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_sql_database - Add sql threat detection policy configuration #1628

Merged
merged 9 commits into from
Sep 9, 2018
Merged

azurerm_sql_database - Add sql threat detection policy configuration #1628

merged 9 commits into from
Sep 9, 2018

Conversation

TFaga
Copy link
Contributor

@TFaga TFaga commented Jul 23, 2018

This pull request adds the ability to configure sql threat detection policy for an azurerm_sql_database. Addresses issue #1484

Copy link
Contributor

@tombuildsstuff tombuildsstuff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey @TFaga

Thanks for this PR - I've taken a look through and left some comments in-line - if we can fix those up we should be able to run the tests and see how this looks :)

Thanks!

email_addresses = "%s"
storage_account_access_key = "${azurerm_storage_account.test.primary_access_key}"
storage_endpoint = "${azurerm_storage_account.test.primary_blob_endpoint}"
use_server_default = "%s"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor can we make the indentation consistent here?

Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"disabled_alerts": {
Type: schema.TypeString,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

going on the description below:

  • disabled_alerts - (Optional) Specifies the semicolon-separated list of alerts that are disabled, or empty string to disable no alerts. Possible values: Sql_Injection; Sql_Injection_Vulnerability; Access_Anomaly; Usage_Anomaly.

If this is guaranteed to be a list of semicolon-separated strings, it would be a better UX to make this a List/Set of strings, so that users could get a better diff here in future; what do you think?

Type: schema.TypeString,
Optional: true,
DiffSuppressFunc: suppress.CaseDifference,
Default: string(sql.SecurityAlertPolicyUseServerDefaultDisabled),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 would it make sense for this to default to Enabled?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I left it Disabled is because that's the default value returned from the API if no policy is set.

Default values from the API:

{
    "properties": {
        "useServerDefault": "Disabled",
        "state": "Disabled",
        "disabledAlerts": "",
        "emailAddresses": "",
        "emailAccountAdmins": "Disabled",
        "storageEndpoint": "",
        "storageAccountAccessKey": "",
        "retentionDays": 0
    }
}

},

"email_addresses": {
Type: schema.TypeString,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(same here) I think this'd make more sense as a list?

@@ -644,3 +682,51 @@ resource "azurerm_sql_database" "test" {
}
`, rInt, location, rInt, rInt, requestedServiceObjectiveName)
}

func testAccAzureRMSqlDatabase_threatDetectionPolicy(rInt int, location, state, disabledAlerts, emailAccountAdmins, emailAddresses,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we hard-code the state,disabledAlerts, emailAccountAdmins, emailAddresses, useServerDefault and retentionDays fields here? since they're only used in a single place, there's no need to make them dynamic

@@ -85,6 +87,17 @@ The following arguments are supported:
* `authentication_type` - (Required) Specifies the type of authentication used to access the server. Valid values are `SQL` or `ADPassword`.
* `operation_mode` - (Optional) Specifies the type of import operation being performed. The only allowable value is `Import`.

`threat_detection_policy` supports the following:

* `state` - (Required) Specifies the state of the policy. If state is Enabled, storageEndpoint and storageAccountAccessKey are required.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we update:

Specifies the state of the policy. If state is Enabled, storageEndpoint and storageAccountAccessKey are required.

to be:

The State of the Policy. Possible values are `Enabled`, `Disabled` or `New`.

@@ -85,6 +87,17 @@ The following arguments are supported:
* `authentication_type` - (Required) Specifies the type of authentication used to access the server. Valid values are `SQL` or `ADPassword`.
* `operation_mode` - (Optional) Specifies the type of import operation being performed. The only allowable value is `Import`.

`threat_detection_policy` supports the following:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor can we add --- and then a blank line above this to add a divider?


* `state` - (Required) Specifies the state of the policy. If state is Enabled, storageEndpoint and storageAccountAccessKey are required.
* `disabled_alerts` - (Optional) Specifies the semicolon-separated list of alerts that are disabled, or empty string to disable no alerts. Possible values: Sql_Injection; Sql_Injection_Vulnerability; Access_Anomaly; Usage_Anomaly.
* `email_account_admins` - (Optional) Specifies that the alert is sent to the account administrators.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifies that the alert is sent to the account administrators. -> Should the account administrators be emailed when this alert is triggered?

* `state` - (Required) Specifies the state of the policy. If state is Enabled, storageEndpoint and storageAccountAccessKey are required.
* `disabled_alerts` - (Optional) Specifies the semicolon-separated list of alerts that are disabled, or empty string to disable no alerts. Possible values: Sql_Injection; Sql_Injection_Vulnerability; Access_Anomaly; Usage_Anomaly.
* `email_account_admins` - (Optional) Specifies that the alert is sent to the account administrators.
* `email_addresses` - (Optional) Specifies the semicolon-separated list of e-mail addresses to which the alert is sent.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we've updated this to a list (above) we should be able to make this:

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. If state is Enabled, storageAccountAccessKey is required.
* `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. If state is Enabled, storageEndpoint is required.
* `use_server_default` - (Optional) Specifies whether to use the default server policy.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifies whether to use the default server policy. ->

Should the default server policy be used? Defaults to `true`

…es in the sql threat detection configuration of the `azurerm_sql_database` resource
@TFaga
Copy link
Contributor Author

TFaga commented Jul 25, 2018

@tombuildsstuff Hey, thank you for the quick review. I've pushed a commit to address the changes.

Copy link
Collaborator

@katbyte katbyte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @TFaga,

Thank you for the updates, apologies with the delay in giving this a re-review. I have left some mostly minor comments inline with my primary concern being there are no import test and a missing nil check. The rest are fairly stylistic in nature and are not blocking the PR.

@@ -327,6 +435,12 @@ func resourceArmSqlDatabaseCreateUpdate(d *schema.ResourceData, meta interface{}

d.SetId(*resp.ID)

threatDetectionClient := meta.(*ArmClient).sqlDatabaseThreatDetectionPoliciesClient
_, err = threatDetectionClient.CreateOrUpdate(ctx, resourceGroup, serverName, name, *threatDetection)
if err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two lines could be combined:

	if _, err = threatDetectionClient.CreateOrUpdate(ctx, resourceGroup, serverName, name, *threatDetection); err != nil {

@@ -354,6 +468,19 @@ func resourceArmSqlDatabaseRead(d *schema.ResourceData, meta interface{}) error
return fmt.Errorf("Error making Read request on Sql Database %s: %+v", name, err)
}

oldThreatDetection, hasOldThreatDetection := d.GetOk("threat_detection_policy")
if hasOldThreatDetection {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason we are only reading this in when it has been set in state?

If the user changes this in the portal/we are importing won't those changes now be ignored?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem was that the thread detection problem is a separate API call which always returns the default values, causing every plan operation to be the following every time, if the threat_detection_policy was not set in the manifest as it filled out the list in the state with all default values:

  ~ azurerm_sql_database.test
      threat_detection_policy.#: "1" => "0"

I've changed this so that the list element is marked computed, which alleviates the problem. Though I'm not sure if this is ok or if there is a better option to handle this case.

@@ -439,6 +566,46 @@ func flattenEncryptionStatus(encryption *[]sql.TransparentDataEncryption) string
return ""
}

func flattenArmSqlServerThreatDetectionPolicy(policy sql.DatabaseSecurityAlertPolicy, oldThreatDetection []interface{}) []interface{} {

properties := policy.DatabaseSecurityAlertPolicyProperties
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we get a nil check here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe return early:

threatDetectionPolicy := make(map[string]interface{})

properties := policy.DatabaseSecurityAlertPolicyProperties
if properties != nil {
    return []interface{}{threatDetectionPolicy}
}

threatDetectionPolicy["email_account_admins"] = string(properties.EmailAccountAdmins)
threatDetectionPolicy["use_server_default"] = string(properties.UseServerDefault)

if properties.DisabledAlerts != nil && *properties.DisabledAlerts != "" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we simplify this by:

if disabledAlerts := properties.DisabledAlerts; disabledAlerts != nil {

And if strings.Split() gets an empty string it should return an empty slice. Is there any harm in setting disabled_alerts to an empty slice?

This applies to email_addresses.

}

// If storage account access key is in state readd it to the new state, as the API does not return it for security reasons
if t, ok := oldThreatDetection[0].(map[string]interface{})["storage_account_access_key"]; ok {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor, but this can be done by passing in d and grabbing the property directly. An example can be see here

if v, ok := threadDetection["disabled_alerts"]; ok {
alerts := v.(*schema.Set).List()
expandedAlerts := make([]string, len(alerts))
for i := range alerts {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor but this could be

for _, v := range v.(*schema.Set).List() {
    expandedAlerts[i] = v.(string)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated to use the value from the current element of the loop, though still needed the index for the expandedAlerts variable.

* `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. If state is Enabled, storageAccountAccessKey is required.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Echoing toms comment, could

If state is Enabled, storageAccountAccessKey is required.

become:

Required if state is enabled.

),
},
{
Config: postConfig,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we get an import test step to verify that import works?

            {
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},

…g of the remote state of the `azurerm_sql_database` resource
@TFaga
Copy link
Contributor Author

TFaga commented Aug 18, 2018

Hi @katbyte ,

Thanks for the review. Apologies for the late response. I've pushed the changes in the comments.

Copy link
Collaborator

@WodansSon WodansSon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This LGTM, I left some very minor validation request comments. Other than that this looks great.

"storage_account_access_key": {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add some validation here to ensure something is entered?

ValidateFunc: validation.NoZeroValues,


"storage_endpoint": {
Type: schema.TypeString,
Optional: true,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here with the validation.

@TFaga
Copy link
Contributor Author

TFaga commented Aug 21, 2018

Hi @jeffreyCline,

I've pushed the changes in the comments.

@ghost ghost added the size/L label Sep 9, 2018
@katbyte
Copy link
Collaborator

katbyte commented Sep 9, 2018

@TFaga,

Thank you for the updates! this LGTM 👍

Tests pass:
screen shot 2018-09-09 at 10 19 35

@ghost ghost added the size/L label Sep 9, 2018
Copy link
Collaborator

@katbyte katbyte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(forgot to approve in last message)

@katbyte katbyte merged commit 9538c6b into hashicorp:master Sep 9, 2018
@katbyte katbyte added this to the 1.15.0 milestone Sep 9, 2018
katbyte added a commit that referenced this pull request Sep 9, 2018
@ghost
Copy link

ghost commented Mar 6, 2019

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.

If you feel this issue should be reopened, we encourage creating a new issue linking back to this one for added context. If you feel I made an error 🤖 🙉 , please reach out to my human friends 👉 [email protected]. Thanks!

@ghost ghost locked and limited conversation to collaborators Mar 6, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants