Skip to main content

6. Inputs in InSpec

Aaron LippoldAbout 3 min

Refactoring the code to use Inputs

Your my_nginx profile is off to a great start. As your requirements evolve, you can add additional controls. You can also run this profile as often as you need to verify whether your systems remain in compliance.

Let's review the control file, example.rb.

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

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

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

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

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

Although these rules do what you expect, imagine your control file contains dozens or hundreds of tests. As the data you check for, such as the version or which modules are installed, evolve, it can become tedious to locate and update your tests. You may also find that you repeat the same data across multiple control files.

One way to improve these tests is to use inputs. Inputs enable you to separate the logic of your tests from the data of your tests. Input files are typically expressed as a YAML file (files ending in .yaml or .yml).

Profile Inputs exist in your profile's main directory within the inspec.yml for global inputs to be used across all the controls in your profile.

Let's create the inspec.yml file for our profile:

name: my_nginx
title: InSpec Profile
maintainer: The Authors
copyright: The Authors
copyright_email: you@example.com
license: Apache-2.0
summary: An InSpec Compliance Profile
version: 0.1.0
supports:
  platform: os

inputs:
  - name: nginx_version
    type: String
    value: 1.10.3

To access an input you will use the input keyword. You can use this anywhere in your control code.

For example:

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 >= input('nginx_version') }
  end
end

For our next control we require specific modules

Example of adding an array object of servers:

Array Input Generic
  - name: servers
    type: Array
    value:
      - server1
      - server2
      - server3

Your control can be changed to look like this:

control 'nginx-modules' do
  impact 1.0
  title 'NGINX modules'
  desc 'The required NGINX modules should be installed.'

  nginx_modules = input('nginx_modules')
  
  describe nginx do
    nginx_modules.each do |current_module|
      its('modules') { should include current_module }
    end
  end
end

Lastly, we can edit our inspec.yml file to create a list of admin users:

Array Input - Admin Users
  - name: admin_users
    type: Array
    value:
      - admin

Your fifth control can be changed to look like this:

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 input('admin_users')}
  end
end

Input File Example

To change your inputs for platform specific cases you can setup multiple input files.

For example, an NGINX web server could be run on a Windows or Linux machine, which may require different admin users for each context. The inputs can be tailored for each system. You can create the inputs-windows.yml and inputs-linux.yml files in your home directory.

Note

Another example is that a production and development environment may require different inputs.

inputs-windows.yml
admin_users:
  - Administrator
  - Randy

The following command runs the tests and applies the inputs specified, first, on the Linux system:

inspec exec ./my_nginx -t docker://nginx --input-file inputs-linux.yml

And, on our Windows systems:

inspec exec ./my_nginx -t docker://nginx --input-file inputs-windows.yml
Best Practice - inputs.yml file

It is best practice to create a separate file for inputs when using the provided profile. The exception to this is when working with an overlay profile, which you will see in Section 10.