diff --git a/README.md b/README.md index 2509f05b2..81d59d718 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,8 @@ The following is a list of static resources. - [azure_webapps](docs/resources/azure_webapps.md) - [azure_bastion_hosts_resource](docs/resources/azure_bastion_hosts_resource.md) - [azure_bastion_hosts_resources](docs/resources/azure_bastion_hosts_resources.md) +- [azure_express_route_circuit_connections_resource](docs/resources/azure_express_route_circuit_connections_resource.md) +- [azure_express_route_circuit_connections_resources](docs/resources/azure_express_route_circuit_connections_resources.md) For more details and different use cases, please refer to the specific resource pages. diff --git a/docs/resources/azure_express_route_circuit_connections_resource.md b/docs/resources/azure_express_route_circuit_connections_resource.md new file mode 100644 index 000000000..f21b6fdc4 --- /dev/null +++ b/docs/resources/azure_express_route_circuit_connections_resource.md @@ -0,0 +1,114 @@ +--- +title: About the azure_express_route_circuit_connections_resource Resource +platform: azure +--- + +# azure_express_route_circuit_connections_resource + +Use the `azure_express_route_circuit_connections_resource` InSpec audit resource to test properties related to a express circuit resource. + +## 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). + + + +Also, refer to [Azure documentation](https://docs.microsoft.com/en-us/rest/api/expressroute/express-route-circuit-connections/get) for properties available. +Any attribute in the response may be accessed with the key names separated by dots (`.`). +## Syntax + +```ruby +describe azure_express_route_circuit_connections_resource(resource_group: 'example', circuit_name: 'cn', peering_name: 'pn', connection_name: 'cn') do + it { should exist } +end +``` +## Parameters + +| Name | Description | +|--------------------------------|----------------------------------------------------------------------------------| +| resource_group | Azure resource group that the targeted resource resides in. `MyResourceGroup` | +| circuit_name | Name of the Express route circuit to test. `circuit_name` | +| peering_name | Name of the peering to test. `peering_name` | +| connection_name | The name of the express route circuit connection. `connection_name` | + +All of the parameter sets should be provided for a valid query: +- `resource_group`, `circuit_name`, `peering_name`, `connection_name` + +## Properties + +| Name | Description | +|--------------------------------|----------------------------------------------------------------------------------| +| resource_group | The name of the resource group in which to create the ExpressRoute circuit. Changing this forces a new resource to be created. `MyResourceGroup` | +| name | The name of the ExpressRoute circuit. Changing this forces a new resource to be created. `Myexpress circuitHostName` | +| type | type of express ExpressRoute circuit | +| provisioning_state | State of express ExpressRoute circuit creation | +| location | Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created. | +| circuit_connection_status | Express Route Circuit connection state. | +| ipv6_circuit_connection_config_status | IPv6 Address PrefixProperties of the express route circuit connection i.e Express Route Circuit connection state. | + + +Also, refer to [Azure documentation](https://docs.microsoft.com/en-us/rest/api/expressroute/express-route-circuits/get) for other properties available. +Any attribute in the response may be accessed with the key names separated by dots (`.`). + + +## Examples + +### Ensure that the express circuit resource has is from same type +```ruby +describe azure_express_route_circuit_connections_resource(resource_group: 'example', circuit_name: 'cn', peering_name: 'pn', connection_name: 'cn') do + its('type') { should eq 'Microsoft.Network/expressRouteCircuits' } +end +``` +### Ensure that the express circuit resource is in successful state +```ruby +describe azure_express_route_circuit_connections_resource(resource_group: 'example', circuit_name: 'cn', peering_name: 'pn', connection_name: 'cn') do + its('provisioning_state') { should include('Succeeded') } +end +``` + +### Ensure that the express circuit resource is from same location +```ruby +describe azure_express_route_circuit_connections_resource(resource_group: 'example', circuit_name: 'cn', peering_name: 'pn', connection_name: 'cn') do + its('location') { should include df_location } +end +``` +## Matchers + +This InSpec audit resource has the following special matchers. For a full list of available matchers, please visit our [Universal Matchers page](/inspec/matchers/). +```ruby +describe azure_express_route_circuit_connections_resource(resource_group: 'example', circuit_name: 'cn', peering_name: 'pn', connection_name: 'cn') do + its('provisioning_state') { should eq 'Succeeded' } + its('ipv6_circuit_connection_config_status') { should eq 'Connected' } + its('circuit_connection_status') { should eq 'Connected' } +end +``` + +### exists +```ruby +# If a express circuit resource is found it will exist +describe azure_express_route_circuit_connections_resource(resource_group: 'example', circuit_name: 'cn', peering_name: 'pn', connection_name: 'cn') do + it { should exist } +end + +# express circuit resources that aren't found will not exist +describe azure_express_route_circuit_connections_resource(resource_group: 'example', circuit_name: 'cn', peering_name: 'pn', connection_name: 'does_not_exist') 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. \ No newline at end of file diff --git a/docs/resources/azure_express_route_circuit_connections_resources.md b/docs/resources/azure_express_route_circuit_connections_resources.md new file mode 100644 index 000000000..1ea65f525 --- /dev/null +++ b/docs/resources/azure_express_route_circuit_connections_resources.md @@ -0,0 +1,110 @@ +--- +title: About the azure_express_route_circuit_connections_resources Resource +platform: azure +--- + +# azure_express_route_circuit_connections_resources + +Use the `azure_express_route_circuit_connections_resources` InSpec audit resource to test properties related to express_route_circuits for a resource group or the entire subscription. + +## 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). + + +Also, refer to [Azure documentation](https://docs.microsoft.com/en-us/rest/api/expressroute/express-route-circuit-connections/list) for properties available. +Any attribute in the response may be accessed with the key names separated by dots (`.`). +## Syntax + +An `azure_express_route_circuit_connections_resources` resource block returns all Azure express route circuits, either within a Resource Group (if provided) +```ruby +describe azure_express_route_circuit_connections_resources(resource_group: 'rg', circuit_name: 'cn', peering_name: 'pn') do + +end +``` +## Parameters + +| Name | Description | +|--------------------------------|----------------------------------------------------------------------------------| +| resource_group | Azure resource group that the targeted resource resides in. `MyResourceGroup` | +| circuit_name | The name of the express route circuit to test. `circuit_name` | +| peering_name | Name of the peering to test. `peering_name` | + + +All of the parameter sets should be provided for a valid query: +- `resource_group`, `circuit_name`, `peering_name` + +## Properties + +|Property | Description | Filter Criteria* | +|---------------|--------------------------------------------------------------------------------------|-----------------| +| names | A list of name the resource group in which to create the ExpressRoute circuit. Changing this forces a new resource to be created. `MyResourceGroup` | `name` | +| ids | A list of id the ExpressRoute circuit. Changing this forces a new resource to be created. `Myexpress circuitHostName | `id` | +| tags | A list of `tag:value` pairs defined on the resources. | `tags` | +| provisioning_states | State of express_route_circuits creation | `provisioning_state` | +| types | Types of all the express_route_circuits | `type` | +| locations | Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created. | `location` | +| circuit_connection_status| Express Route Circuit connection state. | `circuit_connection_status`| +| ipv6_circuit_connection_config_status |IPv6 Address PrefixProperties of the express route circuit connection.| `ipv6_circuit_connection_config_status` | +| properties| The properties of Resource. | `properties` | +* 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 + +### Ensure that the express_route_circuits resource is in successful state +```ruby +describe azure_express_route_circuit_connections_resources(resource_group: 'rg', circuit_name: 'cn', peering_name: 'pn') do + its('provisioning_states') { should include('Succeeded') } +end +``` + +### Ensure that the express_route_circuits resource is from same location +```ruby +describe azure_express_route_circuit_connections_resources(resource_group: 'rg', circuit_name: 'cn', peering_name: 'pn') do + its('location') { should include df_location } +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/). +```ruby +describe azure_express_route_circuit_connections_resources(resource_group: 'rg', circuit_name: 'cn', peering_name: 'pn') do + its('names') { should include circuitName } + its('locations') { should include location } + its('types') { should include 'Microsoft.Network/expressRouteCircuits' } + its('provisioning_states') { should include('Succeeded') } + its('circuit_connection_status') { should include('Connected') } + its('ipv6_circuit_connection_config_status') { should include('Connected') } +end +``` + + +### exists +```ruby +# Should exist if express_route_circuits are in the resource group +describe azure_express_route_circuit_connections_resources(resource_group: 'rg', circuit_name: 'cn', peering_name: 'pn') do + it { should exist } +end +# Should not exist if no express_route_circuits are in the resource group +describe azure_express_route_circuit_connections_resources(resource_group: 'rg', circuit_name: 'cn', peering_name: 'should_not_exist') 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. \ No newline at end of file diff --git a/libraries/azure_express_route_circuit_connections_resource.rb b/libraries/azure_express_route_circuit_connections_resource.rb new file mode 100644 index 000000000..8a2a4c607 --- /dev/null +++ b/libraries/azure_express_route_circuit_connections_resource.rb @@ -0,0 +1,69 @@ +require 'azure_generic_resource' + +class AzureExpressRouteCircuitConnectionsResource < AzureGenericResource + name 'azure_express_route_circuit_connections_resource' + desc 'ExpressRoute circuit connect your on-premises infrastructure to Microsoft through a connectivity provider' + example <<-EXAMPLE + describe azure_express_route_circuit_connections_resource(resource_group: 'example', circuit_name: 'cn', peering_name: 'pn', connection_name: 'cn') do + it { should exist } + 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) + + # Azure REST API endpoint URL format for the resource: + # GET https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/ + # providers/Microsoft.Network/expressRouteCircuits/{circuitName}/peerings/{peeringName}/ + # connections/{connectionName}?api-version=2021-02-01 + # + # The dynamic part that has to be created in this resource: + # Microsoft.Network/expressRouteCircuits/{circuitName}/peerings/{peeringName}/ + # connections/{connectionName}?api-version=2021-02-01 + # + # Parameters acquired from environment variables: + # - {subscriptionId} => Required parameter. It will be acquired by the backend from environment variables. + # + # User supplied parameters: + # - resource_group => Required parameter unless `resource_id` is provided. {resourceGroupName} + # - name => Required parameter unless `resource_id` is provided. ExpressRouteCircuit name. {vmName} + # - resource_id => Optional parameter. If exists, `resource_group` and `name` must not be provided. + # In the following format: + # /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/ + # providers/Microsoft.Network/expressRouteCircuits/{circuitName}/peerings/{peeringName}/ + # connections/{connectionName} + # - api_version => Optional parameter. The latest version will be used unless provided. api-version + # + # **`resource_group` and (resource) `name` or `resource_id` will be validated in the backend appropriately. + # We don't have to do anything here. + # + # Following resource parameters have to be defined here. + # - resource_provider => Microsoft.Network/expressRouteCircuits + # The `specific_resource_constraint` method will validate the user input + # not to accept a different `resource_provider`. + # + opts[:resource_provider] = specific_resource_constraint('Microsoft.Network/expressRouteCircuits', opts) + opts[:required_parameters] = %i(circuit_name peering_name) + opts[:resource_path] = [opts[:circuit_name], 'peerings', opts[:peering_name], 'connections'].join('/') + opts[:resource_identifiers] = %i(connection_name) + # static_resource parameter must be true for setting the resource_provider in the backend. + super(opts, true) + end + + def to_s + super(AzureExpressRouteCircuitConnectionsResource) + end + + def provisioning_state + properties.provisioningState if exists? + end + + def circuit_connection_status + properties.circuitConnectionStatus if exists? + end + + def ipv6_circuit_connection_config_status + properties.ipv6CircuitConnectionConfig.circuitConnectionStatus + end +end diff --git a/libraries/azure_express_route_circuit_connections_resources.rb b/libraries/azure_express_route_circuit_connections_resources.rb new file mode 100644 index 000000000..06e402949 --- /dev/null +++ b/libraries/azure_express_route_circuit_connections_resources.rb @@ -0,0 +1,103 @@ +require 'azure_generic_resources' + +class AzureExpressRouteCircuitConnectionsResources < AzureGenericResources + name 'azure_express_route_circuit_connections_resources' + desc 'ExpressRoute circuits connect your on-premises infrastructure to Microsoft through a connectivity provider' + example <<-EXAMPLE + describe azure_express_route_circuit_connections_resources(resource_group: 'rg', circuit_name: 'cn', peering_name: 'pn') 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) + + # Azure REST API endpoint URL format listing the all resources for a given subscription: + # GET https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/ + # providers/Microsoft.Network/expressRouteCircuits/{circuitName}/peerings/{peeringName}/connections?api-version=2021-02-01 + # + # + # The dynamic part that has to be created for this resource: + # Microsoft.Network/expressRouteCircuits/{circuitName}/peerings/{peeringName}/connections?api-version=2021-02-01 + # + # Parameters acquired from environment variables: + # - {subscriptionId} => Required parameter. It will be acquired by the backend from environment variables. + # + # For parameters applicable to all resources, see project's README. + # + # User supplied parameters: + # - resource_group => Optional parameter. + # - api_version => Optional parameter. The latest version will be used unless provided. + # + # **`resource_group` will be used in the backend appropriately. + # We don't have to do anything here. + # + # Following resource parameters have to be defined/created here. + # resource_provider => Microsoft.Network/expressRouteCircuits + # The `specific_resource_constraint` method will validate the user input + # not to accept a different `resource_provider`. + # + opts[:resource_provider] = specific_resource_constraint('Microsoft.Network/expressRouteCircuits', opts) + opts[:required_parameters] = %i(circuit_name peering_name) + opts[:resource_path] = [opts[:circuit_name], 'peerings', opts[:peering_name], 'connections'].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 + table_schema = [ + { column: :ids, field: :id }, + { column: :names, field: :name }, + { column: :locations, field: :location }, + { column: :types, field: :type }, + { column: :tags, field: :tags }, + { column: :provisioning_states, field: :provisioning_state }, + { column: :circuit_connection_status, field: :circuit_connection_status }, + { column: :ipv6_circuit_connection_config_status, field: :ipv6_circuit_connection_config_status }, + { column: :properties, field: :properties }, + ] + # 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(AzureExpressRouteCircuitConnectionsResources) + end + + private + + # Populate the @table with the resource attributes. + # @table has been declared in the super class as an empty array. + # Each item in the @table + # - should be a Hash object + # - should have the exact key names defined in the @table_schema as `field`. + def populate_table + # If @resources empty than @table should stay as an empty array as declared in superclass. + # This will ensure constructing resource and passing `should_not exist` test. + return [] if @resources.empty? + + @resources.each do |resource| + @table << { + id: resource[:id], + name: resource[:name], + location: resource[:location], + type: resource[:type], + tags: resource[:tags], + provisioning_state: resource[:properties][:provisioningState], + circuit_connection_status: resource[:properties][:circuitConnectionStatus], + ipv6_circuit_connection_config_status: resource[:properties][:ipv6CircuitConnectionConfig][:circuitConnectionStatus], + properties: resource[:properties], + } + end + end +end diff --git a/test/integration/verify/controls/azure_express_route_circuit_connections_resource.rb b/test/integration/verify/controls/azure_express_route_circuit_connections_resource.rb new file mode 100644 index 000000000..5f3e6d392 --- /dev/null +++ b/test/integration/verify/controls/azure_express_route_circuit_connections_resource.rb @@ -0,0 +1,28 @@ +resource_group = input('resource_group', value: nil) +circuit_name = input('circuitName', value: nil) +location = input('circuitLocation', value: nil) +peering_name = input('peeringName', value: nil) +connection_name = input('connectionName', value: nil) + +control 'azure_express_route_circuit_connections_resource' do + describe azure_express_route_circuit_connections_resource(resource_group: resource_group, circuit_name: circuit_name, + peering_name: peering_name, connection_name: connection_name) do + it { should exist } + its('name') { should eq circuit_name } + its('type') { should eq 'Microsoft.Network/expressRouteCircuits' } + its('provisioning_state') { should include('Succeeded') } + its('location') { should include location } + its('ipv6_circuit_connection_config_status') { should eq 'Connected' } + its('circuit_connection_status') { should include 'Connected' } + end + + describe azure_express_route_circuit_connections_resource(resource_group: resource_group, circuit_name: circuit_name, + peering_name: peering_name, connection_name: connection_name) do + it { should_not exist } + end + + describe azure_express_route_circuit_connections_resource(resource_group: resource_group, circuit_name: circuit_name, + peering_name: peering_name, connection_name: 'does_not_exist') do + it { should_not exist } + end +end diff --git a/test/integration/verify/controls/azure_express_route_circuit_connections_resources.rb b/test/integration/verify/controls/azure_express_route_circuit_connections_resources.rb new file mode 100644 index 000000000..77451bbdd --- /dev/null +++ b/test/integration/verify/controls/azure_express_route_circuit_connections_resources.rb @@ -0,0 +1,16 @@ +erc_resource_group = input('resource_group', value: nil) +erc_circuit_name = input('circuitName', value: nil) +erc_location = input('circuitLocation', value: nil) +peering_name = input('peeringName', value: nil) + +control 'azure_express_route_circuit_connections_resources' do + describe azure_express_route_circuit_connections_resources(resource_group: erc_resource_group, circuit_name: erc_circuit_name, peering_name: peering_name) do + it { should exist } + its('names') { should include circuitName } + its('locations') { should include erc_location } + its('types') { should include 'Microsoft.Network/expressRouteCircuits' } + its('provisioning_states') { should include('Succeeded') } + its('circuit_connection_status') { should include('Connected') } + its('ipv6_circuit_connection_config_status') { should include('Connected') } + end +end diff --git a/test/unit/resources/azure_express_route_circuit_connections_resource_test.rb b/test/unit/resources/azure_express_route_circuit_connections_resource_test.rb new file mode 100644 index 000000000..1394858cb --- /dev/null +++ b/test/unit/resources/azure_express_route_circuit_connections_resource_test.rb @@ -0,0 +1,17 @@ +require_relative 'helper' +require 'azure_express_route_circuit' + +class AzureExpressRouteCircuitConstructorTest < Minitest::Test + def test_empty_param_not_ok + assert_raises(ArgumentError) { AzureExpressRouteCircuit.new } + end + + # resource_provider should not be allowed. + def test_resource_provider_not_ok + assert_raises(ArgumentError) { AzureExpressRouteCircuit.new(resource_provider: 'some_type') } + end + + def test_resource_group + assert_raises(ArgumentError) { AzureExpressRouteCircuit.new(name: 'my-name') } + end +end diff --git a/test/unit/resources/azure_express_route_circuit_connections_resources_test.rb b/test/unit/resources/azure_express_route_circuit_connections_resources_test.rb new file mode 100644 index 000000000..461e2f0da --- /dev/null +++ b/test/unit/resources/azure_express_route_circuit_connections_resources_test.rb @@ -0,0 +1,25 @@ +require_relative 'helper' +require 'azure_express_route_circuits' + +class AzureExpressRouteCircuitsConstructorTest < Minitest::Test + # resource_type should not be allowed. + def test_resource_type_not_ok + assert_raises(ArgumentError) { AzureExpressRouteCircuits.new(resource_provider: 'some_type') } + end + + def tag_value_not_ok + assert_raises(ArgumentError) { AzureExpressRouteCircuits.new(tag_value: 'some_tag_value') } + end + + def tag_name_not_ok + assert_raises(ArgumentError) { AzureExpressRouteCircuits.new(tag_name: 'some_tag_name') } + end + + def test_resource_id_not_ok + assert_raises(ArgumentError) { AzureExpressRouteCircuits.new(resource_id: 'some_id') } + end + + def test_name_not_ok + assert_raises(ArgumentError) { AzureExpressRouteCircuits.new(name: 'some_name') } + end +end