From dd106084d5a77ae26e8336228ec484daa75d3447 Mon Sep 17 00:00:00 2001 From: ziyeqf Date: Wed, 8 Feb 2023 15:07:22 +0800 Subject: [PATCH] New Resource: `azurerm_sentinel_alert_rule_anomaly_built_in`, New DataSource:`azurerm_sentinel_anomaly_rule` --- internal/services/sentinel/client/client.go | 5 + .../sentinel/parse/ml_analytics_settings.go | 75 +++ .../parse/ml_analytics_settings_test.go | 128 +++++ internal/services/sentinel/registration.go | 5 +- internal/services/sentinel/resourceids.go | 1 + .../sentinel/sentinel_alert_rule_anomaly.go | 56 +++ ...el_alert_rule_anomaly_built_in_resource.go | 440 ++++++++++++++++++ ...ert_rule_anomaly_built_in_resource_test.go | 68 +++ ...sentinel_alert_rule_anomaly_data_source.go | 271 +++++++++++ ...nel_alert_rule_anomaly_data_source_test.go | 51 ++ .../validate/ml_analytics_settings_id.go | 23 + .../validate/ml_analytics_settings_id_test.go | 88 ++++ .../sentinel_alert_rule_anomaly.html.markdown | 100 ++++ ..._alert_rule_anomaly_built_in.html.markdown | 116 +++++ 14 files changed, 1426 insertions(+), 1 deletion(-) create mode 100644 internal/services/sentinel/parse/ml_analytics_settings.go create mode 100644 internal/services/sentinel/parse/ml_analytics_settings_test.go create mode 100644 internal/services/sentinel/sentinel_alert_rule_anomaly.go create mode 100644 internal/services/sentinel/sentinel_alert_rule_anomaly_built_in_resource.go create mode 100644 internal/services/sentinel/sentinel_alert_rule_anomaly_built_in_resource_test.go create mode 100644 internal/services/sentinel/sentinel_alert_rule_anomaly_data_source.go create mode 100644 internal/services/sentinel/sentinel_alert_rule_anomaly_data_source_test.go create mode 100644 internal/services/sentinel/validate/ml_analytics_settings_id.go create mode 100644 internal/services/sentinel/validate/ml_analytics_settings_id_test.go create mode 100644 website/docs/d/sentinel_alert_rule_anomaly.html.markdown create mode 100644 website/docs/r/sentinel_alert_rule_anomaly_built_in.html.markdown diff --git a/internal/services/sentinel/client/client.go b/internal/services/sentinel/client/client.go index e71307000a15..07324f89a073 100644 --- a/internal/services/sentinel/client/client.go +++ b/internal/services/sentinel/client/client.go @@ -15,6 +15,7 @@ type Client struct { WatchlistsClient *securityinsight.WatchlistsClient WatchlistItemsClient *securityinsight.WatchlistItemsClient OnboardingStatesClient *sentinelonboardingstates.SentinelOnboardingStatesClient + AnalyticsSettingsClient *securityinsight.SecurityMLAnalyticsSettingsClient } func NewClient(o *common.ClientOptions) *Client { @@ -39,6 +40,9 @@ func NewClient(o *common.ClientOptions) *Client { onboardingStatesClient := sentinelonboardingstates.NewSentinelOnboardingStatesClientWithBaseURI(o.ResourceManagerEndpoint) o.ConfigureClient(&onboardingStatesClient.Client, o.ResourceManagerAuthorizer) + analyticsSettingsClient := securityinsight.NewSecurityMLAnalyticsSettingsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&analyticsSettingsClient.Client, o.ResourceManagerAuthorizer) + return &Client{ AlertRulesClient: &alertRulesClient, AlertRuleTemplatesClient: &alertRuleTemplatesClient, @@ -47,5 +51,6 @@ func NewClient(o *common.ClientOptions) *Client { WatchlistsClient: &watchListsClient, WatchlistItemsClient: &watchListItemsClient, OnboardingStatesClient: &onboardingStatesClient, + AnalyticsSettingsClient: &analyticsSettingsClient, } } diff --git a/internal/services/sentinel/parse/ml_analytics_settings.go b/internal/services/sentinel/parse/ml_analytics_settings.go new file mode 100644 index 000000000000..ad500f2529eb --- /dev/null +++ b/internal/services/sentinel/parse/ml_analytics_settings.go @@ -0,0 +1,75 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +type MLAnalyticsSettingsId struct { + SubscriptionId string + ResourceGroup string + WorkspaceName string + SecurityMLAnalyticsSettingName string +} + +func NewMLAnalyticsSettingsID(subscriptionId, resourceGroup, workspaceName, securityMLAnalyticsSettingName string) MLAnalyticsSettingsId { + return MLAnalyticsSettingsId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + WorkspaceName: workspaceName, + SecurityMLAnalyticsSettingName: securityMLAnalyticsSettingName, + } +} + +func (id MLAnalyticsSettingsId) String() string { + segments := []string{ + fmt.Sprintf("Security M L Analytics Setting Name %q", id.SecurityMLAnalyticsSettingName), + fmt.Sprintf("Workspace Name %q", id.WorkspaceName), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "M L Analytics Settings", segmentsStr) +} + +func (id MLAnalyticsSettingsId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.OperationalInsights/workspaces/%s/providers/Microsoft.SecurityInsights/securityMLAnalyticsSettings/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.WorkspaceName, id.SecurityMLAnalyticsSettingName) +} + +// MLAnalyticsSettingsID parses a MLAnalyticsSettings ID into an MLAnalyticsSettingsId struct +func MLAnalyticsSettingsID(input string) (*MLAnalyticsSettingsId, error) { + id, err := resourceids.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := MLAnalyticsSettingsId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + if resourceId.WorkspaceName, err = id.PopSegment("workspaces"); err != nil { + return nil, err + } + if resourceId.SecurityMLAnalyticsSettingName, err = id.PopSegment("securityMLAnalyticsSettings"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/internal/services/sentinel/parse/ml_analytics_settings_test.go b/internal/services/sentinel/parse/ml_analytics_settings_test.go new file mode 100644 index 000000000000..e0eee0876617 --- /dev/null +++ b/internal/services/sentinel/parse/ml_analytics_settings_test.go @@ -0,0 +1,128 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +var _ resourceids.Id = MLAnalyticsSettingsId{} + +func TestMLAnalyticsSettingsIDFormatter(t *testing.T) { + actual := NewMLAnalyticsSettingsID("12345678-1234-9876-4563-123456789012", "resGroup1", "workspace1", "setting1").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/securityMLAnalyticsSettings/setting1" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestMLAnalyticsSettingsID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *MLAnalyticsSettingsId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // missing WorkspaceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/", + Error: true, + }, + + { + // missing value for WorkspaceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/", + Error: true, + }, + + { + // missing SecurityMLAnalyticsSettingName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/", + Error: true, + }, + + { + // missing value for SecurityMLAnalyticsSettingName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/securityMLAnalyticsSettings/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/securityMLAnalyticsSettings/setting1", + Expected: &MLAnalyticsSettingsId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + WorkspaceName: "workspace1", + SecurityMLAnalyticsSettingName: "setting1", + }, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.OPERATIONALINSIGHTS/WORKSPACES/WORKSPACE1/PROVIDERS/MICROSOFT.SECURITYINSIGHTS/SECURITYMLANALYTICSSETTINGS/SETTING1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := MLAnalyticsSettingsID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + if actual.WorkspaceName != v.Expected.WorkspaceName { + t.Fatalf("Expected %q but got %q for WorkspaceName", v.Expected.WorkspaceName, actual.WorkspaceName) + } + if actual.SecurityMLAnalyticsSettingName != v.Expected.SecurityMLAnalyticsSettingName { + t.Fatalf("Expected %q but got %q for SecurityMLAnalyticsSettingName", v.Expected.SecurityMLAnalyticsSettingName, actual.SecurityMLAnalyticsSettingName) + } + } +} diff --git a/internal/services/sentinel/registration.go b/internal/services/sentinel/registration.go index 2a4093bf7160..9c8c56861ee0 100644 --- a/internal/services/sentinel/registration.go +++ b/internal/services/sentinel/registration.go @@ -55,7 +55,9 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { } func (r Registration) DataSources() []sdk.DataSource { - return []sdk.DataSource{} + return []sdk.DataSource{ + AlertRuleAnomalyDataSource{}, + } } func (r Registration) Resources() []sdk.Resource { @@ -72,5 +74,6 @@ func (r Registration) Resources() []sdk.Resource { LogAnalyticsWorkspaceOnboardResource{}, DataConnectorThreatIntelligenceTAXIIResource{}, DataConnectorMicrosoftThreatIntelligenceResource{}, + AlertRuleAnomalyBuiltInResource{}, } } diff --git a/internal/services/sentinel/resourceids.go b/internal/services/sentinel/resourceids.go index dc5677e2a9eb..5dcd3a553e0d 100644 --- a/internal/services/sentinel/resourceids.go +++ b/internal/services/sentinel/resourceids.go @@ -6,3 +6,4 @@ package sentinel //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=AutomationRule -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/automationRules/rule1 -rewrite=true //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=Watchlist -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/watchlists/list1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=WatchlistItem -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/watchlists/list1/watchlistItems/item1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=MLAnalyticsSettings -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/securityMLAnalyticsSettings/setting1 diff --git a/internal/services/sentinel/sentinel_alert_rule_anomaly.go b/internal/services/sentinel/sentinel_alert_rule_anomaly.go new file mode 100644 index 000000000000..6809cadf0c00 --- /dev/null +++ b/internal/services/sentinel/sentinel_alert_rule_anomaly.go @@ -0,0 +1,56 @@ +package sentinel + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/hashicorp/go-azure-sdk/resource-manager/operationalinsights/2022-10-01/workspaces" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" + securityinsight "github.com/tombuildsstuff/kermit/sdk/securityinsights/2022-10-01-preview/securityinsights" +) + +type AnomalyRuleRequiredDataConnectorModel struct { + ConnectorId string `tfschema:"connector_id"` + DataTypes []string `tfschema:"data_types"` +} + +// the service always return a fixed one no matter what id we pass, tracked on https://github.com/Azure/azure-rest-api-specs/issues/22485 +func AlertRuleAnomalyReadWithPredicate(ctx context.Context, client *securityinsight.SecurityMLAnalyticsSettingsClient, workspaceId workspaces.WorkspaceId, predicateFunc func(v *securityinsight.AnomalySecurityMLAnalyticsSettings) bool) (*securityinsight.AnomalySecurityMLAnalyticsSettings, error) { + resp, err := client.ListComplete(ctx, workspaceId.ResourceGroupName, workspaceId.WorkspaceName) + if err != nil { + return nil, fmt.Errorf("retrieving: %+v", err) + } + + for resp.NotDone() { + item := resp.Value() + if v, ok := item.AsAnomalySecurityMLAnalyticsSettings(); ok { + if predicateFunc(v) { + return v, nil + } + + } + if err := resp.NextWithContext(ctx); err != nil { + return nil, fmt.Errorf("listing next: %+v", err) + } + } + return nil, nil +} + +// when the id of workspace is too long, the service return without workspace name: +// "/subscriptions/{sub_id}/resourceGroups/{rg_name}/providers/Microsoft.OperationalInsights/workspaces//providers/Microsoft.SecurityInsights/securityMLAnalyticsSettings/5020e404-9768-4364-98f6-679940c21362", +// tracked on https://github.com/Azure/azure-rest-api-specs/issues/22500 +func AlertRuleAnomalyIdFromWorkspaceId(workspaceId workspaces.WorkspaceId, name string) string { + return parse.NewMLAnalyticsSettingsID(workspaceId.SubscriptionId, workspaceId.ResourceGroupName, workspaceId.WorkspaceName, name).ID() +} + +func flattenSentinelAlertRuleAnomalyCustomizableObservations(input interface{}) (string, error) { + value := "" + val, err := json.Marshal(input) + if err != nil { + return "", fmt.Errorf("failed to marshal to json: %+v", err) + } + value = string(val) + + return value, nil +} diff --git a/internal/services/sentinel/sentinel_alert_rule_anomaly_built_in_resource.go b/internal/services/sentinel/sentinel_alert_rule_anomaly_built_in_resource.go new file mode 100644 index 000000000000..b39dd8ac83d0 --- /dev/null +++ b/internal/services/sentinel/sentinel_alert_rule_anomaly_built_in_resource.go @@ -0,0 +1,440 @@ +package sentinel + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/hashicorp/go-azure-sdk/resource-manager/operationalinsights/2022-10-01/workspaces" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/utils" + securityinsight "github.com/tombuildsstuff/kermit/sdk/securityinsights/2022-10-01-preview/securityinsights" +) + +type AlertRuleAnomalyBuiltInModel struct { + Name string `tfschema:"name"` + DisplayName string `tfschema:"display_name"` + WorkspaceId string `tfschema:"log_analytics_workspace_id"` + CustomizableObservations string `tfschema:"customizable_observations"` + Enabled bool `tfschema:"enabled"` + Mode string `tfschema:"mode"` + AnomalyVersion string `tfschema:"anomaly_version"` + AnomalySettingsVersion int32 `tfschema:"anomaly_settings_version"` + Description string `tfschema:"description"` + Frequency string `tfschema:"frequency"` + IsDefaultSettings bool `tfschema:"is_default_settings"` + RequiredDataConnectors []AnomalyRuleRequiredDataConnectorModel `tfschema:"required_data_connector"` + SettingsDefinitionId string `tfschema:"settings_definition_id"` + Tactics []string `tfschema:"tactics"` + Techniques []string `tfschema:"techniques"` +} + +type AlertRuleAnomalyBuiltInResource struct{} + +var _ sdk.ResourceWithUpdate = AlertRuleAnomalyBuiltInResource{} + +func (r AlertRuleAnomalyBuiltInResource) ModelObject() interface{} { + return &AlertRuleAnomalyBuiltInModel{} +} + +func (r AlertRuleAnomalyBuiltInResource) ResourceType() string { + return "azurerm_sentinel_alert_rule_anomaly_built_in" +} + +func (r AlertRuleAnomalyBuiltInResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validate.MLAnalyticsSettingsID +} + +func (r AlertRuleAnomalyBuiltInResource) Arguments() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "name": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringIsNotEmpty, + ExactlyOneOf: []string{"name", "display_name"}, + }, + "display_name": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringIsNotEmpty, + ExactlyOneOf: []string{"name", "display_name"}, + }, + "log_analytics_workspace_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: workspaces.ValidateWorkspaceID, + }, + "enabled": { + Type: pluginsdk.TypeBool, + Required: true, + }, + "mode": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(securityinsight.SettingsStatusProduction), + string(securityinsight.SettingsStatusFlighting), + }, false), + }, + } +} + +func (r AlertRuleAnomalyBuiltInResource) Attributes() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "anomaly_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "anomaly_settings_version": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + "customizable_observations": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "description": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "frequency": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "is_default_settings": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + "required_data_connector": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "connector_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "data_types": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, + }, + }, + "settings_definition_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "tactics": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: pluginsdk.TypeString, + }, + }, + "techniques": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: pluginsdk.TypeString, + }, + }, + } +} + +func (r AlertRuleAnomalyBuiltInResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + var metaModel AlertRuleAnomalyBuiltInModel + if err := metadata.Decode(&metaModel); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + client := metadata.Client.Sentinel.AnalyticsSettingsClient + + workspaceId, err := workspaces.ParseWorkspaceID(metaModel.WorkspaceId) + if err != nil { + return fmt.Errorf("parsing workspace id: %+v", err) + } + + builtinRule, err := AlertRuleAnomalyReadWithPredicate(ctx, client, *workspaceId, func(v *securityinsight.AnomalySecurityMLAnalyticsSettings) bool { + if v.Name != nil && strings.EqualFold(*v.Name, metaModel.Name) { + return true + } + + if v.DisplayName != nil && strings.EqualFold(*v.DisplayName, metaModel.DisplayName) { + return true + } + + return false + }) + + if err != nil { + return fmt.Errorf("reading: %+v", err) + } + if builtinRule == nil { + if metaModel.DisplayName != "" { + return fmt.Errorf("built in rule (Display Name %q) was not found", metaModel.DisplayName) + } + return fmt.Errorf("built in rule (Display Name %q) was not found", metaModel.Name) + } + + id, err := parse.MLAnalyticsSettingsID(AlertRuleAnomalyIdFromWorkspaceId(*workspaceId, *builtinRule.Name)) + if err != nil { + return fmt.Errorf("parsing: %+v", err) + } + + param := securityinsight.AnomalySecurityMLAnalyticsSettings{ + Kind: securityinsight.KindBasicSecurityMLAnalyticsSettingKindAnomaly, + AnomalySecurityMLAnalyticsSettingsProperties: &securityinsight.AnomalySecurityMLAnalyticsSettingsProperties{ + Description: builtinRule.Description, + DisplayName: builtinRule.DisplayName, + RequiredDataConnectors: builtinRule.RequiredDataConnectors, + Tactics: builtinRule.Tactics, + Techniques: builtinRule.Techniques, + AnomalyVersion: builtinRule.AnomalyVersion, + Frequency: builtinRule.Frequency, + IsDefaultSettings: builtinRule.IsDefaultSettings, + AnomalySettingsVersion: builtinRule.AnomalySettingsVersion, + SettingsDefinitionID: builtinRule.SettingsDefinitionID, + Enabled: utils.Bool(metaModel.Enabled), + SettingsStatus: securityinsight.SettingsStatus(metaModel.Mode), + CustomizableObservations: builtinRule.CustomizableObservations, + }, + } + + _, err = client.CreateOrUpdate(ctx, id.ResourceGroup, id.WorkspaceName, id.SecurityMLAnalyticsSettingName, param) + if err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + metadata.SetID(id) + return nil + }, + } +} + +func (r AlertRuleAnomalyBuiltInResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Sentinel.AnalyticsSettingsClient + + id, err := parse.MLAnalyticsSettingsID(metadata.ResourceData.Id()) + if err != nil { + return fmt.Errorf("parsing %s: %+v", metadata.ResourceData.Id(), err) + } + workspaceId := workspaces.NewWorkspaceID(id.SubscriptionId, id.ResourceGroup, id.WorkspaceName) + + resp, err := AlertRuleAnomalyReadWithPredicate(ctx, client, workspaceId, func(v *securityinsight.AnomalySecurityMLAnalyticsSettings) bool { + if v.ID != nil && strings.EqualFold(*v.ID, id.ID()) { + return true + } + return false + }) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + if resp == nil { + return metadata.MarkAsGone(id) + } + + state := AlertRuleAnomalyBuiltInModel{ + WorkspaceId: workspaceId.ID(), + Mode: string(resp.SettingsStatus), + } + + if resp.Name != nil { + state.Name = *resp.Name + } + if resp.DisplayName != nil { + state.DisplayName = *resp.DisplayName + } + if resp.AnomalyVersion != nil { + state.AnomalyVersion = *resp.AnomalyVersion + } + if resp.AnomalySettingsVersion != nil { + state.AnomalySettingsVersion = *resp.AnomalySettingsVersion + } + if resp.Description != nil { + state.Description = *resp.Description + } + if resp.Enabled != nil { + state.Enabled = *resp.Enabled + } + if resp.Frequency != nil { + state.Frequency = *resp.Frequency + } + if resp.IsDefaultSettings != nil { + state.IsDefaultSettings = *resp.IsDefaultSettings + } + state.RequiredDataConnectors = flattenSentinelAlertRuleAnomalyRequiredDataConnectors(resp.RequiredDataConnectors) + if resp.SettingsDefinitionID != nil { + state.SettingsDefinitionId = resp.SettingsDefinitionID.String() + } + state.Tactics = flattenSentinelAlertRuleAnomalyTactics(resp.Tactics) + if resp.Techniques != nil { + state.Techniques = *resp.Techniques + } + if resp.CustomizableObservations != nil { + state.CustomizableObservations, err = flattenSentinelAlertRuleAnomalyCustomizableObservations(resp.CustomizableObservations) + if err != nil { + return fmt.Errorf("flattening `customizable_observations`: %+v", err) + } + } + + return metadata.Encode(&state) + }, + } +} + +func (r AlertRuleAnomalyBuiltInResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + var metaModel AlertRuleAnomalyBuiltInModel + if err := metadata.Decode(&metaModel); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + client := metadata.Client.Sentinel.AnalyticsSettingsClient + + id, err := parse.MLAnalyticsSettingsID(metadata.ResourceData.Id()) + if err != nil { + return fmt.Errorf("parsing: %+v", err) + } + + workspaceId, err := workspaces.ParseWorkspaceID(metaModel.WorkspaceId) + if err != nil { + return fmt.Errorf("parsing workspace id: %+v", err) + } + + existing, err := AlertRuleAnomalyReadWithPredicate(ctx, client, *workspaceId, func(v *securityinsight.AnomalySecurityMLAnalyticsSettings) bool { + if v.ID != nil && strings.EqualFold(*v.ID, id.ID()) { + return true + } + return false + }) + + if err != nil { + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + if existing == nil { + return fmt.Errorf("retrieving %s: not found", *id) + } + + param := securityinsight.AnomalySecurityMLAnalyticsSettings{ + Kind: securityinsight.KindBasicSecurityMLAnalyticsSettingKindAnomaly, + AnomalySecurityMLAnalyticsSettingsProperties: &securityinsight.AnomalySecurityMLAnalyticsSettingsProperties{ + Description: existing.Description, + DisplayName: existing.DisplayName, + RequiredDataConnectors: existing.RequiredDataConnectors, + Tactics: existing.Tactics, + Techniques: existing.Techniques, + AnomalyVersion: existing.AnomalyVersion, + Frequency: existing.Frequency, + IsDefaultSettings: existing.IsDefaultSettings, + AnomalySettingsVersion: existing.AnomalySettingsVersion, + SettingsDefinitionID: existing.SettingsDefinitionID, + Enabled: utils.Bool(metaModel.Enabled), + SettingsStatus: securityinsight.SettingsStatus(metaModel.Mode), + CustomizableObservations: existing.CustomizableObservations, + }, + } + + _, err = client.CreateOrUpdate(ctx, id.ResourceGroup, id.WorkspaceName, id.SecurityMLAnalyticsSettingName, param) + if err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + return nil + }, + } +} + +func (r AlertRuleAnomalyBuiltInResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + // it's not able to delete built-in rules. + var metaModel AlertRuleAnomalyBuiltInModel + if err := metadata.Decode(&metaModel); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + client := metadata.Client.Sentinel.AnalyticsSettingsClient + + id, err := parse.MLAnalyticsSettingsID(metadata.ResourceData.Id()) + if err != nil { + return fmt.Errorf("parsing: %+v", err) + } + + workspaceId, err := workspaces.ParseWorkspaceID(metaModel.WorkspaceId) + if err != nil { + return fmt.Errorf("parsing workspace id: %+v", err) + } + + existing, err := AlertRuleAnomalyReadWithPredicate(ctx, client, *workspaceId, func(v *securityinsight.AnomalySecurityMLAnalyticsSettings) bool { + if v.ID != nil && strings.EqualFold(*v.ID, id.ID()) { + return true + } + return false + }) + + if err != nil { + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + if existing == nil { + return fmt.Errorf("retrieving %s: not found", *id) + } + + param := securityinsight.AnomalySecurityMLAnalyticsSettings{ + Kind: securityinsight.KindBasicSecurityMLAnalyticsSettingKindAnomaly, + AnomalySecurityMLAnalyticsSettingsProperties: &securityinsight.AnomalySecurityMLAnalyticsSettingsProperties{ + Description: existing.Description, + DisplayName: existing.DisplayName, + RequiredDataConnectors: existing.RequiredDataConnectors, + Tactics: existing.Tactics, + Techniques: existing.Techniques, + AnomalyVersion: existing.AnomalyVersion, + Frequency: existing.Frequency, + IsDefaultSettings: existing.IsDefaultSettings, + AnomalySettingsVersion: existing.AnomalySettingsVersion, + SettingsDefinitionID: existing.SettingsDefinitionID, + Enabled: utils.Bool(false), + SettingsStatus: securityinsight.SettingsStatus(metaModel.Mode), + }, + } + + if metaModel.CustomizableObservations != "" { + v, err := pluginsdk.ExpandJsonFromString(metaModel.CustomizableObservations) + if err != nil { + return fmt.Errorf("expanding `customizable_observations`: %+v", err) + } + param.AnomalySecurityMLAnalyticsSettingsProperties.CustomizableObservations = v + } else { + param.AnomalySecurityMLAnalyticsSettingsProperties.CustomizableObservations = existing.CustomizableObservations + } + + _, err = client.CreateOrUpdate(ctx, id.ResourceGroup, id.WorkspaceName, id.SecurityMLAnalyticsSettingName, param) + if err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + return nil + }, + } +} diff --git a/internal/services/sentinel/sentinel_alert_rule_anomaly_built_in_resource_test.go b/internal/services/sentinel/sentinel_alert_rule_anomaly_built_in_resource_test.go new file mode 100644 index 000000000000..bd46cb629423 --- /dev/null +++ b/internal/services/sentinel/sentinel_alert_rule_anomaly_built_in_resource_test.go @@ -0,0 +1,68 @@ +package sentinel_test + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/hashicorp/go-azure-sdk/resource-manager/operationalinsights/2022-10-01/workspaces" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" + securityinsight "github.com/tombuildsstuff/kermit/sdk/securityinsights/2022-10-01-preview/securityinsights" +) + +type SentinelAlertRuleAnomalyBuiltInResource struct{} + +func (r SentinelAlertRuleAnomalyBuiltInResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.MLAnalyticsSettingsID(state.ID) + if err != nil { + return nil, err + } + + workspaceId := workspaces.NewWorkspaceID(id.SubscriptionId, id.ResourceGroup, id.WorkspaceName) + client := clients.Sentinel.AnalyticsSettingsClient + resp, err := sentinel.AlertRuleAnomalyReadWithPredicate(ctx, client, workspaceId, func(r *securityinsight.AnomalySecurityMLAnalyticsSettings) bool { + if r.Name != nil && strings.EqualFold(sentinel.AlertRuleAnomalyIdFromWorkspaceId(workspaceId, *r.Name), id.ID()) { + return true + } + return false + }) + if err != nil { + return nil, fmt.Errorf("retrieving Sentinel Alert Rule Anomaly Built In %q (Workspace %q / Resource Group %q): %+v", id.SecurityMLAnalyticsSettingName, id.WorkspaceName, id.ResourceGroup, err) + } + return utils.Bool(resp != nil && resp.Enabled != nil && *resp.Enabled == true), nil +} + +func TestAccSentinelAlertRuleAnomalyBuiltIn_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_sentinel_alert_rule_anomaly_built_in", "test") + r := SentinelAlertRuleAnomalyBuiltInResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (SentinelAlertRuleAnomalyBuiltInResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s +resource "azurerm_sentinel_alert_rule_anomaly_built_in" "test" { + display_name = "UEBA Anomalous Sign In" + log_analytics_workspace_id = azurerm_log_analytics_workspace.test.id + enabled = true + mode = "Production" + depends_on = [azurerm_sentinel_log_analytics_workspace_onboarding.test] +} +`, SecurityInsightsSentinelOnboardingStateResource{}.basic(data)) +} diff --git a/internal/services/sentinel/sentinel_alert_rule_anomaly_data_source.go b/internal/services/sentinel/sentinel_alert_rule_anomaly_data_source.go new file mode 100644 index 000000000000..6fea0c1863e6 --- /dev/null +++ b/internal/services/sentinel/sentinel_alert_rule_anomaly_data_source.go @@ -0,0 +1,271 @@ +package sentinel + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/hashicorp/go-azure-sdk/resource-manager/operationalinsights/2022-10-01/workspaces" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + securityinsight "github.com/tombuildsstuff/kermit/sdk/securityinsights/2022-10-01-preview/securityinsights" +) + +type AlertRuleAnomalyDataSourceModel struct { + Name string `tfschema:"name"` + DisplayName string `tfschema:"display_name"` + WorkspaceId string `tfschema:"log_analytics_workspace_id"` + AnomalyVersion string `tfschema:"anomaly_version"` + AnomalySettingsVersion int32 `tfschema:"anomaly_settings_version"` + CustomizableObservations string `tfschema:"customizable_observations"` + Description string `tfschema:"description"` + Enabled bool `tfschema:"enabled"` + Frequency string `tfschema:"frequency"` + IsDefaultSettings bool `tfschema:"is_default_settings"` + RequiredDataConnectors []AnomalyRuleRequiredDataConnectorModel `tfschema:"required_data_connector"` + SettingsDefinitionId string `tfschema:"settings_definition_id"` + Mode string `tfschema:"mode"` + Tactics []string `tfschema:"tactics"` + Techniques []string `tfschema:"techniques"` +} + +type AlertRuleAnomalyDataSource struct{} + +var _ sdk.DataSource = AlertRuleAnomalyDataSource{} + +func (a AlertRuleAnomalyDataSource) Arguments() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "name": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringIsNotEmpty, + ExactlyOneOf: []string{"name", "display_name"}, + }, + + "display_name": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringIsNotEmpty, + ExactlyOneOf: []string{"name", "display_name"}, + }, + + "log_analytics_workspace_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: workspaces.ValidateWorkspaceID, + }, + } +} + +func (a AlertRuleAnomalyDataSource) Attributes() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "anomaly_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "anomaly_settings_version": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + "customizable_observations": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "description": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + "frequency": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "is_default_settings": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + "required_data_connector": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "connector_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "data_types": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, + }, + }, + "mode": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "settings_definition_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "tactics": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: pluginsdk.TypeString, + }, + }, + "techniques": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: pluginsdk.TypeString, + }, + }, + } +} + +func (a AlertRuleAnomalyDataSource) ModelObject() interface{} { + return &AlertRuleAnomalyDataSourceModel{} +} + +func (a AlertRuleAnomalyDataSource) ResourceType() string { + return "azurerm_sentinel_alert_rule_anomaly" +} + +func (a AlertRuleAnomalyDataSource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + var metaModel AlertRuleAnomalyDataSourceModel + if err := metadata.Decode(&metaModel); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + client := metadata.Client.Sentinel.AnalyticsSettingsClient + workspaceId, err := workspaces.ParseWorkspaceID(metaModel.WorkspaceId) + if err != nil { + return fmt.Errorf("parsing workspace id: %+v", err) + } + + setting, err := AlertRuleAnomalyReadWithPredicate(ctx, client, *workspaceId, func(v *securityinsight.AnomalySecurityMLAnalyticsSettings) bool { + if v.Name != nil && strings.EqualFold(*v.Name, metaModel.Name) { + return true + } + + if v.DisplayName != nil && strings.EqualFold(*v.DisplayName, metaModel.DisplayName) { + return true + } + + return false + }) + + if err != nil { + return fmt.Errorf("retrieving: %+v", err) + } + if setting == nil { + if metaModel.DisplayName != "" { + return fmt.Errorf("reading Sentinel Anomaly Rule (Display Name %q) was not found", metaModel.DisplayName) + } + return fmt.Errorf("reading Sentinel Anomaly Rule (Name %q) was not found", metaModel.Name) + } + + id, err := parse.MLAnalyticsSettingsID(AlertRuleAnomalyIdFromWorkspaceId(*workspaceId, *setting.Name)) + if err != nil { + return fmt.Errorf("parsing: %+v", err) + } + + state := AlertRuleAnomalyDataSourceModel{ + WorkspaceId: workspaceId.ID(), + Mode: string(setting.SettingsStatus), + } + + if setting.Name != nil { + state.Name = *setting.Name + } + if setting.DisplayName != nil { + state.DisplayName = *setting.DisplayName + } + if setting.AnomalyVersion != nil { + state.AnomalyVersion = *setting.AnomalyVersion + } + if setting.AnomalySettingsVersion != nil { + state.AnomalySettingsVersion = *setting.AnomalySettingsVersion + } + if setting.Description != nil { + state.Description = *setting.Description + } + if setting.Enabled != nil { + state.Enabled = *setting.Enabled + } + if setting.Frequency != nil { + state.Frequency = *setting.Frequency + } + if setting.IsDefaultSettings != nil { + state.IsDefaultSettings = *setting.IsDefaultSettings + } + state.RequiredDataConnectors = flattenSentinelAlertRuleAnomalyRequiredDataConnectors(setting.RequiredDataConnectors) + if setting.SettingsDefinitionID != nil { + state.SettingsDefinitionId = setting.SettingsDefinitionID.String() + } + state.Tactics = flattenSentinelAlertRuleAnomalyTactics(setting.Tactics) + if setting.Techniques != nil { + state.Techniques = *setting.Techniques + } + if setting.CustomizableObservations != nil { + state.CustomizableObservations, err = flattenSentinelAlertRuleAnomalyCustomizableObservations(setting.CustomizableObservations) + if err != nil { + return fmt.Errorf("flattening `customizable_observations`: %+v", err) + } + } + + metadata.SetID(id) + return metadata.Encode(&state) + }, + } +} + +func flattenSentinelAlertRuleAnomalyRequiredDataConnectors(input *[]securityinsight.SecurityMLAnalyticsSettingsDataSource) []AnomalyRuleRequiredDataConnectorModel { + if input == nil { + return []AnomalyRuleRequiredDataConnectorModel{} + } + + output := make([]AnomalyRuleRequiredDataConnectorModel, 0) + for _, v := range *input { + if v.ConnectorID == nil || v.DataTypes == nil { + continue + } + + output = append(output, AnomalyRuleRequiredDataConnectorModel{ + ConnectorId: *v.ConnectorID, + DataTypes: *v.DataTypes, + }) + } + + return output +} + +func flattenSentinelAlertRuleAnomalyTactics(input *[]securityinsight.AttackTactic) []string { + if input == nil { + return []string{} + } + + output := make([]string, 0) + for _, v := range *input { + output = append(output, string(v)) + } + + return output +} diff --git a/internal/services/sentinel/sentinel_alert_rule_anomaly_data_source_test.go b/internal/services/sentinel/sentinel_alert_rule_anomaly_data_source_test.go new file mode 100644 index 000000000000..4683b8d85733 --- /dev/null +++ b/internal/services/sentinel/sentinel_alert_rule_anomaly_data_source_test.go @@ -0,0 +1,51 @@ +package sentinel_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" +) + +type SentinelAlertRuleAnomalyDataSource struct{} + +func TestAccSentinelAlertRuleAnomalyDataSource_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_sentinel_alert_rule_anomaly", "test") + r := SentinelAlertRuleAnomalyDataSource{} + + data.DataSourceTest(t, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("id").Exists(), + check.That(data.ResourceName).Key("name").Exists(), + check.That(data.ResourceName).Key("display_name").Exists(), + check.That(data.ResourceName).Key("anomaly_version").Exists(), + check.That(data.ResourceName).Key("anomaly_settings_version").Exists(), + check.That(data.ResourceName).Key("customizable_observations").Exists(), + check.That(data.ResourceName).Key("description").Exists(), + check.That(data.ResourceName).Key("enabled").Exists(), + check.That(data.ResourceName).Key("frequency").Exists(), + check.That(data.ResourceName).Key("is_default_settings").Exists(), + check.That(data.ResourceName).Key("required_data_connector.#").HasValue("2"), + check.That(data.ResourceName).Key("mode").Exists(), + check.That(data.ResourceName).Key("settings_definition_id").Exists(), + check.That(data.ResourceName).Key("tactics.#").HasValue("1"), + check.That(data.ResourceName).Key("techniques.#").HasValue("1"), + ), + }, + }) +} + +func (SentinelAlertRuleAnomalyDataSource) complete(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +data "azurerm_sentinel_alert_rule_anomaly" "test" { + log_analytics_workspace_id = azurerm_log_analytics_workspace.test.id + display_name = "UEBA Anomalous Sign In" + depends_on = [azurerm_sentinel_log_analytics_workspace_onboarding.test] +} +`, SecurityInsightsSentinelOnboardingStateResource{}.basic(data)) +} diff --git a/internal/services/sentinel/validate/ml_analytics_settings_id.go b/internal/services/sentinel/validate/ml_analytics_settings_id.go new file mode 100644 index 000000000000..17e3d54d0bdd --- /dev/null +++ b/internal/services/sentinel/validate/ml_analytics_settings_id.go @@ -0,0 +1,23 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" +) + +func MLAnalyticsSettingsID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := parse.MLAnalyticsSettingsID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/internal/services/sentinel/validate/ml_analytics_settings_id_test.go b/internal/services/sentinel/validate/ml_analytics_settings_id_test.go new file mode 100644 index 000000000000..9d87ab2cfdfb --- /dev/null +++ b/internal/services/sentinel/validate/ml_analytics_settings_id_test.go @@ -0,0 +1,88 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestMLAnalyticsSettingsID(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + + { + // empty + Input: "", + Valid: false, + }, + + { + // missing SubscriptionId + Input: "/", + Valid: false, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Valid: false, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Valid: false, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Valid: false, + }, + + { + // missing WorkspaceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/", + Valid: false, + }, + + { + // missing value for WorkspaceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/", + Valid: false, + }, + + { + // missing SecurityMLAnalyticsSettingName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/", + Valid: false, + }, + + { + // missing value for SecurityMLAnalyticsSettingName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/securityMLAnalyticsSettings/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/securityMLAnalyticsSettings/setting1", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.OPERATIONALINSIGHTS/WORKSPACES/WORKSPACE1/PROVIDERS/MICROSOFT.SECURITYINSIGHTS/SECURITYMLANALYTICSSETTINGS/SETTING1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := MLAnalyticsSettingsID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/website/docs/d/sentinel_alert_rule_anomaly.html.markdown b/website/docs/d/sentinel_alert_rule_anomaly.html.markdown new file mode 100644 index 000000000000..016090b14c83 --- /dev/null +++ b/website/docs/d/sentinel_alert_rule_anomaly.html.markdown @@ -0,0 +1,100 @@ +--- +subcategory: "Sentinel" +layout: "azurerm" +page_title: "Azure Resource Manager: Data Source: azurerm_sentinel_alert_rule_anomaly" +description: |- + Gets information about an existing Anomaly Alert Rule. +--- + +# Data Source: azurerm_sentinel_alert_rule_anomaly + +Use this data source to access information about an existing Anomaly Alert Rule. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_log_analytics_workspace" "example" { + name = "example-law" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + sku = "PerGB2018" +} + +resource "azurerm_security_insights_sentinel_onboarding" "example" { + resource_group_name = azurerm_resource_group.example.name + workspace_name = azurerm_log_analytics_workspace.example.name + customer_managed_key_enabled = false +} + +data "azurerm_sentinel_alert_rule_anomaly" "example" { + log_analytics_workspace_id = azurerm_log_analytics_workspace.example.id + display_name = "UEBA Anomalous Sign In" + + depends_on = [azurerm_sentinel_log_analytics_workspace_onboarding.example] +} + +output "id" { + value = data.azurerm_sentinel_alert_rule_anomaly.example.id +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `log_analytics_workspace_id` - (Required) The ID of the Log Analytics Workspace. + +* `name` - (Optional) The guid of this Sentinel Alert Rule Template. Either `display_name` or `name` have to be specified. + +* `display_name` - (Optional) The display name of this Sentinel Alert Rule Template. Either `display_name` or `name` have to be specified. + +~> **NOTE** One of `name` or `display_name` must be specified. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Anomaly Alert Rule. + +* `anomaly_settings_version` - The anomaly settings version of the Anomaly security ml analytics settings that dictates whether job version gets updated or not. + +* `anomaly_version` - The anomaly version of the Anomaly Alert Rule. + +* `customizable_observations` - The customizable observations of the Anomaly Alert Rule. + +* `description` - The description of the Anomaly Alert Rule. + +* `enabled` - Is the Anomaly Alert Rule enabled? + +* `frequency` - The frequency the Anomaly Alert Rule will be run. + +* `is_default_settings` - Whether this Anomaly Alert Rule is a default settings. + +* `required_data_connector` - A `required_data_connector` block as defined below. + +* `settings_definition_id` - The ID of the anomaly settings definition Id. + +* `Mode` - The Mode of the Anomaly Alert Rule. + +* `tactics` - A list of categories of attacks by which to classify the rule. + +* `techniques` - A list of techniques of attacks by which to classify the rule. + +--- + +A `required_data_connector` block exports the following: + +* `connector_id` - The ID of the required Data Connector. + +* `data_types` - A list of data types of the required Data Connector. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `read` - (Defaults to 5 minutes) Used when retrieving the Anomaly Alert Rule. diff --git a/website/docs/r/sentinel_alert_rule_anomaly_built_in.html.markdown b/website/docs/r/sentinel_alert_rule_anomaly_built_in.html.markdown new file mode 100644 index 000000000000..714e5235f540 --- /dev/null +++ b/website/docs/r/sentinel_alert_rule_anomaly_built_in.html.markdown @@ -0,0 +1,116 @@ +--- +subcategory: "Sentinel" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_sentinel_alert_rule_anomaly_built_in" +description: |- + Manages a Built-in Anomaly Alert Rule. +--- +## Disclaimers + +~> **Note:** A Built-in Anomaly Alert Rule could not be deleted. delete a Terraform managed Built-in Anomaly Alert Rule will cause the Built-in Anomaly Alert Rule to be disabled. + +# azurerm_sentinel_alert_rule_anomaly_built_in + +Manages a Built-in Anomaly Alert Rule. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_log_analytics_workspace" "example" { + name = "example-law" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + sku = "PerGB2018" +} + +resource "azurerm_security_insights_sentinel_onboarding" "example" { + resource_group_name = azurerm_resource_group.example.name + workspace_name = azurerm_log_analytics_workspace.example.name + customer_managed_key_enabled = false +} + +data "azurerm_sentinel_alert_rule_anomaly" "example" { + log_analytics_workspace_id = azurerm_log_analytics_workspace.example.id + display_name = "UEBA Anomalous Sign In" + depends_on = [azurerm_sentinel_log_analytics_workspace_onboarding.example] +} + +resource "azurerm_sentinel_alert_rule_anomaly_built_in" "example" { + display_name = "UEBA Anomalous Sign In" + log_analytics_workspace_id = azurerm_log_analytics_workspace.example.id + mode = "Production" + enabled = false +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Optional) The Name of the built-in Anomaly Alert Rule. Changing this forces a new Built-in Anomaly Alert Rule to be created. + +* `display_name` - (Optional) The Display Name of the built-in Anomaly Alert Rule. Changing this forces a new Built-in Anomaly Alert Rule to be created. + +~> **Note:** One of `name` or `display_name` block must be specified. + +* `log_analytics_workspace_id` - (Required) The ID of the Log Analytics Workspace. Changing this forces a new Built-in Anomaly Alert Rule to be created. + +* `enabled` - (Required) Should the Built-in Anomaly Alert Rule be enabled? + +* `mode` - (Required) mode of the Built-in Anomaly Alert Rule. Possible Values are `Production` and `Flighting`. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Built-in Anomaly Alert Rule. + +* `anomaly_settings_version` - The anomaly settings version of the Anomaly security ml analytics settings that dictates whether job version gets updated or not. + +* `anomaly_version` - The anomaly version of the Anomaly Alert Rule. + +* `description` - The description of the Anomaly Alert Rule. + +* `frequency` - The frequency the Anomaly Alert Rule will be run. + +* `is_default_settings` - Whether this Anomaly Alert Rule is a default settings. + +* `required_data_connector` - A `required_data_connector` block as defined below. + +* `settings_definition_id` - The ID of the anomaly settings definition Id. + +* `tactics` - A list of categories of attacks by which to classify the rule. + +* `techniques` - A list of techniques of attacks by which to classify the rule. + +* `customizable_observations` - The customizable observations of the Built-in Anomaly Alert Rule. + +--- + +A `required_data_connector` block exports the following: + +* `connector_id` - The ID of the required Data Connector. + +* `data_types` - A list of data types of the required Data Connector. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Built In Anomaly Alert Rule. +* `read` - (Defaults to 5 minutes) Used when retrieving the Built In Anomaly Alert Rule. +* `update` - (Defaults to 30 minutes) Used when updating the Built In Anomaly Alert Rule. +* `delete` - (Defaults to 5 minutes) Used when deleting the Built In Anomaly Alert Rule. + +## Import + +Built In Anomaly Alert Rules can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_sentinel_alert_rule_anomaly_built_in.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/securityMLAnalyticsSettings/setting1 +```