From 3e8ce6711e01db089bad602978d13c2d507641f7 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Fri, 11 Dec 2020 14:31:55 +0800 Subject: [PATCH 1/3] New resource: azurerm_sentinel_alert_rule_fusion --- .../services/sentinel/registration.go | 1 + .../sentinel_alert_rule_fusion_resource.go | 190 ++++++++++++++++++ ...entinel_alert_rule_fusion_resource_test.go | 167 +++++++++++++++ website/azurerm.erb | 4 + .../sentinel_alert_rule_fusion.html.markdown | 70 +++++++ 5 files changed, 432 insertions(+) create mode 100644 azurerm/internal/services/sentinel/sentinel_alert_rule_fusion_resource.go create mode 100644 azurerm/internal/services/sentinel/sentinel_alert_rule_fusion_resource_test.go create mode 100644 website/docs/r/sentinel_alert_rule_fusion.html.markdown diff --git a/azurerm/internal/services/sentinel/registration.go b/azurerm/internal/services/sentinel/registration.go index 8f0bcebfa534..2150e3c34178 100644 --- a/azurerm/internal/services/sentinel/registration.go +++ b/azurerm/internal/services/sentinel/registration.go @@ -28,6 +28,7 @@ func (r Registration) SupportedDataSources() map[string]*schema.Resource { // SupportedResources returns the supported Resources supported by this Service func (r Registration) SupportedResources() map[string]*schema.Resource { return map[string]*schema.Resource{ + "azurerm_sentinel_alert_rule_fusion": resourceArmSentinelAlertRuleFusion(), "azurerm_sentinel_alert_rule_ms_security_incident": resourceArmSentinelAlertRuleMsSecurityIncident(), "azurerm_sentinel_alert_rule_scheduled": resourceArmSentinelAlertRuleScheduled(), } diff --git a/azurerm/internal/services/sentinel/sentinel_alert_rule_fusion_resource.go b/azurerm/internal/services/sentinel/sentinel_alert_rule_fusion_resource.go new file mode 100644 index 000000000000..38c0fff7601c --- /dev/null +++ b/azurerm/internal/services/sentinel/sentinel_alert_rule_fusion_resource.go @@ -0,0 +1,190 @@ +package sentinel + +import ( + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/preview/securityinsight/mgmt/2019-01-01-preview/securityinsight" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + loganalyticsParse "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/loganalytics/parse" + loganalyticsValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/loganalytics/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/sentinel/parse" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmSentinelAlertRuleFusion() *schema.Resource { + return &schema.Resource{ + Create: resourceArmSentinelAlertRuleFusionCreateUpdate, + Read: resourceArmSentinelAlertRuleFusionRead, + Update: resourceArmSentinelAlertRuleFusionCreateUpdate, + Delete: resourceArmSentinelAlertRuleFusionDelete, + + Importer: azSchema.ValidateResourceIDPriorToImportThen(func(id string) error { + _, err := parse.SentinelAlertRuleID(id) + return err + }, importSentinelAlertRule(securityinsight.Fusion)), + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "log_analytics_workspace_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: loganalyticsValidate.LogAnalyticsWorkspaceID, + }, + + "alert_rule_template_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.IsUUID, + }, + + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + }, + } +} + +func resourceArmSentinelAlertRuleFusionCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Sentinel.AlertRulesClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + + workspaceID, err := loganalyticsParse.LogAnalyticsWorkspaceID(d.Get("log_analytics_workspace_id").(string)) + if err != nil { + return err + } + + if d.IsNewResource() { + resp, err := client.Get(ctx, workspaceID.ResourceGroup, "Microsoft.OperationalInsights", workspaceID.WorkspaceName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("checking for existing Sentinel Alert Rule Fusion %q (Resource Group %q): %+v", name, workspaceID.ResourceGroup, err) + } + } + + id := alertRuleID(resp.Value) + if id != nil && *id != "" { + return tf.ImportAsExistsError("azurerm_sentinel_alert_rule_fusion", *id) + } + } + + params := securityinsight.FusionAlertRule{ + Kind: securityinsight.KindFusion, + FusionAlertRuleProperties: &securityinsight.FusionAlertRuleProperties{ + AlertRuleTemplateName: utils.String(d.Get("alert_rule_template_name").(string)), + Enabled: utils.Bool(d.Get("enabled").(bool)), + }, + } + + // Service avoid concurrent update of this resource via checking the "etag" to guarantee it is the same value as last Read. + if !d.IsNewResource() { + resp, err := client.Get(ctx, workspaceID.ResourceGroup, "Microsoft.OperationalInsights", workspaceID.WorkspaceName, name) + if err != nil { + return fmt.Errorf("retrieving Sentinel Alert Rule Fusion %q (Resource Group %q / Workspace: %q): %+v", name, workspaceID.ResourceGroup, workspaceID.WorkspaceName, err) + } + + if err := assertAlertRuleKind(resp.Value, securityinsight.Fusion); err != nil { + return fmt.Errorf("asserting alert rule of %q (Resource Group %q / Workspace: %q): %+v", name, workspaceID.ResourceGroup, workspaceID.WorkspaceName, err) + } + params.Etag = resp.Value.(securityinsight.FusionAlertRule).Etag + } + + if _, err := client.CreateOrUpdate(ctx, workspaceID.ResourceGroup, "Microsoft.OperationalInsights", workspaceID.WorkspaceName, name, params); err != nil { + return fmt.Errorf("creating Sentinel Alert Rule Fusion %q (Resource Group %q / Workspace: %q): %+v", name, workspaceID.ResourceGroup, workspaceID.WorkspaceName, err) + } + + resp, err := client.Get(ctx, workspaceID.ResourceGroup, "Microsoft.OperationalInsights", workspaceID.WorkspaceName, name) + if err != nil { + return fmt.Errorf("retrieving Sentinel Alert Rule Fusion %q (Resource Group %q / Workspace: %q): %+v", name, workspaceID.ResourceGroup, workspaceID.WorkspaceName, err) + } + id := alertRuleID(resp.Value) + if id == nil || *id == "" { + return fmt.Errorf("empty or nil ID returned for Sentinel Alert Rule Fusion %q (Resource Group %q / Workspace: %q) ID", name, workspaceID.ResourceGroup, workspaceID.WorkspaceName) + } + d.SetId(*id) + + return resourceArmSentinelAlertRuleFusionRead(d, meta) +} + +func resourceArmSentinelAlertRuleFusionRead(d *schema.ResourceData, meta interface{}) error { + subscriptionId := meta.(*clients.Client).Account.SubscriptionId + client := meta.(*clients.Client).Sentinel.AlertRulesClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.SentinelAlertRuleID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, "Microsoft.OperationalInsights", id.Workspace, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[DEBUG] Sentinel Alert Rule Fusion %q was not found in Workspace: %q in Resource Group %q - removing from state!", id.Name, id.Workspace, id.ResourceGroup) + d.SetId("") + return nil + } + + return fmt.Errorf("retrieving Sentinel Alert Rule Fusion %q (Resource Group %q / Workspace: %q): %+v", id.Name, id.ResourceGroup, id.Workspace, err) + } + + if err := assertAlertRuleKind(resp.Value, securityinsight.Fusion); err != nil { + return fmt.Errorf("asserting alert rule of %q (Resource Group %q / Workspace: %q): %+v", id.Name, id.ResourceGroup, id.Workspace, err) + } + rule := resp.Value.(securityinsight.FusionAlertRule) + + d.Set("name", id.Name) + + logAnalyticsWorkspaceId := loganalyticsParse.NewLogAnalyticsWorkspaceID(subscriptionId, id.ResourceGroup, id.Workspace) + d.Set("log_analytics_workspace_id", logAnalyticsWorkspaceId.ID(subscriptionId)) + + if prop := rule.FusionAlertRuleProperties; prop != nil { + d.Set("enabled", prop.Enabled) + d.Set("alert_rule_template_name", prop.AlertRuleTemplateName) + } + + return nil +} + +func resourceArmSentinelAlertRuleFusionDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Sentinel.AlertRulesClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.SentinelAlertRuleID(d.Id()) + if err != nil { + return err + } + + if _, err := client.Delete(ctx, id.ResourceGroup, "Microsoft.OperationalInsights", id.Workspace, id.Name); err != nil { + return fmt.Errorf("deleting Sentinel Alert Rule Fusion %q (Resource Group %q / Workspace: %q): %+v", id.Name, id.ResourceGroup, id.Workspace, err) + } + + return nil +} diff --git a/azurerm/internal/services/sentinel/sentinel_alert_rule_fusion_resource_test.go b/azurerm/internal/services/sentinel/sentinel_alert_rule_fusion_resource_test.go new file mode 100644 index 000000000000..eb1f308d58d2 --- /dev/null +++ b/azurerm/internal/services/sentinel/sentinel_alert_rule_fusion_resource_test.go @@ -0,0 +1,167 @@ +package sentinel_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/sentinel/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +type SentinelAlertRuleFusionResource struct{} + +func TestAccAzureRMSentinelAlertRuleFusion_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_sentinel_alert_rule_fusion", "test") + r := SentinelAlertRuleFusionResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccAzureRMSentinelAlertRuleFusion_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_sentinel_alert_rule_fusion", "test") + r := SentinelAlertRuleFusionResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.complete(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccAzureRMSentinelAlertRuleFusion_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_sentinel_alert_rule_fusion", "test") + r := SentinelAlertRuleFusionResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.complete(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccAzureRMSentinelAlertRuleFusion_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_sentinel_alert_rule_fusion", "test") + r := SentinelAlertRuleFusionResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func (r SentinelAlertRuleFusionResource) Exists(ctx context.Context, client *clients.Client, state *terraform.InstanceState) (*bool, error) { + alertRuleClient := client.Sentinel.AlertRulesClient + id, err := parse.SentinelAlertRuleID(state.ID) + if err != nil { + return nil, err + } + + resp, err := alertRuleClient.Get(ctx, id.ResourceGroup, "Microsoft.OperationalInsights", id.Workspace, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return utils.Bool(false), nil + } + + return nil, fmt.Errorf("retrieving Sentinel Alert Rule Fusion %q: %+v", state.ID, err) + } + + return utils.Bool(resp.Value != nil), nil +} + +func (r SentinelAlertRuleFusionResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_sentinel_alert_rule_fusion" "test" { + name = "acctest-SentinelAlertRule-Fusion-%d" + log_analytics_workspace_id = azurerm_log_analytics_workspace.test.id + alert_rule_template_name = "f71aba3d-28fb-450b-b192-4e76a83015c8" +} +`, r.template(data), data.RandomInteger) +} + +func (r SentinelAlertRuleFusionResource) complete(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_sentinel_alert_rule_fusion" "test" { + name = "acctest-SentinelAlertRule-Fusion-%d" + log_analytics_workspace_id = azurerm_log_analytics_workspace.test.id + alert_rule_template_name = "f71aba3d-28fb-450b-b192-4e76a83015c8" + enabled = false +} +`, r.template(data), data.RandomInteger) +} + +func (r SentinelAlertRuleFusionResource) requiresImport(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_sentinel_alert_rule_fusion" "import" { + name = azurerm_sentinel_alert_rule_fusion.test.name + log_analytics_workspace_id = azurerm_sentinel_alert_rule_fusion.test.log_analytics_workspace_id + alert_rule_template_name = azurerm_sentinel_alert_rule_fusion.test.alert_rule_template_name +} +`, r.basic(data)) +} + +func (r SentinelAlertRuleFusionResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-sentinel-%d" + location = "%s" +} + +resource "azurerm_log_analytics_workspace" "test" { + name = "acctestLAW-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku = "PerGB2018" +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} diff --git a/website/azurerm.erb b/website/azurerm.erb index 78240f975d60..a0270b7ef50d 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -2774,6 +2774,10 @@
  • Sentinel Resources