From 9c88d1ecd9e00fff2833fc05f572bdc4a56186fa Mon Sep 17 00:00:00 2001 From: Samir <85890442+sa-progress@users.noreply.github.com> Date: Wed, 1 Jun 2022 20:08:34 +0530 Subject: [PATCH] RESOURCE-135 F/alert rule (#477) * new resource added alert rule * read me added * read me added Signed-off-by: sa-progress * linting done * Update azure_sentinel_alert_rules.md * Update azure_sentinel_alert_rule.md * Create azure_sentinel_alert_rule.md * added * added tf Signed-off-by: sa-progress * updated the singular doc and deleted the unwanted file Signed-off-by: Soumyodeep Karmakar * Docs edits Signed-off-by: Ian Maddaus * Some Lang. fixes Signed-off-by: Deepa Kumaraswamy Co-authored-by: Soumyodeep Karmakar Co-authored-by: Ian Maddaus Co-authored-by: Sathish Co-authored-by: Deepa Kumaraswamy --- README.md | 52 +++---- .../resources/azure_sentinel_alert_rule.md | 133 ++++++++++++++++++ .../resources/azure_sentinel_alert_rules.md | 128 +++++++++++++++++ libraries/azure_sentinel_alert_rule.rb | 26 ++++ libraries/azure_sentinel_alert_rules.rb | 60 ++++++++ terraform/azure.tf | 13 ++ terraform/outputs.tf | 14 ++ .../controls/azure_sentinel_alert_rule.rb | 22 +++ .../controls/azure_sentinel_alert_rules.rb | 16 +++ .../azure_sentinel_alert_rule_test.rb | 17 +++ .../azure_sentinel_alert_rules_test.rb | 25 ++++ 11 files changed, 482 insertions(+), 24 deletions(-) create mode 100644 docs-chef-io/content/inspec/resources/azure_sentinel_alert_rule.md create mode 100644 docs-chef-io/content/inspec/resources/azure_sentinel_alert_rules.md create mode 100644 libraries/azure_sentinel_alert_rule.rb create mode 100644 libraries/azure_sentinel_alert_rules.rb create mode 100644 test/integration/verify/controls/azure_sentinel_alert_rule.rb create mode 100644 test/integration/verify/controls/azure_sentinel_alert_rules.rb create mode 100644 test/unit/resources/azure_sentinel_alert_rule_test.rb create mode 100644 test/unit/resources/azure_sentinel_alert_rules_test.rb diff --git a/README.md b/README.md index 1f1c1ae19..fe82339a2 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ This InSpec resource pack uses the Azure REST API and provides the required reso - [Parameters Applicable To All Resources](#parameters-applicable-to-all-resources) - [`api_version`](#api_version) - [User-Provided API Version](#user-provided-api-version) - - [Pre-defined Default API Version](#pre-defined-default-api-version) + - [Pre-defined Default Api Version](#pre-defined-default-api-version) - [Latest API Version](#latest-api-version) - [endpoint](#endpoint) - [http_client parameters](#http_client-parameters) @@ -59,23 +59,25 @@ You must have the following pieces of information: To create your account Service Principal Account: -1. Log in to the Azure portal. +1. Log in to the **Azure portal**. 1. Click **Azure Active Directory**. 1. Click **APP registrations**. 1. Click **New application registration**. -1. Enter name and select **Web** from the **Application Type** drop-down. Save your application. -1. Note your Application ID. This is your **client_id** above. +1. Enter name and select **Web** from the **Application Type** drop-down. +1. Save your application. +1. Note your Application ID. This is your **client_id**. 1. Click **Certificates & secrets**. 1. Click **New client secret**. 1. Create a new password. This value is your **client_secret** above. -1. Go to your subscription (click on **All Services** then subscriptions). Choose your subscription from that list. -1. Note your Subscription ID can be found here. +1. Go to your subscription (click **All Services** then subscriptions). +1. Choose your subscription from that list. +1. Note your **Subscription ID**. 1. Click **Access control (IAM)**. 1. Click **Add**. 1. Select the **reader** role. -1. Select the application you created and save. +1. Select the application you created and click **save**. -These must be stored in an environment variables prefaced with `AZURE_`. If you use Dotenv, then you may save these values in your own `.envrc` file. Either source it or run `direnv allow`. If you don't use `Dotenv`, then you may just create environment variables in the way that you prefer. +These must be stored in an environment variables prefaced with `AZURE_`. If you use Dotenv, then you can save these values in your own `.envrc` file. Either source it or run `direnv allow`. If you do not use `Dotenv`, then you can create environment variables in the way that you prefer. ### Use the Resources @@ -114,7 +116,7 @@ The following is a list of generic resources. With the generic resources: -- Azure cloud resources that this resource pack does not include a static InSpec resource for can be tested. +- Azure cloud resources pack, which does not include a static InSpec resource and can be tested. - Azure resources from different resource providers and resource groups can be tested at the same time. - Server-side filtering can be used for more efficient tests. @@ -437,6 +439,8 @@ The following is a list of static resources. - [azure_service_bus_topic](docs/resources/azure_service_bus_topic.md) - [azure_service_bus_topics](docs/resources/azure_service_bus_topics.md) - [azure_service_bus_regions](docs/resources/azure_service_bus_regions.md) +- [azure_sentinel_alert_rule](docs/resources/azure_sentinel_alert_rule.md) +- [azure_sentinel_alert_rules](docs/resources/azure_sentinel_alert_rules.md) - [azure_sql_database](docs/resources/azure_sql_database.md) - [azure_sql_databases](docs/resources/azure_sql_databases.md) - [azure_sql_server](docs/resources/azure_sql_server.md) @@ -533,8 +537,7 @@ The generic resources and their derivations support the following parameters unl ### `api_version` -As an Azure resource provider enables new features, it releases a new version of the REST API. They are generally in the format of `2020-01-01`. -InSpec Azure resources can be forced to use a specific version of the API to eliminate the behavioral changes between the tests using different API versions. The latest version will be used unless a specific version is provided. +As an Azure resource provider enables new features, it releases a new version of the REST API. They are generally in the format of `2020-01-01`. InSpec Azure resources can be forced to use a specific version of the API to eliminate the behavioral changes between the tests using different API versions. The latest version is used unless a specific version is provided. ### User-Provided API Version @@ -547,43 +550,43 @@ end ### Pre-defined Default Api Version -`default` api version can be used if it is supported by the resource provider. +`DEFAULT` api version can be used, if it is supported by the resource provider. ```ruby describe azure_generic_resource(resource_provider: 'Microsoft.Compute/virtualMachines', name: 'NAME', api_version: 'DEFAULT') do - its('api_version_used_for_query_state') { should eq 'default' } + its('api_version_used_for_query_state') { should eq 'DEFAULT' } end ``` ### Latest API Version -`latest` version will be determined by this resource pack within the supported API versions. If the latest version is a `preview`, than an older, but a stable version might be used. Explicitly forcing to use the `latest` version. +`LATEST` version is determined by this resource pack within the supported API versions. If the latest version is a `preview`, than an older, but a stable version might be used. Explicitly forcing to use the `LATEST` version. ```ruby -describe azure_virtual_networks(api_version: 'latest') do - its('api_version_used_for_query_state') { should eq 'latest' } +describe azure_virtual_networks(api_version: 'LATEST') do + its('api_version_used_for_query_state') { should eq 'LATEST' } end ``` -`latest` version will be used unless provided (Implicit). +`LATEST` version is used unless provided (Implicit). ```ruby describe azure_network_security_groups(resource_group: 'RESOURCE_GROUP') do - its('api_version_used_for_query_state') { should eq 'latest' } + its('api_version_used_for_query_state') { should eq 'LATEST' } end ``` -`latest` version will be used if the provided is invalid. +`LATEST` version is used if the provided is invalid. ```ruby -describe azure_network_security_groups(resource_group: 'my_group', api_version: 'invalid_api_version') do - its('api_version_used_for_query_state') { should eq 'latest' } +describe azure_network_security_groups(resource_group: 'RESOURCE_GROUP', api_version: 'invalid_api_version') do + its('api_version_used_for_query_state') { should eq 'LATEST' } end ``` ### endpoint -Microsoft Azure cloud services are available through a global and three national networks of the datacenter as described [here](https://docs.microsoft.com/en-us/graph/deployments). The preferred data center can be defined via `endpoint` parameter. Azure Global Cloud will be used if not provided. +Microsoft Azure cloud services are available through a global and three national networks of the datacenter as described [here](https://docs.microsoft.com/en-us/graph/deployments). The preferred data center can be defined via `endpoint` parameter. Azure Global Cloud is used if not provided. - `azure_cloud` (default) - `azure_china_cloud` @@ -663,7 +666,7 @@ They can be defined as environment variables or resource parameters (has priorit ## Development -If you'd like to contribute to this project, please see [Contributing Rules](CONTRIBUTING.md). +If you would like to contribute to this project, please see [Contributing Rules](CONTRIBUTING.md). For a detailed walk-through of resource creation, see the [Resource Creation Guide](dev-docs/resource_creation_guide.md). @@ -702,7 +705,7 @@ A plural resource is used to test the collection of resources of a specific type ### Setting the Environment Variables -The following instructions will help you get your development environment set up to run integration tests. +The following instructions helps you get your development environment setup to run integration tests. Copy `.envrc-example` to `.envrc` and fill in the fields with the values from your account. @@ -787,6 +790,7 @@ rake test:integration ``` Please note that Graph API resource requires specific privileges granted to your service principal. + Please refer to the [Microsoft Documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-integrating-applications#updating-an-application) for information on how to grant these permissions to your application. To run a control called `azure_virtual_machine` only: diff --git a/docs-chef-io/content/inspec/resources/azure_sentinel_alert_rule.md b/docs-chef-io/content/inspec/resources/azure_sentinel_alert_rule.md new file mode 100644 index 000000000..3387a55c5 --- /dev/null +++ b/docs-chef-io/content/inspec/resources/azure_sentinel_alert_rule.md @@ -0,0 +1,133 @@ ++++ +title = "azure_sentinel_alert_rule Resource" +platform = "azure" +draft = false +gh_repo = "inspec-azure" + +[menu.inspec] +title = "azure_sentinel_alert_rule" +identifier = "inspec/resources/azure/azure_sentinel_alert_rule Resource" +parent = "inspec/resources/azure" ++++ + +Use the `azure_sentinel_alert_rule` InSpec audit resource to test properties of an Azure Sentinel alert rule for a resource group or the entire subscription. + +For additional information, see the [`Azure Sentinel Alert Rules API documentation`](https://docs.microsoft.com/en-us/rest/api/datafactory/pipeline-runs/query-by-factory). + +## Azure REST API Version, Endpoint, and HTTP Client Parameters + +{{% inspec_azure_common_parameters %}} + +## Installation + +{{% inspec_azure_install %}} + +## Syntax + +An `azure_sentinel_alert_rule` resource block returns all Azure alert_rule, either within a Resource Group (if provided), or within an entire Subscription. + +```ruby +describe azure_sentinel_alert_rule(resource_group: 'RESOURCE_GROUP', workspace_name: 'WORKSPACE_NAME', rule_id: 'RULE_ID') do + it { should exit } +end +``` + +## Parameters + +`resource_group` _(required)_ + +: Azure resource group that the targeted resource resides in. + +`workspace_name` _(required)_ + +: Azure workspace Name for which alert rule is retrieved. + +`rule_id` _(required)_ + +: Alert rule ID. + +## Properties + +`id` +: The ID of the alert rule. + +`name` +: The name of the alert rule. + +`type` +: The alert rule type. + +`kind` +: The kind of the alert rule. + +`etag` +: The etag of the alert rule. + +`properties` +: The properties of the alert rule. + +## Examples + +Tests if the rule ID exists. + +```ruby +describe azure_sentinel_alert_rule(resource_group: 'RESOURCE_GROUP', workspace_name: 'WORKSPACE_NAME', rule_id: 'RULE_ID') do + its('id') { should eq 'ALERT_RULE_ID' } +end +``` + +Tests if the rule name exists. + +```ruby +describe azure_sentinel_alert_rule(resource_group: 'RESOURCE_GROUP', workspace_name: 'WORKSPACE_NAME', rule_id: 'RULE_ID') do + its('name') { should eq 'ALERT_RULE_NAME' } +end +``` + +Tests if the rule kind is `Scheduled`. + +```ruby +describe azure_sentinel_alert_rule(resource_group: 'RESOURCE_GROUP', workspace_name: 'WORKSPACE_NAME', rule_id: 'RULE_ID') do + its('kind') { should eq 'Scheduled' } +end +``` + +Test if the rule type is `Microsoft.SecurityInsights/alertRules`. + +```ruby +describe azure_sentinel_alert_rule(resource_group: 'RESOURCE_GROUP', workspace_name: 'WORKSPACE_NAME', rule_id: 'RULE_ID') do + its('type') { should eq 'Microsoft.SecurityInsights/alertRules' } +end +``` + +Test if the display name is present or not. + +```ruby +describe azure_sentinel_alert_rule(resource_group: 'RESOURCE_GROUP', workspace_name: 'WORKSPACE_NAME', rule_id: 'RULE_ID') do + its('properties.displayName') { should eq "DISPLAY_NAME" } +end +``` + +## Matchers + +This InSpec audit resource has the following special matchers. For a full list of available matchers, please visit our [Universal Matchers page](https://www.inspec.io/docs/reference/matchers/). + +### exists + +```ruby +# If we expect a resource to always exist + +describe azure_sentinel_alert_rule(resource_group: 'RESOURCE_GROUP', workspace_name: 'WORKSPACE_NAME', rule_id: 'RULE_ID') do + it { should exist } +end + +# If we expect a resource to never exist + +describe azure_sentinel_alert_rule(resource_group: 'RESOURCE_GROUP', workspace_name: 'WORKSPACE_NAME', rule_id: 'RULE_ID') do + it { should_not exist } +end +``` + +## Azure Permissions + +{{% azure_permissions_service_principal role="contributor" %}} diff --git a/docs-chef-io/content/inspec/resources/azure_sentinel_alert_rules.md b/docs-chef-io/content/inspec/resources/azure_sentinel_alert_rules.md new file mode 100644 index 000000000..bde52deb7 --- /dev/null +++ b/docs-chef-io/content/inspec/resources/azure_sentinel_alert_rules.md @@ -0,0 +1,128 @@ ++++ +title = "azure_sentinel_alert_rules Resource" +platform = "azure" +draft = false +gh_repo = "inspec-azure" + +[menu.inspec] +title = "azure_sentinel_alert_rules" +identifier = "inspec/resources/azure/azure_sentinel_alert_rules Resource" +parent = "inspec/resources/azure" ++++ + +Use the `azure_sentinel_alert_rules` Chef InSpec audit resource to test properties of an Azure Sentinel alert rule for a resource group or the entire subscription. + +For additional information, see the [`Azure Sentinel Alert Rules API documentation`](https://docs.microsoft.com/en-us/rest/api/datafactory/pipeline-runs/query-by-factory). + +## Azure REST API Version, Endpoint, and HTTP Client Parameters + +{{% inspec_azure_common_parameters %}} + +## Installation + +{{% inspec_azure_install %}} + +## Syntax + +An `azure_sentinel_alert_rules` resource block returns all Azure Sentinel alerts rules, either within a Resource Group (if provided), or within an entire Subscription. + +```ruby +describe azure_sentinel_alert_rules(resource_group: 'RESOURCE_GROUP', workspace_name: 'WORKSPACE_NAME') do + #... +end +``` + +## Parameters + +`resource_group` _(required)_ + +: The name of the resource group. + +`workspace_name` _(required)_ + +: The name of the workspace. + +## Properties + +`names` +: A list of the unique resource names. + +: **Field**: `name` + +`ids` +: A list of alert_rule IDs . + +: **Field**: `id` + +`properties` +: A list of properties for the resource. + +: **Field**: `properties` + +`types` +: A list of types for each resource. + +: **Field**: `type` + +`severities` +: The list of severity for alerts created by this alert rule. + +: **Field**: `severity` + +`displayNames` +: The List of display name for alerts created by this alert rule. + +: **Field**: `displayName` + +`enableds` +: The list of flags which Determines whether this alert rule is enabled or disabled. + +: **Field**: `enabled` + +`kinds` +: The alert rule kind. + +: **Field**: `kind` + +`alertRuleTemplateNames` +: The Name of the alert rule template used to create this rule. + +: **Field**: `alertRuleTemplateName` + +{{% inspec_filter_table %}} + +## Examples + +**Test if properties match.** + +```ruby +describe azure_sentinel_alert_rules(resource_group: resource_group, workspace_name: workspace_name) do + its('names') { should include 'BuiltInFusion' } + its('types') { should include 'Microsoft.SecurityInsights/alertRules' } + its('kinds') { should include 'Fusion' } + its('severities') { should include 'High' } + its('enableds') { should include true } + its('displayNames') { should include 'Advanced Multistage Attack Detection' } + its('alertRuleTemplateNames') { should include 'f71aba3d-28fb-450b-b192-4e76a83015c8' } +end +``` + +**Test if any alert rules exist in the resource group.** + +```ruby +describe azure_sentinel_alert_rules(resource_group: 'RESOURCE_GROUP', workspace_name: 'WORKSPACE_NAME') do + it { should exist } +end +``` + +**Test that there aren't any alert rules in a resource group.** + +```ruby +describe azure_sentinel_alert_rules(resource_group: 'RESOURCE_GROUP', workspace_name: 'WORKSPACE_NAME') do + it { should_not exist } +end +``` + +## Azure Permissions + +{{% azure_permissions_service_principal role="contributor" %}} diff --git a/libraries/azure_sentinel_alert_rule.rb b/libraries/azure_sentinel_alert_rule.rb new file mode 100644 index 000000000..de9677d34 --- /dev/null +++ b/libraries/azure_sentinel_alert_rule.rb @@ -0,0 +1,26 @@ +require 'azure_generic_resource' + +class AzureSentinelAlertRule < AzureGenericResource + name 'azure_sentinel_alert_rule' + desc 'Verifies settings for an Sentinel Alert Rule' + example <<-EXAMPLE + describe azure_sentinel_alert_rule(resource_group: 'example', workspace_name: 'workspaceName', rule_id: 'rule_id') do + it { should exit } + end + EXAMPLE + def initialize(opts = {}) + # Options should be Hash type. Otherwise Ruby will raise an error when we try to access the keys. + raise ArgumentError, 'Parameters must be provided in an Hash object.' unless opts.is_a?(Hash) + + opts[:resource_provider] = specific_resource_constraint('Microsoft.OperationalInsights/workspaces', opts) + opts[:required_parameters] = %i(workspace_name) + opts[:resource_path] = [opts[:workspace_name], 'providers/Microsoft.SecurityInsights/alertRules/'].join('/') + opts[:resource_identifiers] = %i(rule_id) + # static_resource parameter must be true for setting the resource_provider in the backend. + super(opts, true) + end + + def to_s + super(AzureSentinelAlertRule) + end +end diff --git a/libraries/azure_sentinel_alert_rules.rb b/libraries/azure_sentinel_alert_rules.rb new file mode 100644 index 000000000..48d3141f7 --- /dev/null +++ b/libraries/azure_sentinel_alert_rules.rb @@ -0,0 +1,60 @@ +require 'azure_generic_resources' + +class AzureSentinelAlertRules < AzureGenericResources + name 'azure_sentinel_alert_rules' + desc 'Verifies settings for Azure Alert Rule' + example <<-EXAMPLE + azure_sentinel_alert_rules(resource_group: 'example', workspace_name: 'workspaceName') do + it{ should exist } + end + EXAMPLE + + attr_reader :table + + def initialize(opts = {}) + # Options should be Hash type. Otherwise Ruby will raise an error when we try to access the keys. + raise ArgumentError, 'Parameters must be provided in an Hash object.' unless opts.is_a?(Hash) + + opts[:resource_provider] = specific_resource_constraint('Microsoft.OperationalInsights/workspaces', opts) + opts[:required_parameters] = %i(workspace_name) + opts[:resource_path] = [opts[:workspace_name], 'providers/Microsoft.SecurityInsights/alertRules/'].join('/') + # static_resource parameter must be true for setting the resource_provider in the backend. + super(opts, true) + # Check if the resource is failed. + # It is recommended to check that after every usage of superclass methods or API calls. + return if failed_resource? + + # Define the column and field names for FilterTable. + # - column: It is defined as an instance method, callable on the resource, and present `field` values in a list. + # - field: It has to be identical with the `key` names in @table items that will be presented in the FilterTable. + # @see https://github.com/inspec/inspec/blob/master/docs/dev/filtertable-usage.md + + # FilterTable is populated at the very end due to being an expensive operation. + populate_filter_table_from_response + end + + def to_s + super(AzureSentinelAlertRules) + end + + private + + def flatten_hash(hash) + hash.each_with_object({}) do |(k, v), h| + if v.is_a? Hash + flatten_hash(v).map do |h_k, h_v| + h["#{k}_#{h_k}".to_sym] = h_v + end + else + h[k] = v + end + end + end + + def populate_table + @resources.each do |resource| + resource = resource.merge(resource[:properties]) + @table << flatten_hash(resource) + end + end +end diff --git a/terraform/azure.tf b/terraform/azure.tf index ec11c0426..4f9aee896 100644 --- a/terraform/azure.tf +++ b/terraform/azure.tf @@ -1703,4 +1703,17 @@ resource "azurerm_hpc_cache" "inspec_hpc_cache" { cache_size_in_gb = 3072 subnet_id = azurerm_subnet.subnet.id sku_name = "Standard_2G" +} + +resource "azurerm_sentinel_alert_rule_scheduled" "alert_rule_scheduled" { + name = "azurerm_sentinel_alert_rule_scheduled" + log_analytics_workspace_id = azurerm_log_analytics_workspace.workspace.id + display_name = "sentinel_alert_rule" + severity = "High" + query = <