6. Inputs in InSpec
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:
- name: servers
type: Array
value:
- server1
- server2
- server3
- name: nginx_modules
type: Array
value:
- http_ssl
- stream_ssl
- mail_ssl
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
- name: nginx_modules
type: Array
value:
- http_ssl
- stream_ssl
- mail_ssl
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:
- name: admin_users
type: Array
value:
- admin
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
- name: nginx_modules
type: Array
value:
- http_ssl
- stream_ssl
- mail_ssl
- 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.
admin_users:
- Administrator
- Randy
admin_users:
- root
- randy
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
- name: nginx_modules
type: Array
value:
- http_ssl
- stream_ssl
- mail_ssl
- name: admin_users
type: Array
value:
- admin
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.