From c0ef3c925ab9027d49f791fe079693f99d2faecf Mon Sep 17 00:00:00 2001 From: Stuart Paterson Date: Thu, 7 Jun 2018 14:58:02 +0100 Subject: [PATCH 1/6] Created the following resources: google_compute_zone; google_compute_zones; google_compute_firewalls as well as example controls and documentation. Also added helpers for retrieving compute instance labels. Signed-off-by: Stuart Paterson --- .../resources/google_compute_firewalls.md.erb | 80 +++++++++++++++++++ docs/resources/google_compute_instance.md.erb | 8 +- docs/resources/google_compute_zone.md.erb | 56 +++++++++++++ docs/resources/google_compute_zones.md.erb | 75 +++++++++++++++++ libraries/google_compute_firewall.rb | 6 +- libraries/google_compute_firewalls.rb | 49 ++++++++++++ libraries/google_compute_instance.rb | 12 +++ libraries/google_compute_zone.rb | 41 ++++++++++ libraries/google_compute_zones.rb | 49 ++++++++++++ .../verify/controls/generic_external_vm.rb | 2 +- .../controls/generic_external_vm_data_disk.rb | 1 + .../controls/google_compute_firewalls.rb | 20 +++++ .../controls/google_compute_firewalls_loop.rb | 17 ++++ .../verify/controls/google_compute_zones.rb | 18 +++++ .../controls/google_compute_zones_loop.rb | 17 ++++ 15 files changed, 448 insertions(+), 3 deletions(-) create mode 100644 docs/resources/google_compute_firewalls.md.erb create mode 100644 docs/resources/google_compute_zone.md.erb create mode 100644 docs/resources/google_compute_zones.md.erb create mode 100644 libraries/google_compute_firewalls.rb create mode 100644 libraries/google_compute_zone.rb create mode 100644 libraries/google_compute_zones.rb create mode 100644 test/integration/verify/controls/google_compute_firewalls.rb create mode 100644 test/integration/verify/controls/google_compute_firewalls_loop.rb create mode 100644 test/integration/verify/controls/google_compute_zones.rb create mode 100644 test/integration/verify/controls/google_compute_zones_loop.rb diff --git a/docs/resources/google_compute_firewalls.md.erb b/docs/resources/google_compute_firewalls.md.erb new file mode 100644 index 000000000..735e3fc0a --- /dev/null +++ b/docs/resources/google_compute_firewalls.md.erb @@ -0,0 +1,80 @@ +--- +title: About the google_compute_firewalls Resource +platform: gcp +--- + +# google\_compute\_firewalls + +Use the `google_compute_firewalls` InSpec audit resource to test properties of all GCP compute firewalls for a project. + +
+ +## Syntax + +A `google_compute_firewalls` resource block collects GCP firewalls by project then tests that group. + + describe google_compute_firewalls(project: 'chef-inspec-gcp') do + it { should exist } + end + +Use this InSpec resource to enumerate IDs then test in-depth using `google_compute_firewall`. + + google_compute_firewalls(project: 'chef-inspec-gcp').firewall_names.each do |firewall_name| + describe google_compute_firewall(project: 'chef-inspec-gcp', name: firewall_name) do + it { should exist } + its('kind') { should eq "compute#firewall" } + end + end + +
+ +## Examples + +The following examples show how to use this InSpec audit resource. + +### Test that there are no more than a specified number of firewalls available for the project + + describe google_compute_firewalls(project: 'chef-inspec-gcp') do + its('entries.count') { should be <= 100} + end + +### Test the exact number of firewalls in the project + + describe google_compute_firewalls(project: 'chef-inspec-gcp') do + its('firewall_ids.count') { should cmp 8 } + end + +### Test that an expected firewall is available for the project + + describe google_compute_firewalls(project: 'chef-inspec-gcp') do + its('firewall_names') { should include "my-app-firewall-rule" } + end + +### Test that a particular named rule does not exist + + describe google_compute_firewalls(project: 'chef-inspec-gcp') do + its('firewall_names') { should_not include "default-allow-ssh" } + end + +### Test there are no firewalls for the "INGRESS" direction + + describe google_compute_firewalls(project: 'chef-inspec-gcp') do + its('firewall_directions') { should_not include "INGRESS" } + end + +
+ +## Filter Criteria + +This resource currently does not support any filter criteria; it will always fetch all available firewalls. + +## Properties + +* `firewall_id`, `firewall_name`, `firewall_directions` + +
+ + +## GCP Permissions + +Ensure the [Compute Engine API](https://console.cloud.google.com/apis/library/compute.googleapis.com/) is enabled for the project where the resource is located. \ No newline at end of file diff --git a/docs/resources/google_compute_instance.md.erb b/docs/resources/google_compute_instance.md.erb index 479847bfa..457bdf3d4 100644 --- a/docs/resources/google_compute_instance.md.erb +++ b/docs/resources/google_compute_instance.md.erb @@ -75,11 +75,17 @@ The following examples show how to use this InSpec audit resource. its('first_network_interface_type'){ should eq "one_to_one_nat" } end +### Test that a particular compute instance label key is present + + describe google_compute_instance(project: 'chef-inspec-gcp', zone: 'us-east1-b', name: 'inspec-test-vm') do + its('labels_keys') { should include 'my_favourite_label' } + end +
## Properties -* `cpu_platform`, `creation_timestamp`, `deletion_protection`, `disks`, `id`, `kind`, `label_fingerprint`, `machine_type`, `metadata`, `name`, `network_interfaces`, `scheduling`, `start_restricted`, `status`, `tags`, `zone` +* `cpu_platform`, `creation_timestamp`, `deletion_protection`, `disks`, `id`, `kind`, `label_fingerprint`, `machine_type`, `metadata`, `name`, `network_interfaces`, `scheduling`, `start_restricted`, `status`, `tags`, `zone`, `labels_keys`, `labels_values`
diff --git a/docs/resources/google_compute_zone.md.erb b/docs/resources/google_compute_zone.md.erb new file mode 100644 index 000000000..9f800aaa3 --- /dev/null +++ b/docs/resources/google_compute_zone.md.erb @@ -0,0 +1,56 @@ +--- +title: About the google_compute_zone Resource +platform: gcp +--- + +# google\_compute\_zone + +Use the `google_compute_zone` InSpec audit resource to test properties of a single GCP compute zone. + +
+ +## Syntax + +A `google_compute_zone` resource block declares the tests for a single GCP zone by project and name. + + describe google_compute_zone(project: 'chef-inspec-gcp', zone: 'us-east1-b') do + it { should exist } + its('name') { should match 'us-east1-b' } + end + +
+ +## Examples + +The following examples show how to use this InSpec audit resource. + +### Test that a GCP compute zone does not exist + + describe google_compute_zone(project: 'chef-inspec-gcp', zone: 'us-east1-b') do + it { should_not exist } + end + +### Test that a GCP compute zone is in the expected state + + describe google_compute_zone(project: 'chef-inspec-gcp', zone: 'us-east1-b') do + its('status') { should eq 'UP' } + end + +### Test that a GCP compute zone has an expected CPU platform + + describe google_compute_zone(project: 'chef-inspec-gcp', zone: 'us-east1-b') do + its('available_cpu_platforms') { should include "Intel Skylake" } + end + +
+ +## Properties + +* `available_cpu_platforms`, `creation_timestamp`, `description`, `id`, `kind`, `name`, `region`, `status`, `region_name` + +
+ + +## GCP Permissions + +Ensure the [Compute Engine API](https://console.cloud.google.com/apis/library/compute.googleapis.com/) is enabled for the project where the resource is located. \ No newline at end of file diff --git a/docs/resources/google_compute_zones.md.erb b/docs/resources/google_compute_zones.md.erb new file mode 100644 index 000000000..cb2b9403d --- /dev/null +++ b/docs/resources/google_compute_zones.md.erb @@ -0,0 +1,75 @@ +--- +title: About the google_compute_zones Resource +platform: gcp +--- + +# google\_compute\_zones + +Use the `google_compute_zones` InSpec audit resource to test properties of all GCP compute zones for a project in a particular zone. + +
+ +## Syntax + +A `google_compute_zones` resource block collects GCP zones by project then tests that group. + + describe google_compute_zones(project: 'chef-inspec-gcp') do + it { should exist } + end + +Use this InSpec resource to enumerate IDs then test in-depth using `google_compute_zone`. + + google_compute_zones(project: 'chef-inspec-gcp').zone_names.each do |zone_name| + describe google_compute_zone(project: 'chef-inspec-gcp', zone: zone_name) do + it { should exist } + its('kind') { should eq "compute#zone" } + its('status') { should eq 'UP' } + end + end + +
+ +## Examples + +The following examples show how to use this InSpec audit resource. + +### Test that there are no more than a specified number of zones available for the project + + describe google_compute_zones(project: 'chef-inspec-gcp') do + its('entries.count') { should be <= 100} + end + +### Test the exact number of zones in the project + + describe google_compute_zones(project: 'chef-inspec-gcp') do + its('zone_ids.count') { should cmp 9 } + end + +### Test that an expected zone is available for the project + + describe google_compute_zones(project: 'chef-inspec-gcp') do + its('zone_names') { should include "us-east1-b" } + end + +### Test whether any zones are in status "DOWN" + + describe google_compute_zones(project: 'chef-inspec-gcp') do + its('zone_statuses') { should_not include "DOWN" } + end + +
+ +## Filter Criteria + +This resource currently does not support any filter criteria; it will always fetch all available zones. + +## Properties + +* `zone_id`, `zone_name`, `zone_statuses` + +
+ + +## GCP Permissions + +Ensure the [Compute Engine API](https://console.cloud.google.com/apis/library/compute.googleapis.com/) is enabled for the project where the resource is located. \ No newline at end of file diff --git a/libraries/google_compute_firewall.rb b/libraries/google_compute_firewall.rb index 8de5ee601..01e23b92c 100644 --- a/libraries/google_compute_firewall.rb +++ b/libraries/google_compute_firewall.rb @@ -8,7 +8,7 @@ class GoogleComputeFirewall < GcpResourceBase desc 'Verifies settings for a compute firewall rule' example " - describe google_compute_firewall(project: 'chef-inspec-gcp', location: 'us-west2', name: 'gcp-inspec-test') do + describe google_compute_firewall(project: 'chef-inspec-gcp', name: 'gcp-inspec-test') do it { should exist } its('name') { should eq 'inspec-test' } its('status') { should eq 'in_use' } @@ -51,6 +51,10 @@ def ports_protocol_allowed(port_list, protocol = 'tcp', index = 0) end end + def exists? + !@firewall.nil? + end + def to_s "Firewall Rule #{@display_name}" end diff --git a/libraries/google_compute_firewalls.rb b/libraries/google_compute_firewalls.rb new file mode 100644 index 000000000..ada881e39 --- /dev/null +++ b/libraries/google_compute_firewalls.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'gcp_backend' + +module Inspec::Resources + class GoogleComputeFirewalls < GcpResourceBase + name 'google_compute_firewalls' + desc 'Verifies settings for GCP compute firewalls in bulk' + + example " + describe google_compute_firewalls(project: 'chef-inspec-gcp') do + it { should exist } + ... + end + " + + def initialize(opts = {}) + # Call the parent class constructor + super(opts) + @display_name = opts[:name] + @project = opts[:project] + end + + # FilterTable setup + filter_table_config = FilterTable.create + filter_table_config.add_accessor(:where) + filter_table_config.add_accessor(:entries) + filter_table_config.add(:exist?) { |filter_table| !filter_table.entries.empty? } + filter_table_config.add(:count) { |filter_table| filter_table.entries.count } + filter_table_config.add(:firewall_ids, field: :firewall_id) + filter_table_config.add(:firewall_names, field: :firewall_name) + filter_table_config.add(:firewall_directions, field: :firewall_direction) + filter_table_config.add(:colors, field: :color, type: :simple) + filter_table_config.connect(self, :fetch_data) + + def fetch_data + firewall_rows = [] + catch_gcp_errors do + @firewalls = @gcp.gcp_compute_client.list_firewalls(@project) + end + @firewalls.items.map do |firewall| + firewall_rows+=[{ firewall_id: firewall.id, + firewall_name: firewall.name, + firewall_direction: firewall.direction }] + end + @table = firewall_rows + end + end +end diff --git a/libraries/google_compute_instance.rb b/libraries/google_compute_instance.rb index e1726577f..9e2351a4c 100644 --- a/libraries/google_compute_instance.rb +++ b/libraries/google_compute_instance.rb @@ -98,6 +98,18 @@ def machine_size machine_type.split('/').last end + # helper for returning label keys to perform checks + def labels_keys + return [] if !defined?(labels) + labels.item.keys + end + + # helper for returning label values to perform checks + def labels_values + return [] if !defined?(labels) + labels.item.values + end + def exists? !@instance.nil? end diff --git a/libraries/google_compute_zone.rb b/libraries/google_compute_zone.rb new file mode 100644 index 000000000..7b8c95c89 --- /dev/null +++ b/libraries/google_compute_zone.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'gcp_backend' + +module Inspec::Resources + class GoogleComputeZone < GcpResourceBase + name 'google_compute_zone' + desc 'Verifies settings for a zone' + + example " + describe google_compute_zone(project: 'chef-inspec-gcp', zone: 'us-east1-b') do + it { should exist } + its('name') { should match 'us-east1-b' } + end + " + + def initialize(opts = {}) + # Call the parent class constructor + super(opts) + @display_name = opts[:name] + catch_gcp_errors do + @zone = @gcp.gcp_compute_client.get_zone(opts[:project], opts[:name]) + create_resource_methods(@zone) + end + end + + # helper method for retrieving a region name + def region_name + return false if !defined?(region) + region.split('/').last + end + + def exists? + !@zone.nil? + end + + def to_s + "Zone #{@display_name}" + end + end +end diff --git a/libraries/google_compute_zones.rb b/libraries/google_compute_zones.rb new file mode 100644 index 000000000..0c8d1d974 --- /dev/null +++ b/libraries/google_compute_zones.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'gcp_backend' + +module Inspec::Resources + class GoogleComputeZones < GcpResourceBase + name 'google_compute_zones' + desc 'Verifies settings for GCP compute zones in bulk' + + example " + describe google_compute_zones(project: 'chef-inspec-gcp') do + it { should exist } + ... + end + " + + def initialize(opts = {}) + # Call the parent class constructor + super(opts) + @display_name = opts[:name] + @project = opts[:project] + end + + # FilterTable setup + filter_table_config = FilterTable.create + filter_table_config.add_accessor(:where) + filter_table_config.add_accessor(:entries) + filter_table_config.add(:exist?) { |filter_table| !filter_table.entries.empty? } + filter_table_config.add(:count) { |filter_table| filter_table.entries.count } + filter_table_config.add(:zone_ids, field: :zone_id) + filter_table_config.add(:zone_names, field: :zone_name) + filter_table_config.add(:zone_statuses, field: :zone_status) + filter_table_config.add(:colors, field: :color, type: :simple) + filter_table_config.connect(self, :fetch_data) + + def fetch_data + zone_rows = [] + catch_gcp_errors do + @zones = @gcp.gcp_compute_client.list_zones(@project) + end + @zones.items.map do |zone| + zone_rows+=[{ zone_id: zone.id, + zone_name: zone.name, + zone_status: zone.status }] + end + @table = zone_rows + end + end +end diff --git a/test/integration/verify/controls/generic_external_vm.rb b/test/integration/verify/controls/generic_external_vm.rb index 48a58274f..45bc051e8 100644 --- a/test/integration/verify/controls/generic_external_vm.rb +++ b/test/integration/verify/controls/generic_external_vm.rb @@ -52,6 +52,6 @@ its('first_network_interface_name'){ should eq "external-nat" } its('first_network_interface_type'){ should eq "one_to_one_nat" } + its('labels_keys') { should_not include 'label_does_not_exist' } end - end diff --git a/test/integration/verify/controls/generic_external_vm_data_disk.rb b/test/integration/verify/controls/generic_external_vm_data_disk.rb index cf6cee00b..33033ddbb 100644 --- a/test/integration/verify/controls/generic_external_vm_data_disk.rb +++ b/test/integration/verify/controls/generic_external_vm_data_disk.rb @@ -53,6 +53,7 @@ its('first_network_interface_nat_ip_exists'){ should be true } its('first_network_interface_name'){ should eq "external-nat" } its('first_network_interface_type'){ should eq "one_to_one_nat" } + its('labels_keys') { should_not include 'non_existing_label' } end diff --git a/test/integration/verify/controls/google_compute_firewalls.rb b/test/integration/verify/controls/google_compute_firewalls.rb new file mode 100644 index 000000000..c0d8450fc --- /dev/null +++ b/test/integration/verify/controls/google_compute_firewalls.rb @@ -0,0 +1,20 @@ +title 'Firewalls Properties' + +gcp_project_id = attribute(:gcp_project_id, default: '', description: 'The GCP project identifier.') + +control 'gcp-firewalls-1.0' do + + impact 1.0 + title 'Ensure firewalls have the correct properties in bulk' + + describe google_compute_firewalls(project: gcp_project_id) do + it { should exist } + its('entries.count') { should be <= 100} + # assume this is a development setup for a moment + its('firewall_names') { should include "default-allow-ssh" } + its('firewall_names') { should include "default-allow-rdp" } + its('firewall_names') { should include "default-allow-internal" } + its('firewall_names') { should include "default-allow-icmp" } + end + +end \ No newline at end of file diff --git a/test/integration/verify/controls/google_compute_firewalls_loop.rb b/test/integration/verify/controls/google_compute_firewalls_loop.rb new file mode 100644 index 000000000..f34d0fffc --- /dev/null +++ b/test/integration/verify/controls/google_compute_firewalls_loop.rb @@ -0,0 +1,17 @@ +title 'Loop over all GCP Firewalls' + +gcp_project_id = attribute(:gcp_project_id, default: '', description: 'The GCP project identifier.') + +control 'gcp-firewalls-loop-1.0' do + + impact 1.0 + title 'Ensure firewalls have the correct properties in bulk using google_compute_firewall for detail.' + + google_compute_firewalls(project: gcp_project_id).firewall_names.each do |firewall_name| + describe google_compute_firewall(project: gcp_project_id, name: firewall_name) do + it { should exist } + its('kind') { should eq "compute#firewall" } + its('direction') { should be_in ["INGRESS","EGRESS"] } + end + end +end \ No newline at end of file diff --git a/test/integration/verify/controls/google_compute_zones.rb b/test/integration/verify/controls/google_compute_zones.rb new file mode 100644 index 000000000..93e190016 --- /dev/null +++ b/test/integration/verify/controls/google_compute_zones.rb @@ -0,0 +1,18 @@ +title 'Zones Properties' + +gcp_project_id = attribute(:gcp_project_id, default: '', description: 'The GCP project identifier.') +gcp_zone = attribute(:gcp_zone, default: '', description: 'The GCP zone being used.') + +control 'gcp-zones-1.0' do + + impact 1.0 + title 'Ensure zones have the correct properties in bulk' + + describe google_compute_zones(project: gcp_project_id) do + it { should exist } + its('entries.count') { should be <= 100} # 46 at the time of writing + its('zone_names') { should include gcp_zone } + its('zone_statuses') { should_not include "DOWN" } + end + +end \ No newline at end of file diff --git a/test/integration/verify/controls/google_compute_zones_loop.rb b/test/integration/verify/controls/google_compute_zones_loop.rb new file mode 100644 index 000000000..06f8a346a --- /dev/null +++ b/test/integration/verify/controls/google_compute_zones_loop.rb @@ -0,0 +1,17 @@ +title 'Loop over all GCP Zones' + +gcp_project_id = attribute(:gcp_project_id, default: '', description: 'The GCP project identifier.') + +control 'gcp-zones-loop-1.0' do + + impact 1.0 + title 'Ensure zones have the correct properties in bulk using google_compute_zone for detail.' + + google_compute_zones(project: gcp_project_id).zone_names.each do |zone_name| + describe google_compute_zone(project: gcp_project_id, name: zone_name) do + it { should exist } + its('kind') { should eq "compute#zone" } + its('status') { should eq 'UP' } + end + end +end \ No newline at end of file From 19f23cd3844ed71ffddf5f5dea84c9e95f320fab Mon Sep 17 00:00:00 2001 From: Stuart Paterson Date: Thu, 7 Jun 2018 15:36:22 +0100 Subject: [PATCH 2/6] New example control to check for label keys across VMs in all zones. Extra protection against null result in plural resources. Signed-off-by: Stuart Paterson --- libraries/google_compute_firewalls.rb | 1 + libraries/google_compute_instances.rb | 1 + libraries/google_compute_zones.rb | 1 + .../google_compute_instance_label_loop.rb | 17 +++++++++++++++++ 4 files changed, 20 insertions(+) create mode 100644 test/integration/verify/controls/google_compute_instance_label_loop.rb diff --git a/libraries/google_compute_firewalls.rb b/libraries/google_compute_firewalls.rb index ada881e39..011e0332f 100644 --- a/libraries/google_compute_firewalls.rb +++ b/libraries/google_compute_firewalls.rb @@ -38,6 +38,7 @@ def fetch_data catch_gcp_errors do @firewalls = @gcp.gcp_compute_client.list_firewalls(@project) end + return [] if !@firewalls.items @firewalls.items.map do |firewall| firewall_rows+=[{ firewall_id: firewall.id, firewall_name: firewall.name, diff --git a/libraries/google_compute_instances.rb b/libraries/google_compute_instances.rb index 2b2aa8da1..d1ea4a4a0 100644 --- a/libraries/google_compute_instances.rb +++ b/libraries/google_compute_instances.rb @@ -38,6 +38,7 @@ def fetch_data catch_gcp_errors do @instances = @gcp.gcp_compute_client.list_instances(@project, @zone) end + return [] if !@instances.items @instances.items.map do |instance| instance_rows+=[{ instance_id: instance.id, instance_name: instance.name }] diff --git a/libraries/google_compute_zones.rb b/libraries/google_compute_zones.rb index 0c8d1d974..31e48d1fe 100644 --- a/libraries/google_compute_zones.rb +++ b/libraries/google_compute_zones.rb @@ -38,6 +38,7 @@ def fetch_data catch_gcp_errors do @zones = @gcp.gcp_compute_client.list_zones(@project) end + return [] if !@zones.items @zones.items.map do |zone| zone_rows+=[{ zone_id: zone.id, zone_name: zone.name, diff --git a/test/integration/verify/controls/google_compute_instance_label_loop.rb b/test/integration/verify/controls/google_compute_instance_label_loop.rb new file mode 100644 index 000000000..1ccc914ab --- /dev/null +++ b/test/integration/verify/controls/google_compute_instance_label_loop.rb @@ -0,0 +1,17 @@ +title 'Loop over all GCP Zones to find all Compute Instances with a particular Label' + +gcp_project_id = attribute(:gcp_project_id, default: '', description: 'The GCP project identifier.') + +control 'gcp-zones-compute-label-loop-1.0' do + + impact 1.0 + title 'Ensure labels for compute instances across all zones have or do not have a particular label.' + + google_compute_zones(project: gcp_project_id).zone_names.each do |zone_name| + google_compute_instances(project: gcp_project_id, zone: zone_name).instance_names.each do |instance_name| + describe google_compute_instance(project: gcp_project_id, zone: zone_name, name: instance_name) do + its('labels_keys') { should_not include 'operations_override_do_not_kill' } + end + end + end +end \ No newline at end of file From afb560df4c72dcb32e0c5a85c18763621bee8cef Mon Sep 17 00:00:00 2001 From: Stuart Paterson Date: Tue, 12 Jun 2018 13:54:19 +0100 Subject: [PATCH 3/6] =?UTF-8?q?Removing=20=E2=80=9Cwhere=E2=80=9D,=20?= =?UTF-8?q?=E2=80=9Centries=E2=80=9D,=20=E2=80=9Cexist=3F=E2=80=9D=20and?= =?UTF-8?q?=20=E2=80=9Ccount=E2=80=9D=20properties=20to=20filter=20tables?= =?UTF-8?q?=20as=20they=20are=20now=20automatic,=20also=20removing=20copy?= =?UTF-8?q?=20paste=20error=20=E2=80=9Ccolor=E2=80=9D=20property.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stuart Paterson --- Gemfile | 2 +- libraries/google_compute_firewalls.rb | 5 ----- libraries/google_compute_instances.rb | 5 ----- libraries/google_compute_zones.rb | 5 ----- test/integration/verify/inspec.yml | 2 +- 5 files changed, 2 insertions(+), 17 deletions(-) diff --git a/Gemfile b/Gemfile index b55115e1d..8b50cb85b 100644 --- a/Gemfile +++ b/Gemfile @@ -15,5 +15,5 @@ group :development do end group :inspec do - gem 'inspec', '~> 2.1', '>= 2.1.78' + gem 'inspec', '~> 2.2', '>= 2.2.10' end diff --git a/libraries/google_compute_firewalls.rb b/libraries/google_compute_firewalls.rb index 011e0332f..3b1c3462e 100644 --- a/libraries/google_compute_firewalls.rb +++ b/libraries/google_compute_firewalls.rb @@ -23,14 +23,9 @@ def initialize(opts = {}) # FilterTable setup filter_table_config = FilterTable.create - filter_table_config.add_accessor(:where) - filter_table_config.add_accessor(:entries) - filter_table_config.add(:exist?) { |filter_table| !filter_table.entries.empty? } - filter_table_config.add(:count) { |filter_table| filter_table.entries.count } filter_table_config.add(:firewall_ids, field: :firewall_id) filter_table_config.add(:firewall_names, field: :firewall_name) filter_table_config.add(:firewall_directions, field: :firewall_direction) - filter_table_config.add(:colors, field: :color, type: :simple) filter_table_config.connect(self, :fetch_data) def fetch_data diff --git a/libraries/google_compute_instances.rb b/libraries/google_compute_instances.rb index d1ea4a4a0..cc94d239a 100644 --- a/libraries/google_compute_instances.rb +++ b/libraries/google_compute_instances.rb @@ -24,13 +24,8 @@ def initialize(opts = {}) # FilterTable setup filter_table_config = FilterTable.create - filter_table_config.add_accessor(:where) - filter_table_config.add_accessor(:entries) - filter_table_config.add(:exist?) { |filter_table| !filter_table.entries.empty? } - filter_table_config.add(:count) { |filter_table| filter_table.entries.count } filter_table_config.add(:instance_ids, field: :instance_id) filter_table_config.add(:instance_names, field: :instance_name) - filter_table_config.add(:colors, field: :color, type: :simple) filter_table_config.connect(self, :fetch_data) def fetch_data diff --git a/libraries/google_compute_zones.rb b/libraries/google_compute_zones.rb index 31e48d1fe..c0a688e5d 100644 --- a/libraries/google_compute_zones.rb +++ b/libraries/google_compute_zones.rb @@ -23,14 +23,9 @@ def initialize(opts = {}) # FilterTable setup filter_table_config = FilterTable.create - filter_table_config.add_accessor(:where) - filter_table_config.add_accessor(:entries) - filter_table_config.add(:exist?) { |filter_table| !filter_table.entries.empty? } - filter_table_config.add(:count) { |filter_table| filter_table.entries.count } filter_table_config.add(:zone_ids, field: :zone_id) filter_table_config.add(:zone_names, field: :zone_name) filter_table_config.add(:zone_statuses, field: :zone_status) - filter_table_config.add(:colors, field: :color, type: :simple) filter_table_config.connect(self, :fetch_data) def fetch_data diff --git a/test/integration/verify/inspec.yml b/test/integration/verify/inspec.yml index 21eada108..d4b55b917 100644 --- a/test/integration/verify/inspec.yml +++ b/test/integration/verify/inspec.yml @@ -1,6 +1,6 @@ name: inspec-gcp-integration-tests version: 0.2.0 -inspec_version: '>= 2.1.78' +inspec_version: '>= 2.2.10' depends: - name: inspec-gcp path: ../../../ From b53bf76c2108d15590eb2b01b08f5783d6f0667e Mon Sep 17 00:00:00 2001 From: Stuart Paterson Date: Tue, 12 Jun 2018 15:12:28 +0100 Subject: [PATCH 4/6] * Documentation updates covering entries.count, filter criteria and properties across firewalls, zones and instances. * Updated tests to use count instead of entries.count. * Added an up? method to google_compute_zone and associated example. Also included in test. Signed-off-by: Stuart Paterson --- .../resources/google_compute_firewalls.md.erb | 20 ++++++++----------- .../resources/google_compute_instances.md.erb | 9 +++++---- docs/resources/google_compute_zone.md.erb | 2 ++ docs/resources/google_compute_zones.md.erb | 7 +++++-- libraries/google_compute_zone.rb | 5 +++++ .../controls/google_compute_firewalls.rb | 2 +- .../verify/controls/google_compute_vms.rb | 2 +- .../verify/controls/google_compute_zones.rb | 2 +- .../controls/google_compute_zones_loop.rb | 2 ++ 9 files changed, 30 insertions(+), 21 deletions(-) diff --git a/docs/resources/google_compute_firewalls.md.erb b/docs/resources/google_compute_firewalls.md.erb index 735e3fc0a..4666d3cd5 100644 --- a/docs/resources/google_compute_firewalls.md.erb +++ b/docs/resources/google_compute_firewalls.md.erb @@ -5,7 +5,7 @@ platform: gcp # google\_compute\_firewalls -Use the `google_compute_firewalls` InSpec audit resource to test properties of all GCP compute firewalls for a project. +Use the `google_compute_firewalls` InSpec audit resource to test properties of all, or a filtered group of, GCP compute firewalls for a project.
@@ -35,13 +35,7 @@ The following examples show how to use this InSpec audit resource. ### Test that there are no more than a specified number of firewalls available for the project describe google_compute_firewalls(project: 'chef-inspec-gcp') do - its('entries.count') { should be <= 100} - end - -### Test the exact number of firewalls in the project - - describe google_compute_firewalls(project: 'chef-inspec-gcp') do - its('firewall_ids.count') { should cmp 8 } + its('count') { should be <= 100} end ### Test that an expected firewall is available for the project @@ -58,19 +52,21 @@ The following examples show how to use this InSpec audit resource. ### Test there are no firewalls for the "INGRESS" direction - describe google_compute_firewalls(project: 'chef-inspec-gcp') do - its('firewall_directions') { should_not include "INGRESS" } + describe google_compute_firewalls(project: 'chef-inspec-gcp').where(firewall_direction: 'INGRESS') do + it { should_not exist } end
## Filter Criteria -This resource currently does not support any filter criteria; it will always fetch all available firewalls. +This resource supports the following filter criteria: `firewall_id`; `firewall_name`; and `firewall_direction`. Any of these may be used with `where`, as a block or as a method. ## Properties -* `firewall_id`, `firewall_name`, `firewall_directions` +* `firewall_ids` - an array of google_compute_firewall identifier integers +* `firewall_names` - an array of google_compute_firewall name strings +* `firewall_directions`- an array of google_compute_firewall directions containing strings e.g. "INGRESS" or "EGRESS"
diff --git a/docs/resources/google_compute_instances.md.erb b/docs/resources/google_compute_instances.md.erb index 2837f7edb..7569edbcd 100644 --- a/docs/resources/google_compute_instances.md.erb +++ b/docs/resources/google_compute_instances.md.erb @@ -5,7 +5,7 @@ platform: gcp # google\_compute\_instances -Use the `google_compute_instances` InSpec audit resource to test properties of all GCP compute instances for a project in a particular zone. +Use the `google_compute_instances` InSpec audit resource to test properties of all, or a filtered group of, GCP compute instances for a project in a particular zone.
@@ -38,7 +38,7 @@ The following examples show how to use this InSpec audit resource. ### Test that there are no more than a specified number of instances in the project and zone describe google_compute_instances(project: 'chef-inspec-gcp', zone: 'europe-west2-a') do - its('entries.count') { should be <= 100} + its('count') { should be <= 100} end ### Test the exact number of instances in the project and zone @@ -57,11 +57,12 @@ The following examples show how to use this InSpec audit resource. ## Filter Criteria -This resource currently does not support any filter criteria; it will always fetch all instances in the zone. +This resource supports the following filter criteria: `instance_id` and `instance_name`. Either of these may be used with `where`, as a block or as a method. ## Properties -* `instance_id`, `instance_name` +* `instance_ids` - an array of google_compute_instance identifier integers +* `instance_names` - an array of google_compute_instance name strings
diff --git a/docs/resources/google_compute_zone.md.erb b/docs/resources/google_compute_zone.md.erb index 9f800aaa3..d0a87e22b 100644 --- a/docs/resources/google_compute_zone.md.erb +++ b/docs/resources/google_compute_zone.md.erb @@ -34,6 +34,8 @@ The following examples show how to use this InSpec audit resource. describe google_compute_zone(project: 'chef-inspec-gcp', zone: 'us-east1-b') do its('status') { should eq 'UP' } + # or equivalently + it { should be_up } end ### Test that a GCP compute zone has an expected CPU platform diff --git a/docs/resources/google_compute_zones.md.erb b/docs/resources/google_compute_zones.md.erb index cb2b9403d..940788482 100644 --- a/docs/resources/google_compute_zones.md.erb +++ b/docs/resources/google_compute_zones.md.erb @@ -5,7 +5,7 @@ platform: gcp # google\_compute\_zones -Use the `google_compute_zones` InSpec audit resource to test properties of all GCP compute zones for a project in a particular zone. +Use the `google_compute_zones` InSpec audit resource to test properties of all, or a filtered group of, GCP compute zones for a project in a particular zone.
@@ -36,7 +36,7 @@ The following examples show how to use this InSpec audit resource. ### Test that there are no more than a specified number of zones available for the project describe google_compute_zones(project: 'chef-inspec-gcp') do - its('entries.count') { should be <= 100} + its('count') { should be <= 100} end ### Test the exact number of zones in the project @@ -66,6 +66,9 @@ This resource currently does not support any filter criteria; it will always fet ## Properties * `zone_id`, `zone_name`, `zone_statuses` +* `firewall_ids` - an array of google_compute_firewall identifier integers +* `firewall_name` - an array of google_compute_firewall name strings +* `firewall_directions`- an array of google_compute_firewall directions containing strings e.g. "INGRESS" or "EGRESS"
diff --git a/libraries/google_compute_zone.rb b/libraries/google_compute_zone.rb index 7b8c95c89..678c10278 100644 --- a/libraries/google_compute_zone.rb +++ b/libraries/google_compute_zone.rb @@ -34,6 +34,11 @@ def exists? !@zone.nil? end + def up? + return false if !defined?(status) + status == 'UP' + end + def to_s "Zone #{@display_name}" end diff --git a/test/integration/verify/controls/google_compute_firewalls.rb b/test/integration/verify/controls/google_compute_firewalls.rb index c0d8450fc..318cb29ef 100644 --- a/test/integration/verify/controls/google_compute_firewalls.rb +++ b/test/integration/verify/controls/google_compute_firewalls.rb @@ -9,7 +9,7 @@ describe google_compute_firewalls(project: gcp_project_id) do it { should exist } - its('entries.count') { should be <= 100} + its('count') { should be <= 100} # assume this is a development setup for a moment its('firewall_names') { should include "default-allow-ssh" } its('firewall_names') { should include "default-allow-rdp" } diff --git a/test/integration/verify/controls/google_compute_vms.rb b/test/integration/verify/controls/google_compute_vms.rb index 467a74d0c..a90868360 100644 --- a/test/integration/verify/controls/google_compute_vms.rb +++ b/test/integration/verify/controls/google_compute_vms.rb @@ -11,7 +11,7 @@ describe google_compute_instances(project: gcp_project_id, zone: gcp_zone) do it { should exist } - its('entries.count') { should be <= 100} + its('count') { should be <= 100} its('instance_ids.count') { should cmp 9 } its('instance_names') { should include gcp_ext_vm_data_disk_name } end diff --git a/test/integration/verify/controls/google_compute_zones.rb b/test/integration/verify/controls/google_compute_zones.rb index 93e190016..5c50320d9 100644 --- a/test/integration/verify/controls/google_compute_zones.rb +++ b/test/integration/verify/controls/google_compute_zones.rb @@ -10,7 +10,7 @@ describe google_compute_zones(project: gcp_project_id) do it { should exist } - its('entries.count') { should be <= 100} # 46 at the time of writing + its('count') { should be <= 100} # 46 at the time of writing its('zone_names') { should include gcp_zone } its('zone_statuses') { should_not include "DOWN" } end diff --git a/test/integration/verify/controls/google_compute_zones_loop.rb b/test/integration/verify/controls/google_compute_zones_loop.rb index 06f8a346a..0731b29f7 100644 --- a/test/integration/verify/controls/google_compute_zones_loop.rb +++ b/test/integration/verify/controls/google_compute_zones_loop.rb @@ -12,6 +12,8 @@ it { should exist } its('kind') { should eq "compute#zone" } its('status') { should eq 'UP' } + # or equivalently + it { should be_up } end end end \ No newline at end of file From 07013b1825c020a7d64523aa339eca1a7868f072 Mon Sep 17 00:00:00 2001 From: Stuart Paterson Date: Tue, 12 Jun 2018 16:31:16 +0100 Subject: [PATCH 5/6] Add pagination for retrieving firewalls and instances plural resources. Also, flip logic to test for existence of a zone in documented example. Signed-off-by: Stuart Paterson --- docs/resources/google_compute_zone.md.erb | 5 ++--- libraries/google_compute_firewalls.rb | 21 +++++++++++++-------- libraries/google_compute_instances.rb | 19 ++++++++++++------- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/docs/resources/google_compute_zone.md.erb b/docs/resources/google_compute_zone.md.erb index d0a87e22b..6f826445c 100644 --- a/docs/resources/google_compute_zone.md.erb +++ b/docs/resources/google_compute_zone.md.erb @@ -14,7 +14,6 @@ Use the `google_compute_zone` InSpec audit resource to test properties of a sing A `google_compute_zone` resource block declares the tests for a single GCP zone by project and name. describe google_compute_zone(project: 'chef-inspec-gcp', zone: 'us-east1-b') do - it { should exist } its('name') { should match 'us-east1-b' } end @@ -24,10 +23,10 @@ A `google_compute_zone` resource block declares the tests for a single GCP zone The following examples show how to use this InSpec audit resource. -### Test that a GCP compute zone does not exist +### Test that a GCP compute zone exists describe google_compute_zone(project: 'chef-inspec-gcp', zone: 'us-east1-b') do - it { should_not exist } + it { should exist } end ### Test that a GCP compute zone is in the expected state diff --git a/libraries/google_compute_firewalls.rb b/libraries/google_compute_firewalls.rb index 3b1c3462e..e2333dea8 100644 --- a/libraries/google_compute_firewalls.rb +++ b/libraries/google_compute_firewalls.rb @@ -30,14 +30,19 @@ def initialize(opts = {}) def fetch_data firewall_rows = [] - catch_gcp_errors do - @firewalls = @gcp.gcp_compute_client.list_firewalls(@project) - end - return [] if !@firewalls.items - @firewalls.items.map do |firewall| - firewall_rows+=[{ firewall_id: firewall.id, - firewall_name: firewall.name, - firewall_direction: firewall.direction }] + next_page = nil + loop do + catch_gcp_errors do + @firewalls = @gcp.gcp_compute_client.list_firewalls(@project, page_token: next_page) + end + return [] if !@firewalls.items + @firewalls.items.map do |firewall| + firewall_rows+=[{ firewall_id: firewall.id, + firewall_name: firewall.name, + firewall_direction: firewall.direction }] + end + next_page = @firewalls.next_page_token + break unless next_page end @table = firewall_rows end diff --git a/libraries/google_compute_instances.rb b/libraries/google_compute_instances.rb index cc94d239a..875255b85 100644 --- a/libraries/google_compute_instances.rb +++ b/libraries/google_compute_instances.rb @@ -30,13 +30,18 @@ def initialize(opts = {}) def fetch_data instance_rows = [] - catch_gcp_errors do - @instances = @gcp.gcp_compute_client.list_instances(@project, @zone) - end - return [] if !@instances.items - @instances.items.map do |instance| - instance_rows+=[{ instance_id: instance.id, - instance_name: instance.name }] + next_page = nil + loop do + catch_gcp_errors do + @instances = @gcp.gcp_compute_client.list_instances(@project, @zone, page_token: next_page) + end + return [] if !@instances.items + @instances.items.map do |instance| + instance_rows+=[{ instance_id: instance.id, + instance_name: instance.name }] + end + next_page = @instances.next_page_token + break unless next_page end @table = instance_rows end From 30e03bf9f5e739ed3a2fb3acb12ce4e71168a34e Mon Sep 17 00:00:00 2001 From: Stuart Paterson Date: Tue, 12 Jun 2018 19:29:45 +0100 Subject: [PATCH 6/6] =?UTF-8?q?Ensure=20=E2=80=9Cids=E2=80=9D=20parameters?= =?UTF-8?q?=20for=20plural=20resource=20are=20tested.=20Add=20configuratio?= =?UTF-8?q?n=20flag=20for=20optionally=20executing=20tests=20that=20rely?= =?UTF-8?q?=20on=20gcloud/grep.=20Correct=20zones=20filter=20criteria=20an?= =?UTF-8?q?d=20properties=20documentation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stuart Paterson --- docs/resources/google_compute_zones.md.erb | 9 ++++----- test/integration/configuration/gcp_inspec_config.rb | 7 ++++++- .../verify/controls/google_compute_firewalls.rb | 5 +++++ test/integration/verify/controls/google_compute_vms.rb | 5 +++++ test/integration/verify/controls/google_compute_zones.rb | 2 ++ 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/docs/resources/google_compute_zones.md.erb b/docs/resources/google_compute_zones.md.erb index 940788482..a5c24b476 100644 --- a/docs/resources/google_compute_zones.md.erb +++ b/docs/resources/google_compute_zones.md.erb @@ -61,14 +61,13 @@ The following examples show how to use this InSpec audit resource. ## Filter Criteria -This resource currently does not support any filter criteria; it will always fetch all available zones. +This resource supports the following filter criteria: `zone_id`; `zone_name` and `zone_status`. Anyy of these may be used with `where`, as a block or as a method. ## Properties -* `zone_id`, `zone_name`, `zone_statuses` -* `firewall_ids` - an array of google_compute_firewall identifier integers -* `firewall_name` - an array of google_compute_firewall name strings -* `firewall_directions`- an array of google_compute_firewall directions containing strings e.g. "INGRESS" or "EGRESS" +* `zone_ids` - an array of google_compute_zone identifier integers +* `zone_names` - an array of google_compute_zone name strings +* `zone_statuses`- an array of google_compute_zone statuses
diff --git a/test/integration/configuration/gcp_inspec_config.rb b/test/integration/configuration/gcp_inspec_config.rb index 9b943cea8..f8c391b56 100644 --- a/test/integration/configuration/gcp_inspec_config.rb +++ b/test/integration/configuration/gcp_inspec_config.rb @@ -18,6 +18,7 @@ module GCPInspecConfig # Determine the storage account name and the admin password :gcp_location => "europe-west2", :gcp_zone => "europe-west2-a", + :gcp_zone_id => "2290", :gcp_int_vm_name => "gcp-inspec-int-linux-vm", :gcp_int_vm_size => "f1-micro", :gcp_int_vm_image => "ubuntu-os-cloud/ubuntu-1604-lts", @@ -64,7 +65,11 @@ module GCPInspecConfig # be disabled meaning a user needs no special GCP privileges to run the integration test pack. # # Note, would prefer to use boolean true or false here but will revisit for a future version of tf, see here for more detail: https://www.terraform.io/docs/configuration/variables.html - :gcp_enable_privileged_resources => 0 + :gcp_enable_privileged_resources => 0, + # Some controls make use of the gcloud command and grep to discover live data to then test against. + # Only test execution is affected by this flag, resource creation via terraform is unaffected. + # Default behaviour is for this to be disabled, enable by changing the below flag. + :gcp_enable_gcloud_calls => 0 } def self.config diff --git a/test/integration/verify/controls/google_compute_firewalls.rb b/test/integration/verify/controls/google_compute_firewalls.rb index 318cb29ef..134e8efcc 100644 --- a/test/integration/verify/controls/google_compute_firewalls.rb +++ b/test/integration/verify/controls/google_compute_firewalls.rb @@ -1,9 +1,11 @@ title 'Firewalls Properties' gcp_project_id = attribute(:gcp_project_id, default: '', description: 'The GCP project identifier.') +gcp_enable_gcloud_calls = attribute(:gcp_enable_gcloud_calls,default:0,description:'Flag to enable the use of gcloud command line to pull in live data to test against.') control 'gcp-firewalls-1.0' do + only_if { gcp_enable_gcloud_calls.to_i == 1 } impact 1.0 title 'Ensure firewalls have the correct properties in bulk' @@ -15,6 +17,9 @@ its('firewall_names') { should include "default-allow-rdp" } its('firewall_names') { should include "default-allow-internal" } its('firewall_names') { should include "default-allow-icmp" } + # Only make the call if the configuration flag is specified and the test will run + gcp_firewall_id = `gcloud compute firewall-rules list --filter="name=default-allow-ssh" --format=json | grep id | grep -o '[0-9]\\+'`.chomp.to_i + its('firewall_ids') { should include gcp_firewall_id } end end \ No newline at end of file diff --git a/test/integration/verify/controls/google_compute_vms.rb b/test/integration/verify/controls/google_compute_vms.rb index a90868360..d50342792 100644 --- a/test/integration/verify/controls/google_compute_vms.rb +++ b/test/integration/verify/controls/google_compute_vms.rb @@ -3,9 +3,11 @@ gcp_project_id = attribute(:gcp_project_id, default: '', description: 'The GCP project identifier.') gcp_zone = attribute(:gcp_zone, default: '', description: 'The GCP zone being used.') gcp_ext_vm_data_disk_name = attribute(:gcp_ext_vm_data_disk_name, default: '', description: 'A valid GCP VM name to check for.') +gcp_enable_gcloud_calls = attribute(:gcp_enable_gcloud_calls,default:0,description:'Flag to enable the use of gcloud command line to pull in live data to test against.') control 'gcp-vms-1.0' do + only_if { gcp_enable_gcloud_calls.to_i == 1 } impact 1.0 title 'Ensure VMs have the correct properties in bulk' @@ -14,6 +16,9 @@ its('count') { should be <= 100} its('instance_ids.count') { should cmp 9 } its('instance_names') { should include gcp_ext_vm_data_disk_name } + # Only make the call if the configuration flag is specified and the test will run + gcp_instance_id = `gcloud compute instances list --filter="name=gcp-inspec-ext-linux-vm" --format=json | grep id | grep -o '[0-9]\\+'`.chomp.to_i + its('instance_ids') { should include gcp_instance_id } end end \ No newline at end of file diff --git a/test/integration/verify/controls/google_compute_zones.rb b/test/integration/verify/controls/google_compute_zones.rb index 5c50320d9..e1f357054 100644 --- a/test/integration/verify/controls/google_compute_zones.rb +++ b/test/integration/verify/controls/google_compute_zones.rb @@ -2,6 +2,7 @@ gcp_project_id = attribute(:gcp_project_id, default: '', description: 'The GCP project identifier.') gcp_zone = attribute(:gcp_zone, default: '', description: 'The GCP zone being used.') +gcp_zone_id = attribute(:gcp_zone_id, default: '', description: 'A sample zone identifier to test for.') control 'gcp-zones-1.0' do @@ -13,6 +14,7 @@ its('count') { should be <= 100} # 46 at the time of writing its('zone_names') { should include gcp_zone } its('zone_statuses') { should_not include "DOWN" } + its('zone_ids') { should include gcp_zone_id.to_i } end end \ No newline at end of file