Skip to main content

5. Writing InSpec Controls

Aaron LippoldAbout 5 min

Writing InSpec Controls

Let's work through some example requirements to write InSpec controls.

Security & Configuration Requirements

We write InSpec controls to test some target based on security guidance. Here, let's verify that the NGINX instance had been configured to meet the following requirements:

1. NGINX version 1.10.3 or later.
2. The following NGINX modules should be installed:
   * `http_ssl`
   * `stream_ssl`
   * `mail_ssl`
3. The NGINX configuration file - `/etc/nginx/nginx.conf`- should exist as a file.
4. The NGINX configuration file should:
   * be owned by the `root` user and group.
   * not be readable, writeable, or executable by others.
5. The NGINX shell access should be restricted to admin users.

Requirement (1) - NGINX Version

The first requirement is for the NGINX version to be 1.10.3 or later.

We can check this using the InSpec cmp matcher.

Replace the contents of my_nginx/controls/example.rb with this:

control 'nginx-version' do
  impact 1.0
  title 'NGINX version'
  desc 'The required version of NGINX should be installed.'
  describe nginx do
    its('version') { should cmp >= '1.10.3' }
  end
end

The `nginx_conf` resource docs

nginx_confopen in new window

The test has an impact of 1.0, meaning it is most critical. A failure might indicate to the team that this issue should be resolved as soon as possible, likely by upgrading NGINX to a newer version. The test compares nginx.version against version 1.10.3.

cmp is one of InSpec's built-in matchersopen in new window. cmp understands version numbers and can use the operators ==, <, <=, >=, and >. cmp compares versions by each segment, not as a string. For example, "7.4" is less than than "7.30".

Next, run inspec exec to execute the profile on the remote target.

Command - req 1
inspec exec ./my_nginx -t docker://nginx

You see that the test passes.

Requirement (2) - NGINX Modules are Installed

The second requirement verifies that our required modules are installed.

  • http_ssl
  • stream_ssl
  • mail_ssl

Append your control file to add this describe block:

control 'nginx-modules' do
  impact 1.0
  title 'NGINX modules'
  desc 'The required NGINX modules should be installed.'
  describe nginx do
    its('modules') { should include 'http_ssl' }
    its('modules') { should include 'stream_ssl' }
    its('modules') { should include 'mail_ssl' }
  end
end

The second control resembles the first; however, this version uses multiple its statements and the nginx.modules method. The nginx.modules method returns a list; the built-in include matcher verifies whether a value belongs to a given list.

Run inspec exec on the target.

Command - req 2
inspec exec ./my_nginx -t docker://nginx

This time, both controls pass.

Requirement (3) - The nginx_conf file

The third requirement verifies that the NGINX configuration file - /etc/nginx/nginx.conf - exists as a file.

Append this describe block to your control file:

control 'nginx-conf-file' do
  impact 1.0
  title 'NGINX configuration file'
  desc 'The NGINX config file should exist as a file.'
  describe file('/etc/nginx/nginx.conf') do
    it { should be_file }
  end
end

Run inspec exec on the target.

Command - req 3
inspec exec ./my_nginx -t docker://nginx

Requirement (4) - Permission on the nginx_conf file

The fourth requirement verifies that the NGINX configuration file, /etc/nginx/nginx.conf:

  • is owned by the root user and group.
  • is not be readable, writeable, or executable by others.

Append your control file to add this describe block:

control 'nginx-conf-perms' do
  impact 1.0
  title 'NGINX configuration'
  desc 'The NGINX config file should owned by root, be writable only by owner, and not writeable or and readable by others.'
  describe file('/etc/nginx/nginx.conf') do
    it { should be_owned_by 'root' }
    it { should be_grouped_into 'root' }
    it { should_not be_readable.by('others') }
    it { should_not be_writable.by('others') }
    it { should_not be_executable.by('others') }
  end
end

The `file` resource docs

fileopen in new window

The first 2 tests use should to verify the root owner and group. The last 3 tests use should_not to verify that the file is not readable, writable, or executable by others.

Run inspec exec on the target.

Command - req 4
inspec exec ./my_nginx -t docker://nginx

This time you see a failure. You discover that /etc/nginx/nginx.conf is potentially readable by others. Because this control also has an impact of 1.0, your team may need to investigate further.

Requirement (5) - NGINX shell access

The last requirement checks whether NGINX shell access is provided to non-admin users. In this case, access to bash needs to be restricted to admin users.

Append this describe block to your control file:

control 'nginx-shell-access' do
  impact 1.0
  title 'NGINX shell access'
  desc 'The NGINX shell access should be restricted to admin users.'
  describe users.shells(/bash/).usernames do
    it { should be_in ['admin']}
  end
end

Run inspec exec on the target.

Command - req 5
inspec exec ./my_nginx -t docker://nginx

Remember, the first step, detect, is where you identify where the problems are so that you can accurately assess risk and prioritize remediation actions.

For the second step, correct, you can use a configuration management tool or some other automation framework to correct compliance failures for you.

You won't correct this issue in this module, but later you can check out the Integrated Compliance with Chefopen in new window track to learn more about how to correct compliance issues using Chef.

The Target ID in the InSpec exec output

The target_id in the reporter is the UUID generated by train to uniquely identify the target system/node. Reference the PR that made this updateopen in new window