# 4. Exploring the InSpec Shell
Before we test our NGINX configuration, let's plan which resources and matchers we'll need.
When writing InSpec code, many resources are available to you.
- You can explore the InSpec documentation (opens new window) to see which resources and matchers are available.
- You can examine the source code (opens new window) to see what's available. For example, you can see how file and other InSpec resources are implemented.
- You can also use examples, such as profiles provided on Chef Supermarket (opens new window), as a guide.
There's also InSpec shell (opens new window), which enables you to explore InSpec interactively. In this part, you'll use the InSpec shell to discover which resources you can use to test your NGINX configuration.
You're not required to use InSpec shell to develop your profiles. Some users find the InSpec shell to be a useful way to get immediate feedback and explore what's available. You can also use InSpec shell to debug your profiles.
# 4.1. Enter the shell
Run inspec shell
to enter the interactive session.
inspec shell
Which should drop you into the shell, like this:
Welcome to the interactive InSpec Shell
To find out how to use it, type: help
You are currently running on:
Name: ubuntu
Families: debian, linux, unix
Release: 16.04
Arch: x86_64
2
3
4
5
6
7
8
9
Run help
to see what commands are available.
help
inspec> help
You are currently running on:
Name: ubuntu
Families: debian, linux, unix
Release: 16.04
Arch: x86_64
Available commands:
`[resource]` - run resource on target machine
`help resources` - show all available resources that can be used as commands
`help [resource]` - information about a specific resource
`help matchers` - show information about common matchers
`exit` - exit the InSpec shell
You can use resources in this environment to test the target machine.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Run help resources
to see which resources are available.
help resources
inspec> help resources
- aide_conf
- apache
- apache_conf
- apt
- audit_policy
- auditd
- auditd_conf
...
- file
...
- xml
- yaml
- yum
- yumrepo
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
You see file
and other resources listed.
# 4.2. Exploring the file
resource
Earlier, we saw this describe
block:
describe file('/tmp') do
it { should be_directory }
end
2
3
Let's run a few commands from the InSpec shell to see how the file
resource functions.
InSpec is a DSL on top of Ruby
InSpec is built on the Ruby programming language. InSpec matchers are implemented as Ruby methods.
Run this command to list which methods are available to the file
resource.
file('/tmp').class.superclass.instance_methods(false).sort
Which will give you the following output:
inspec> file('/tmp').class.superclass.instance_methods(false).sort
=> [:allowed?,
:basename,
:block_device?,
:character_device?,
:contain,
:content,
:directory?,
...
:sticky,
:sticky?,
:suid,
:symlink?,
:to_s,
:type,
:uid,
:version?,
:writable?]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
You can use the arrow or Page Up and Page Down keys to scroll through the list. When you're done, press Q
.
What is the InSpec Shell?
InSpec shell is based on a tool called pry
. pry
is an interactive debugging environment for ruby and is one of the ruby developers weapons against bugs.
Let's use the InSpec shell to explore some resources in InSpec. We will start with one of the most common elements on the system, a directory.
In the InSpec Shell call the file.directory?
method.
file('/tmp').directory?
Which will return true
, since /tmp
is a directry on the system.
inspec> file('/tmp').directory?
=> true
2
You see that the /tmp
directory exists on your workstation container.
InSpec - using rspec 'syntax sugar - exposes resource methods as matchers to make the language more logical and easy to read.
For example, the file.directory?
method becomes the be_directory
matcher, and the file.readable?
method becomes the be_readable
matcher. In fact any defined method with a ?
at the end will be turned into a be_method-name
matcher in InSpec.
Following the `boolean ?` convention is key
Given what we have just learned, the best practice
of always having any method with a ?
at the end always return something that evaluates to true
or false
and is critical in InSpec and rSpec if you want your resources to function correctly. In Ruby, false
and nil
are false
; everything else evaluates to true
.
See Ruby predicate methods (opens new window) to learn more...
The InSpec shell understands the structure of blocks. This enables you to run mutiline code. As an example, run the entire describe
block like this.
describe file('/tmp') do
it { should be_directory }
end
2
3
Which will run the entire block of code in the InSpec Shell and return the result.
inspec> describe file('/tmp') do
inspec> it { should be_directory }
inspec> end
Profile: inspec-shell
Version: (not specified)
File /tmp
✔ should be directory
Test Summary: 1 successful, 0 failures, 0 skipped
2
3
4
5
6
7
8
9
10
11
In practice, you don't typically run controls interactively this way for day to day use, but it is a great way to test out your ideas, find bugs or validate your approach before running a scan in it entirety on a target of evaluation.
# 4.3. Explore the nginx resource
Now's a good time to define the requirements for our NGINX configuration. Let's say that you require:
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:
* be owned by the `root` user and group.
* not be readable, writeable, or executable by others.
2
3
4
5
6
7
8
9
Let's see what resources are available to help define these requirements as InSpec controls.
Run help resources
a second time.
help resources
Notice InSpec provides two built-in resources to support NGINX – nginx
and nginx_conf
.
inspec> help resources
- aide_conf
- apache
- apache_conf
- apt
...
- nginx
- nginx_conf
...
- xml
- yaml
- yum
- yumrepo
- zfs_dataset
- zfs_pool
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Run nginx.methods
,
nginx.methods
You see the version
and modules
methods.
You'll use these methods to define the first two requirements.
inspec> nginx.class.superclass.instance_methods(false).sort
=> [:bin_dir,
:compiler_info,
:error_log_path,
:http_client_body_temp_path,
:http_fastcgi_temp_path,
:http_log_path,
:http_proxy_temp_path,
:http_scgi_temp_path,
:http_uwsgi_temp_path,
:lock_path,
:modules,
:modules_path,
:openssl_version,
...
:to_s,
:version]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Run nginx.version
to see what result you get.
nginx.version
inspec> nginx.version
NoMethodError: undefined method `[]' for nil:NilClass
from /opt/inspec/embedded/lib/ruby/gems/2.4.0/gems/inspec-2.0.17/lib/resources/nginx.rb:39:in `block (2 levels) in <class:Nginx>'
2
3
4
Expected Error Ahead
Recall that you're working on your workstation environment, which does not have NGINX installed.
Run the following, and we can verify this with Inspec:
package('nginx').installed?
As you can see we get false
- since nginx is not installed on your runner
.
inspec> package('nginx').installed?
=> false
2
3
Now that we have explored and discovered the resource methods we need – version
and modules
– let's run InSpec shell commands against the target that does have NGINX installed to see what results we find.
To do so, first start by exiting your InSpec shell session.
inspec> exit
Run inspec shell
a second time and this time, provide the -t
argument to connect the shell session to the target container.
First let's find our nginx container id using the docker ps
command:
docker ps
Which will return something like:
➜ course git:(master) ✗ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d4bcef5bb9e3 nginx:latest "/docker-entrypoint.…" 23 seconds ago Up 22 seconds 80/tcp nifty_shtern
4f0ceb9b5974 nginx:latest "/docker-entrypoint.…" 2 months ago Up 45 seconds 0.0.0.0:80->80/tcp nginx
2
3
4
We can then use the container id of our nginx container to target the inspec shell at that container.
inspec shell -t docker://CONTAINER_ID
InSpec will then return at the Shell Prompt as before but this time we see we are on our container.
Welcome to the interactive InSpec Shell
To find out how to use it, type: help
You are currently running on:
Name: debian
Families: debian, linux, unix, os
Release: 11.2
Arch: aarch64
inspec>
2
3
4
5
6
7
8
9
10
11
Remember that the target does not have InSpec installed on it. Your shell session exists on the workstation; InSpec routes commands to the target instance over Docker.
Run the package
resource a second time, this time on the target container.
package('nginx').installed?'
As you can see, how the InSpec package
resources returns true
.
inspec> package('nginx').installed?
=> true
2
3
Now, let's get the version
of NGINX that is installed on the target, run: nginx.version
nginx.version
You see that version 1.10.3 was installed on our container.
inspec> nginx.version
=> "1.10.3"
2
3
To complete the example, let's see which modules
are enabled on the nginx container. Run nginx.modules
to list the installed NGINX modules.
nginx.modules
You see below - and hopefully on the data you got back on your container - that the required modules, http_ssl
, stream_ssl
, and mail_ssl
, are installed.
inspec> nginx.modules
=> ["http_ssl",
"http_stub_status",
"http_realip",
"http_auth_request",
"http_addition",
"http_dav",
"http_geoip",
"http_gunzip",
"http_gzip_static",
"http_image_filter",
"http_v2",
"http_sub",
"http_xslt",
"stream_ssl",
"mail_ssl"]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
The nginx_conf (opens new window) resource examines the contents of the NGINX configuration file, /etc/nginx/nginx.conf
.
Recall that the third requirement is to check whether the NGINX configuration file is owned by root
and is not readable, writeable, or executable by others. Because we want to test attributes of the file itself, and not its contents, you'll use the file
resource.
You saw earlier how the file
resource provides the readable
, writeable
, and executable
methods. You would also see that the file
resource provides the owned_by
and grouped_into
methods.
inspec> file('/tmp').class.superclass.instance_methods(false).sort
=> [:allowed?,
:directory?,
:executable?,
:exist?,
:file,
:file?,
:file_version,
:gid,
:group,
:grouped_into?,
...
:owned_by?,
...
:readable?,
...
:to_s,
:type,
:uid,
:version?,
:writable?]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
These 5 file
methods – grouped_into
, executable
, owned_by
, readable
and writeable
– provide everything we need for the third requirement.
Exit the InSpec shell session.
inspec> exit