3. Studying an InSpec Profile - NGINX Example
Studying an InSpec profile
The best way to learn how an InSpec profile works is to write our own example. Let's start out with a simple profile and start adding more tests to make it useful!
Our Use Case
For the purposes of this class, we will assume the role of an infrastructure engineer who is responsible for the security of a NGINX webserver, which is running in a Docker container named nginx
. We want a way to automate the process of validating the security of our webserver, and we have settled on InSpec as our assessment tool of choice. We have installed InSpec on the host machine for our NGINX container (your GitHub Codespace virtual machine).
What if I don't know anything about NGINX or webservers?
Don't worry. This class does not expect you to be an expert in webserver administration. The concepts we introduce here are relevant to any category of software, from end user applications all the way down to the operating system running your apps.
Our Requirements
Let's say that we are attempting to validate that our NGINX webserver complies with the following simple list of requirements.
1. NGINX should be installed as version 1.27.0 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.
6. NGINX admins should have documentation on security procedures.
Creating the profile
We'll start by creating a profile that will contain the tests we want to run against NGINX.
At your terminal, from your preferred directory, type:
inspec init profile my_nginx
βββββββββββββββββββββββββββ InSpec Code Generator βββββββββββββββββββββββββββ
Creating new profile at /workspaces/saf-training-lab-environment/my_nginx
β’ Creating file inspec.yml
β’ Creating directory /workspaces/saf-training-lab-environment/my_nginx/controls
β’ Creating file controls/example.rb
β’ Creating file README.md
my_nginx_answer_key
You may notice that you already have a folder named my_nginx_answer_key
in your lab system environment that looks suspiciously like a complete InSpec profile for testing NGINX.
That profile is what we will be constructing in the next few sections. Feel free to reference it if you want to see what your code is supposed to look like when we are done.
Understanding the profile structure
Let's take a look at how the profile is structured. We'll start with how a profile's files are structured and then move to what makes up an InSpec control.
First, run tree
from the directory that contains your my_nginx
profile to take a look inside.
tree my_nginx
my_nginx
βββ README.md
βββ controls
β βββ example.rb
βββ inspec.yml
Here's the role of each component.
README.md
provides documentation about the profile, including what it covers and how to run it.- The
controls
directory contains files that define the actual tests. inspec.yml
provides metadata about the profile. Metadata includes the profile's human-friendly description, author, version, and what parameters we can pass to the profile at runtime.- You may optionally have a
libraries
directory. Thelibraries
directory contains resource extensions. A resource extension enables you to define your own resource types. The Advanced Security Automation Developer Class will discuss in-depth how to define your own custom resources.
Understand a control's structure
Let's take a look at the default control file, controls/example.rb
.
# copyright: 2018, The Authors
title 'sample section'
# you can also use plain tests
describe file('/tmp') do
it { should be_directory }
end
# you add controls here
control 'tmp-1.0' do # A unique ID for this control
impact 0.7 # The criticality, if this control fails.
title 'Create /tmp directory' # A human-readable title
desc 'An optional description...'
describe file('/tmp') do # The actual test
it { should be_directory }
end
end
This example shows two tests. Both tests check for the existence of the /tmp
directory. The second test provides additional information about the test. Let's break down each component.
control
(line 11) is followed by the control's name. Each control in a profile must have a unique control identifier which is usually derived from the unique identifier of the security requirement being assessed.impact
(line 12) measures the severity, or relative importance, of the test and must be a value between 0.0 and 1.0 where 0.0 is not applicable and 1.0 is of critical importance.title
(line 13) concisely defines the control's purpose.desc
(line 14) provides a more complete description of what the control is assessing.describe
(lines 15 β 17) defines the test. Here, the test checks for the existence of the/tmp
directory.
In Ruby, the do
and end
keywords define a block
. An InSpec control always contains at least one describe
block. However, a control can contain many describe
blocks as many items might need to be tested to fully assess the requirement.
More information on a block in Ruby
Understand a describe block's structure
As with many test frameworks, InSpec code resembles natural language. Here's the format of a describe block.
describe <entity> do
it { <expectation> }
end
An InSpec test has two main components: the subject to examine and the subject's expected state. Here, <entity>
is the subject you want to examine, for example, a package name, service, file, or network port. The <expectation>
specifies the desired result or expected state, for example, that a port should be open (or perhaps should not be open.)
Let's take a closer look at the describe
block in the example.
describe file('/tmp') do
it { should be_directory }
end
Because InSpec resembles human-readable language, you might read this test as "/tmp should be a directory." Let's break down each component.
file
file is an InSpec resource. Resources are written as Ruby classes to describe a part of the system, providing attributes that are easy to call upon within the InSpec test. For example, the InSpec file resource tests for file attributes, including a file's owner, mode, and permissions. The example examines the /tmp directory.
Note
If you're familiar with Chef, you know that a resource configures one part of the system. InSpec resources are similar.
it
The it
statement validates one of your resource's features. A describe
block contains one or more it
statements. it
enables you to test the resource itself. You'll also see its
, which describes some feature of the resource, such as its mode or owner. You'll see examples of both it
and its
shortly.
it vs. its
Important! Just like in English grammar, pay attention to the difference between the thing (it) and the possessive word (its).
it
is used to describe an action or the expected behavior of the subject/resource in question.
e.g. it { should exist }
its
is used to specify the expectation(s) of an attribute of the subject/resource.
e.g. its('size') { should eq 64 }
should
should
describes the expectation. should
asserts that the condition that follows should be true. Alternatively, should_not
asserts that the condition that follows should not be true. You'll see examples of both shortly.
be_directory
be_directory
is an example of a matcher. A matcher compares a resource's actual value to its expected value. InSpec provides several predefined matchers. The file
resource provides the be_directory
matcher.
Comprehension Check!
Look at the 4 file structures below. Determine which are valid InSpec Profiles that will run the tests!
folder_A
βββ README.md
βββ controls
β βββ example-control-1.rb
β βββ example-control-2.rb
β βββ example-control-3.rb
βββ inspec.yml
2 directories, 5 files
folder_B
βββ README.md
βββ controls
βββ example-control-1.rb
βββ example-control-2.rb
βββ example-control-3.rb
2 directories, 4 files
folder_C
βββ README.md
βββ example-control-1.rb
βββ example-control-2.rb
βββ example-control-3.rb
βββ inspec.yml
1 directories, 5 files
folder_D
βββ Gemfile
βββ README.md
βββ controls
β βββ example-control-1.rb
β βββ example-control-2.rb
β βββ example-control-3.rb
βββ inspec.yml
βββ libraries
βββ my_resource.rb
3 directories, 7 files
Which of the folders above (A, B, C, and D) are valid InSpec profiles?
A and D are valid InSpec profiles!
INVALID PROFILES:
- B is not a profile because it is missing the inspec.yml file!
- C is not a profile because it does not have a controls directory!
VALID PROFILES:
- A is a profile!
- D is a profile! A profile can have extra things like a Gemfile and a libraries folder as long as it has the controls directory and the inspec.yml file!
TIP: inspec check
To see if you have a valid InSpec profile, you can run inspec check <path-to-inspec-profile-folder>