InSpec GCP Deep Dive

As recently announced Chef has deepened support for Google Cloud Platform (GCP) by adding InSpec integration. Furthermore, the InSpec GCP resource pack is freely available here – suggestions (and contributions) welcome!

Before looking at InSpec GCP let’s ensure we have the necessary prerequisites in place. As an aside, we will be following similar setup steps to this excellent introductory article. Note that this is a good time to update to the latest InSpec version as the minimal requirement is 2.2.10. The GCP SDK should also be installed and configured. With that done, let’s create an InSpec profile that makes use of inspec-gcp:

$ inspec init profile gcp-inspec-deep-dive
Create new profile at /Users/spaterson/inspec-gcp-deep-dive-profile
 * Create directory libraries
 * Create file README.md
 * Create directory controls
 * Create file controls/example.rb
 * Create file inspec.yml
 * Create file libraries/.gitkeep

In order to save time, an example profile with all the controls in this article is available here. This leverages an attributes.yml file to supply parameters to the InSpec tests. From the root of the profile, these are executed via:

$ inspec exec . -t gcp:// --attrs attributes.yml

Let’s start with a simple first use-case. Work with any Cloud provider for long enough and you might be lucky enough to experience a zone (or region) becoming unavailable. This can sometimes create ancillary errors that are difficult to pinpoint. So how might we go about writing a simple InSpec test to confirm all GCP zones are up? For the purposes of this article, let’s assume we’re already up and running with GCP and have a project called gcp-project to test against. Let’s start simply and see what testing one zone looks like. To facilitate this, we use the google_compute_zone resource. 

control 'gcp-single-zone-1' do
  title 'Check the status of a single zone'
  describe google_compute_zone(project: 'gcp-project',  zone: 'us-east1-b') do
    it { should exist }
    its('status') { should eq 'UP' }
  end
end

Running this with InSpec we see:

This is fine but we had to supply the zone name ‘us-east1-b’. What if new zones are added that our company uses immediately, does the test need to be updated? Ideally we want to insulate ourselves from these kind of details and run across all available GCP zones each time. This is how InSpec helps us achieve that with plural resources (more details here):

control 'gcp-zones-all-2' do
  title 'All zones should be UP'
  google_compute_zones(project: 'gcp-project').zone_names.each do |zone_name| 
    describe google_compute_zone(project: 'gcp-project', name: zone_name) do
     it { should exist }
     its('status') { should eq 'UP' }
    end
  end
end

Let’s go through this in more detail. First, we get all the google_compute_zones for the project, then use the zone_name field to get the google_compute_zones resource. This is a good example of using an InSpec plural resource to gather a collection of singular resources that we then test one by one.

Now let’s see what happens when we run the above control with InSpec:

(output curtailed as there are 46 zones at the time of writing…)

Of course in practice, the list of zones being checked could be limited to those in use.

Now we can look at a more realistic example.  Let’s assume we have a set of firewall rules in use for our GCP project and want to make absolutely sure that the following rules are enforced:

  • SSH on port 22 is forbidden everywhere
  • HTTP on port 80 is forbidden everywhere

Similarly to checking zone status, we can leverage the google_compute_firewalls plural resource to loop over all singular firewall rules in more detail:  

control 'gcp-firewalls-all-3' do
  title 'Ensure no SSH or HTTP allowed in firewall rules'
  google_compute_firewalls(project: 'gcp-project').firewall_names.each do |firewall_name|
    describe google_compute_firewall(project: 'gcp-project', name: firewall_name) do
      it { should exist }
      its('allowed_ssh?')  { should be false }
      its('allowed_http?')  { should be false }
    end
  end
end

Running this with InSpec, we can easily see whether or not (as shown below) we are in a compliant state:

When any organisation starts to use the public cloud, tags (or for GCP, labels) become an essential way to keep track of what’s going on. An efficient tagging strategy is often essential for operational transparency, amongst other things. Let’s consider a hypothetical organisation where operational edge-cases such as having VMs with uptime beyond the normal life-cycle are tracked with labels. Following best practices, let’s assume processes are in place to automatically remove non-compliant compute resources. Since it’s the real world, let’s also imagine a label such as “operations_override_do_not_kill“ is applied in exceptional circumstances to a running Virtual Machine to make it exempt from automatic termination – sound familiar? Given that adding the tag bypasses the established process, how can we now monitor for compliance? Let’s see how we could write a control to discover such non-compliant compute instances:

control 'gcp-all-compute-labels-4' do
  title 'Ensure there are no compute instances with operations_override_do_not_kill label in use'
  google_compute_zones(project: 'gcp-project').zone_names.each do |zone_name|
    google_compute_instances(project: 'gcp-project', zone: zone_name).instance_names.each do |instance_name|
      describe google_compute_instance(project: 'gcp-project', zone: zone_name, name: instance_name) do
        its('labels_keys') { should_not include 'operations_override_do_not_kill' }
       end
    end
  end
end

This is slightly more involved so let’s step through through what’s happening.  Supplying only the GCP project name to this control, we first retrieve all available zones and loop over them.  For each zone the list of compute instances are then also retrieved. Next we examine each compute instance individually and confirm whether or not our favorite “operations_override_do_not_kill”  label is present.

Running this with InSpec in a situation where one machine has this label yields the following sample output:

Hopefully the above demonstrates that with only minimal customization, InSpec continuous compliance checks can be a powerful tool for those using GCP or indeed other cloud providers.  As previously mentioned, the inspec-gcp resource pack welcomes suggestions for improvement or contributions from the community.  The sample compliance profile containing the above checks is available here.

Stuart Paterson

Stuart Paterson is a Principal Engineer at Chef Software. He is based in Belfast and enjoys solving technical challenges in close collaboration with Chef’s partners. Before joining Chef, Stuart worked in a variety of roles across multiple verticals in Financial Services. His background before that was in particle physics.