diff --git a/README.md b/README.md index bf03f3d9b..d0c801edd 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,8 @@ The static resources derived from the generic resources prepended with `azure_` - [azure_network_interfaces](docs/resources/azure_network_interfaces.md) - [azure_network_security_group](docs/resources/azure_network_security_group.md) - [azure_network_security_groups](docs/resources/azure_network_security_groups.md) +- [azure_network_watcher](docs/resources/azure_network_watcher.md) +- [azure_network_watchers](docs/resources/azure_network_watchers.md) - [azure_policy_definition](docs/resources/azure_policy_definition.md) - [azure_policy_definitions](docs/resources/azure_policy_definitions.md) - [azure_postgresql_database](docs/resources/azure_postgresql_database.md) diff --git a/docs/resources/azure_network_watcher.md b/docs/resources/azure_network_watcher.md new file mode 100644 index 000000000..be98f3a81 --- /dev/null +++ b/docs/resources/azure_network_watcher.md @@ -0,0 +1,113 @@ +--- +title: About the azure_network_watcher Resource +platform: azure +--- + +# azure_network_watcher + +Use the `azure_network_watcher` InSpec audit resource to test properties of an Azure network watcher. + +## Azure REST API version, endpoint and http client parameters + +This resource interacts with api versions supported by the resource provider. +The `api_version` can be defined as a resource parameter. +If not provided, the latest version will be used. +For more information, refer to [`azure_generic_resource`](azure_generic_resource.md). + +Unless defined, `azure_cloud` global endpoint, and default values for the http client will be used. +For more information, refer to the resource pack [README](../../README.md). + +## Availability + +### Installation + +This resource is available in the [InSpec Azure resource pack](https://github.com/inspec/inspec-azure). +For an example `inspec.yml` file and how to set up your Azure credentials, refer to resource pack [README](../../README.md#Service-Principal). + +## Syntax + +An `azure_network_watcher` resource block identifies a network watcher by `name` and `resource_group` or the `resource_id`. +```ruby +describe azure_network_watcher(resource_group: 'resourceGroupName', name: 'networkWatcherName') do + it { should exist } +end +``` +```ruby +describe azure_network_watcher(resource_id: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/networkWatchers/{networkWatcherName}') do + it { should exist } +end +``` +## Parameters + +| Name | Description | +|--------------------------------|-----------------------------------------------------------------------------------| +| resource_group | Azure resource group that the network watcher resides in. `resourceGroupName` | +| name | Name of the network watcher to test. `networkWatcherName` | +| resource_id | The unique resource ID. `/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/networkWatchers/{networkWatcherName}` | +| flow_logs_api_version | The flow log status endpoint api version used for creating `flow_logs` property. The latest version will be used unless provided. A network security group within the same region can be targeted for getting the flow log statuses. For more, see [here](https://docs.microsoft.com/en-us/rest/api/network-watcher/networkwatchers/getflowlogstatus).| +| nsg_resource_id | The unique resource ID of the network security group being targeted to get the flow log statuses. `/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/networkSecurityGroups/{networkSecurityGroupName}` | +| nsg_resource_group | The resource group of the network security group being targeted to get the flow log statuses. This requires `nsg_name` to be provided. | +| nsg_name | The name of the network security group being targeted to get the flow log statuses. This requires `nsg_resource_group` to be provided.| + + +Either one of the parameter sets can be provided for a valid query: +- `resource_id` +- `resource_group` and `name` + +## Properties + +| Property | Description | +|-----------------------|-------------| +| provisioning_state | The provisioning state of the network watcher resource. For the valid values, see [here](https://docs.microsoft.com/en-us/rest/api/network-watcher/networkwatchers/get#provisioningstate). | +| flow_logs | Information on the configuration of flow log and traffic analytics (optional) in [this format](https://docs.microsoft.com/en-us/rest/api/network-watcher/networkwatchers/getflowlogstatus#flowloginformation). All properties can be accessed via dot notation, e.g.: `flow_logs.properties.enabled`. This resource supports targeting network security groups defined at resource creation only. | + +For properties applicable to all resources, such as `type`, `name`, `id`, `properties`, refer to [`azure_generic_resource`](azure_generic_resource.md#properties). + +Also, refer to [Azure documentation](https://docs.microsoft.com/en-us/rest/api/network-watcher/networkwatchers/get#networkwatcher) for other properties available. +Any attribute in the response may be accessed with the key names separated by dots (`.`). + +## Examples + +### Test the Location of a Network Watcher +```ruby +describe azure_network_watcher(resource_group: 'resourceGroupName', name: 'networkWatcherName') do + its('location') { should cmp 'eastus' } +end +``` +### Test the Flow Log Status of a Network Security Group +```ruby +describe azure_network_watcher(resource_group: 'resourceGroupName', name: 'networkWatcherName', nsg_resource_group: 'nsg_rg', nsg_name: 'nsg_eastus') do + its('flow_logs.properties.enabled') { should be true } + its('flow_logs.properties.retentionPolicy.days') { should be >= 90 } +end +``` +### Loop through Network Security Groups with the Resource ID +```ruby +azure_network_security_groups.where(location: 'eastus').ids.each do |nsg_id| + describe azure_network_watcher(resource_group: 'resourceGroupName', name: 'networkWatcherName', nsg_resource_id: nsg_id) do + its('flow_logs.properties.enabled') { should be true } + its('flow_logs.properties.retentionPolicy.days') { should be >= 90 } + end +end +``` +See [integration tests](../../test/integration/verify/controls/azurerm_network_watcher.rb) for more examples. + +## Matchers + +This InSpec audit resource has the following special matchers. For a full list of available matchers, please visit our [Universal Matchers page](https://docs.chef.io/inspec/matchers/). + +### exists +```ruby +# If we expect 'networkWatcherName' to always exist +describe azure_network_watcher(resource_group: 'resourceGroupName', name: 'networkWatcherName') do + it { should exist } +end + +# If we expect 'networkWatcherName' to never exist +describe azure_network_watcher(resource_group: 'resourceGroupName', name: 'networkWatcherName') do + it { should_not exist } +end +``` +## Azure Permissions + +Your [Service Principal](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal) must be setup with a `contributor` role on the subscription you wish to test. diff --git a/docs/resources/azure_network_watchers.md b/docs/resources/azure_network_watchers.md new file mode 100644 index 000000000..51918afe5 --- /dev/null +++ b/docs/resources/azure_network_watchers.md @@ -0,0 +1,84 @@ +--- +title: About the azure_network_watchers Resource +platform: azure +--- + +# azure_network_watchers + +Use the `azure_network_watchers` InSpec audit resource to test properties and configuration of multiple Azure network watchers. + +## Azure REST API version, endpoint and http client parameters + +This resource interacts with api versions supported by the resource provider. +The `api_version` can be defined as a resource parameter. +If not provided, the latest version will be used. +For more information, refer to [`azure_generic_resource`](azure_generic_resource.md). + +Unless defined, `azure_cloud` global endpoint, and default values for the http client will be used. +For more information, refer to the resource pack [README](../../README.md). + +## Availability + +### Installation + +This resource is available in the [InSpec Azure resource pack](https://github.com/inspec/inspec-azure). +For an example `inspec.yml` file and how to set up your Azure credentials, refer to resource pack [README](../../README.md#Service-Principal). + +## Syntax + +An `azure_network_watchers` resource block returns all network watchers, either within a Resource Group (if provided), or within an entire Subscription. +```ruby +describe azure_network_watchers do + #... +end +``` +or +```ruby +describe azure_network_watchers(resource_group: 'my-rg') do + #... +end +``` +## Parameters + +- `resource_group` (Optional) + +## Properties + +|Property | Description | Filter Criteria* | +|---------------|--------------------------------------------------------------------------------------|-----------------| +| ids | A list of the unique resource ids. | `id` | +| locations | A list of locations for all the resources being interrogated. | `location` | +| names | A list of names of all the resources being interrogated. | `name` | +| tags | A list of `tag:value` pairs defined on the resources being interrogated. | `tags` | + +* For information on how to use filter criteria on plural resources refer to [FilterTable usage](https://github.com/inspec/inspec/blob/master/dev-docs/filtertable-usage.md). + +## Examples + +### Test that an Example Resource Group has the Named Network Watcher +```ruby +describe azure_network_watchers(resource_group: 'ExampleGroup') do + its('names') { should include('NetworkWatcherName') } +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 + +The control will pass if the filter returns at least one result. Use `should_not` if you expect zero matches. +```ruby +# If we expect 'ExampleGroup' Resource Group to have Network Watchers +describe azure_network_watchers(resource_group: 'ExampleGroup') do + it { should exist } +end + +# If we expect 'EmptyExampleGroup' Resource Group to not have Network Watchers +describe azure_network_watchers(resource_group: 'EmptyExampleGroup') do + it { should_not exist } +end +``` +## Azure Permissions + +Your [Service Principal](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal) must be setup with a `contributor` role on the subscription you wish to test. diff --git a/docs/resources/azurerm_network_watcher.md b/docs/resources/azurerm_network_watcher.md index ec97663d9..48e6f338e 100644 --- a/docs/resources/azurerm_network_watcher.md +++ b/docs/resources/azurerm_network_watcher.md @@ -3,6 +3,8 @@ title: About the azurerm_network_watcher Resource platform: azure --- +> WARNING This resource will be deprecated in InSpec Azure Resource Pack version **2**. Please start using fully backward compatible [`azure_network_watcher`](azure_network_watcher.md) InSpec audit resource. + # azurerm\_network\_watcher Use the `azurerm_network_watcher` InSpec audit resource to test properties of an Azure diff --git a/docs/resources/azurerm_network_watchers.md b/docs/resources/azurerm_network_watchers.md index 88f0c1923..2a4e4efb1 100644 --- a/docs/resources/azurerm_network_watchers.md +++ b/docs/resources/azurerm_network_watchers.md @@ -3,6 +3,8 @@ title: About the azurerm_network_watchers Resource platform: azure --- +> WARNING This resource will be deprecated in InSpec Azure Resource Pack version **2**. Please start using fully backward compatible [`azure_networke_watchers`](azure_network_watchers.md) InSpec audit resource. + # azurerm\_network\_watchers Use the `azurerm_network_watchers` InSpec audit resource to verify that a Network Watcher diff --git a/libraries/azure_network_watcher.rb b/libraries/azure_network_watcher.rb new file mode 100644 index 000000000..aec2040bc --- /dev/null +++ b/libraries/azure_network_watcher.rb @@ -0,0 +1,104 @@ +require 'azure_generic_resource' + +class AzureNetworkWatcher < AzureGenericResource + name 'azure_network_watcher' + desc 'Verifies settings for Network Watchers' + example <<-EXAMPLE + describe azure_network_watcher(resource_group: 'example', name: 'name') do + its(name) { should eq 'name'} + end + EXAMPLE + + # This is for backward compatibility. + attr_accessor :nsg + + 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.Network/networkWatchers', opts) + opts[:allowed_parameters] = %i(flow_logs_api_version nsg_resource_id nsg_resource_group nsg_name) + opts[:flow_logs_api_version] ||= 'latest' + + # static_resource parameter must be true for setting the resource_provider in the backend. + super(opts, true) + end + + def to_s + super(AzureNetworkWatcher) + end + + def provisioning_state + return unless exists? + properties&.provisioningState + end + + def flow_logs + return unless exists? + # This is for backward compatibility. !!! NOT RECOMMENDED !!! + # nsg is the name of the network security group. + # It can be defined on the instance of this resource. + # nw = azure_network_watcher(resource_group: resource_group, name: nw_name) + # nw.nsg = nsg + # It must be within the same resource group with the network watcher. + if nsg + target_resource_id = validate_resource_uri( + { + resource_uri: + "/resourceGroups/#{@opts[:resource_group]}/providers/Microsoft.Network/networkSecurityGroups/#{nsg}", + add_subscription_id: true, + }, + ) + elsif @opts[:nsg_resource_group] && @opts[:nsg_name] + # network security group can be provided with its resource group and name. + target_resource_id = validate_resource_uri( + { + resource_uri: + "/resourceGroups/#{@opts[:nsg_resource_group]}/providers/Microsoft.Network/networkSecurityGroups/"\ + "#{@opts[:nsg_name]}", + add_subscription_id: true, + }, + ) + elsif @opts[:nsg_resource_id] + # network security group can be provided with its resource id. + target_resource_id = @opts[:nsg_resource_id] + else + raise ArgumentError, + 'The resource group (nsg_resource_group) and the name (nsg_name) of the network security group or '\ + 'the resource id (nsg_resource_id) must be provided at resource initiation.' + end + req_body_hash = { + targetResourceId: target_resource_id, + } + additional_resource_properties( + { + property_name: 'flow_logs', + property_endpoint: id + '/queryFlowLogStatus', + api_version: @opts[:flow_logs_api_version], + method: 'post', + req_body: JSON.generate(req_body_hash), + headers: { 'Content-Type' => 'application/json' }, + }, + ) + end +end + +# Provide the same functionality under the old resource name. +# This is for backward compatibility. +class AzurermNetworkWatcher < AzureNetworkWatcher + name 'azurerm_network_watcher' + desc 'Verifies settings for Network Watchers' + example <<-EXAMPLE + describe azurerm_network_watcher(resource_group: 'example', name: 'name') do + its(name) { should eq 'name'} + end + EXAMPLE + + def initialize(opts = {}) + Inspec::Log.warn Helpers.resource_deprecation_message(@__resource_name__, AzureNetworkWatcher.name) + # For backward compatibility. + opts[:api_version] ||= '2018-02-01' + opts[:flow_logs_api_version] ||= '2019-04-01' + super + end +end diff --git a/libraries/azure_network_watchers.rb b/libraries/azure_network_watchers.rb new file mode 100644 index 000000000..e67d364f2 --- /dev/null +++ b/libraries/azure_network_watchers.rb @@ -0,0 +1,64 @@ +require 'azure_generic_resources' + +class AzureNetworkWatchers < AzureGenericResources + name 'azure_network_watchers' + desc 'Verifies settings for Network Watchers' + example <<-EXAMPLE + azure_network_watchers(resource_group: 'example') 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.Network/networkWatchers', opts) + opts[:allowed_parameters] = %i(resource_group) + + # 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 inherited methods or making API calls. + return if failed_resource? + + # Define the column and field names for FilterTable. + # In most cases, the `column` should be the pluralized form of the `field`. + # @see https://github.com/inspec/inspec/blob/master/docs/dev/filtertable-usage.md + table_schema = [ + { column: :names, field: :name }, + { column: :ids, field: :id }, + { column: :tags, field: :tags }, + { column: :locations, field: :location }, + ] + + # FilterTable is populated at the very end due to being an expensive operation. + AzureGenericResources.populate_filter_table(:table, table_schema) + end + + def to_s + super(AzureNetworkWatchers) + end +end + +# Provide the same functionality under the old resource name. +# This is for backward compatibility. +class AzurermNetworkWatchers < AzureNetworkWatchers + name 'azurerm_network_watchers' + desc 'Verifies settings for Network Watchers' + example <<-EXAMPLE + azurerm_network_watchers(resource_group: 'example') do + it{ should exist } + end + EXAMPLE + + def initialize(opts = {}) + Inspec::Log.warn Helpers.resource_deprecation_message(@__resource_name__, AzureNetworkWatchers.name) + # For backward compatibility. + opts[:api_version] ||= '2018-02-01' + super + end +end diff --git a/libraries/azurerm_network_watcher.rb b/libraries/azurerm_network_watcher.rb deleted file mode 100644 index b40beaf74..000000000 --- a/libraries/azurerm_network_watcher.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -require 'azurerm_resource' - -class AzurermNetworkWatcher < AzurermSingularResource - name 'azurerm_network_watcher' - desc 'Verifies settings for Network Watchers' - example <<-EXAMPLE - describe azurerm_network_watcher(resource_group: 'example', name: 'name') do - its(name) { should eq 'name'} - end - EXAMPLE - - ATTRS = %i( - name - id - etag - type - location - tags - properties - ).freeze - - attr_reader(*ATTRS) - attr_accessor(:nsg) - - def initialize(resource_group: nil, name: nil) - resp = management.network_watcher(resource_group, name) - return if has_error?(resp) - - assign_fields(ATTRS, resp) - - @resource_group = resource_group - @exists = true - end - - def to_s - "'#{name}' Network Watcher" - end - - def flow_logs - return nil if @nsg.nil? - @flow_logs ||= management.network_watcher_flow_log_status(@resource_group, name, nsg) - end - - def provisioning_state - @provisioning_state ||= properties.provisioningState - end -end diff --git a/libraries/azurerm_network_watchers.rb b/libraries/azurerm_network_watchers.rb deleted file mode 100644 index 49653fe4e..000000000 --- a/libraries/azurerm_network_watchers.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -require 'azurerm_resource' - -class AzurermNetworkWatchers < AzurermPluralResource - name 'azurerm_network_watchers' - desc 'Verifies settings for Network Watchers' - example <<-EXAMPLE - azurerm_network_watchers(resource_group: 'example') do - it{ should exist } - end - EXAMPLE - - attr_reader :table - - FilterTable.create - .register_column(:names, field: :name) - .install_filter_methods_on_resource(self, :table) - - def initialize(resource_group = nil) - resource_group = resource_group[:resource_group] if resource_group.is_a?(Hash) - resp = management.network_watchers(resource_group) - return if has_error?(resp) - - @table = resp - end - - include Azure::Deprecations::StringsInWhereClause - - def to_s - 'Network Watchers' - end -end diff --git a/test/integration/verify/controls/azurerm_network_watcher.rb b/test/integration/verify/controls/azurerm_network_watcher.rb index 0f6fbf7e8..675e02731 100644 --- a/test/integration/verify/controls/azurerm_network_watcher.rb +++ b/test/integration/verify/controls/azurerm_network_watcher.rb @@ -3,7 +3,7 @@ nw_id = input('network_watcher_id', value: []).first control 'azurerm_network_watcher' do - only_if { ENV['NETWORK_WATCHER'] } + only_if { !nw.nil? } describe azurerm_network_watcher(resource_group: resource_group, name: nw) do it { should exist } diff --git a/test/integration/verify/controls/azurerm_network_watchers.rb b/test/integration/verify/controls/azurerm_network_watchers.rb index 7eebdef84..c64be9308 100644 --- a/test/integration/verify/controls/azurerm_network_watchers.rb +++ b/test/integration/verify/controls/azurerm_network_watchers.rb @@ -1,10 +1,21 @@ resource_group = input('resource_group', value: nil) +nw = input('network_watcher_name', value: []).first control 'azurerm_network_watchers' do - only_if { ENV['NETWORK_WATCHER'] } + only_if { !nw.nil? } describe azurerm_network_watchers(resource_group: resource_group) do it { should exist } its('names') { should be_an(Array) } end end + +control 'azure_network_watchers' do + only_if { !nw.nil? } + + azurerm_network_watchers.ids.each do |id| + describe azure_network_watcher(resource_id: id) do + it { should exist } + end + end +end diff --git a/test/unit/resources/azure_network_watchers_test.rb b/test/unit/resources/azure_network_watchers_test.rb new file mode 100644 index 000000000..eb3e25e3f --- /dev/null +++ b/test/unit/resources/azure_network_watchers_test.rb @@ -0,0 +1,25 @@ +require_relative 'helper' +require 'azure_network_watchers' + +class AzureNetworkWatchersConstructorTest < Minitest::Test + # resource_type should not be allowed. + def test_resource_type_not_ok + assert_raises(ArgumentError) { AzureNetworkWatchers.new(resource_provider: 'some_type') } + end + + def tag_value_not_ok + assert_raises(ArgumentError) { AzureNetworkWatchers.new(tag_value: 'some_tag_value') } + end + + def tag_name_not_ok + assert_raises(ArgumentError) { AzureNetworkWatchers.new(tag_name: 'some_tag_name') } + end + + def test_resource_id_not_ok + assert_raises(ArgumentError) { AzureNetworkWatchers.new(resource_id: 'some_id') } + end + + def test_name_not_ok + assert_raises(ArgumentError) { AzureNetworkWatchers.new(name: 'some_name') } + end +end diff --git a/test/unit/resources/azure_networke_watcher_test.rb b/test/unit/resources/azure_networke_watcher_test.rb new file mode 100644 index 000000000..60cb0fa59 --- /dev/null +++ b/test/unit/resources/azure_networke_watcher_test.rb @@ -0,0 +1,17 @@ +require_relative 'helper' +require 'azure_network_watcher' + +class AzureNetworkWatcherConstructorTest < Minitest::Test + def test_empty_param_not_ok + assert_raises(ArgumentError) { AzureNetworkWatcher.new } + end + + # resource_provider should not be allowed. + def test_resource_provider_not_ok + assert_raises(ArgumentError) { AzureNetworkWatcher.new(resource_provider: 'some_type') } + end + + def test_resource_group + assert_raises(ArgumentError) { AzureNetworkWatcher.new(name: 'my-name') } + end +end