Skip to content
SAF InSpec Profile DeveloperSAF InSpec Profile Developer
MITRE InSpec Profile Developer Course
Course
Resources
Installation
  • Course

    • Course Overview
      • Starting the Journey
        • Studying an InSpec Profile
          • Exploring the InSpec Shell
            • Writing InSpec Controls
              • Writing InSpec Controls
                • Security & Configuration Requirements
                  • Requirement (1) - NGINX Version
                    • Requirement (2) - NGINX Modules are Installed
                      • Requirement (3) - Permission on the nginx_conf file
                      • Refactoring the code to use Inputs
                        • Multiple Inputs Example
                          • Running the baseline straight from Github
                          • Generating InSpec Results
                            • Viewing and Analyzing Results
                              • InSpec Examples
                                • Profile Inheritance & Overlays
                                  • From STIG to Profile

                                  Writing InSpec Controls

                                  May 22, 2022About 5 min

                                  On This Page
                                  • Writing InSpec Controls
                                    • Security & Configuration Requirements
                                    • Requirement (1) - NGINX Version
                                    • Requirement (2) - NGINX Modules are Installed
                                    • Requirement (3) - Permission on the nginx_conf file
                                  • Refactoring the code to use Inputs
                                  • Multiple Inputs Example
                                  • Running the baseline straight from Github

                                  # Writing InSpec Controls

                                  Now that you understand which methods map to each requirement, you're ready to write InSpec controls.

                                  # Security & Configuration Requirements

                                  Recall from Section 4 that we want to verify that our 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:
                                     * be owned by the `root` user and group.
                                     * not be readable, writeable, or executable by others.
                                  
                                  

                                  # 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.

                                  inspec exec /root/my_nginx -t docker://DOCKER_CONTAINER_ID
                                  

                                  This will ouput:

                                    Profile: InSpec Profile (my_nginx)
                                    Version: 0.1.0
                                    Target:  docker://DOCKER_CONTAINER_ID
                                  
                                      βœ”  nginx-version: NGINX version
                                         βœ”  Nginx Environment version should cmp >= "1.10.3"
                                  
                                  
                                    Profile Summary: 1 successful control, 0 control failures, 0 controls skipped
                                    Test Summary: 1 successful, 0 failures, 0 skipped
                                  

                                  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.

                                  $ inspec exec /root/my_nginx -t docker://DOCKER_CONTAINER_ID
                                  

                                  Which again, will output:

                                  
                                    Profile: InSpec Profile (my_nginx)
                                    Version: 0.1.0
                                    Target:  docker://DOCKER_CONTAINER_ID
                                  
                                      βœ”  nginx-version: NGINX version
                                         βœ”  Nginx Environment version should cmp >= "1.10.3"
                                      βœ”  nginx-modules: NGINX version
                                         βœ”  Nginx Environment modules should include "http_ssl"
                                         βœ”  Nginx Environment modules should include "stream_ssl"
                                         βœ”  Nginx Environment modules should include "mail_ssl"
                                  
                                  
                                    Profile Summary: 2 successful controls, 0 control failures, 0 controls skipped
                                    Test Summary: 4 successful, 0 failures, 0 skipped
                                  

                                  This time, both controls pass.

                                  # Requirement (3) - Permission on the nginx_conf file

                                  The third 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' 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 third control uses the file resource. 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.

                                  $ inspec exec /root/my_nginx -t docker://DOCKER_CONTAINER_ID
                                  

                                  Again, outputting:

                                    Profile: InSpec Profile (my_nginx)
                                    Version: 0.1.0
                                    Target:  docker://DOCKER_CONTAINER_ID
                                  
                                      ...
                                      Γ—  nginx-conf: NGINX configuration (1 failed)
                                         βœ”  File /etc/nginx/nginx.conf should be owned by "root"
                                         βœ”  File /etc/nginx/nginx.conf should be grouped into "root"
                                         Γ—  File /etc/nginx/nginx.conf should not be readable by others
                                         expected File /etc/nginx/nginx.conf not to be readable by others
                                         βœ”  File /etc/nginx/nginx.conf should not be writable by others
                                         βœ”  File /etc/nginx/nginx.conf should not be executable by others
                                  
                                  
                                    Profile Summary: 2 successful controls, 1 control failure, 0 controls skipped
                                    Test Summary: 8 successful, 1 failure, 0 skipped
                                  

                                  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.

                                  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.

                                  # 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' 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
                                  

                                  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 in 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:

                                  inputs:
                                    - name: servers
                                      type: array
                                      value:
                                        - server1
                                        - server2
                                        - server3
                                  

                                  Similarly as the above example we can edit our inspec.yml file like this:

                                  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.'
                                    required_modules = input('nginx_modules')
                                    describe nginx do
                                      required_modules.each do |required_module|
                                        its('modules') { should include required_module }
                                      end
                                    end
                                  end
                                  

                                  # Multiple Inputs Example

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

                                  For example, a inspec.yml:

                                  inputs:
                                    - name: users
                                      type: array
                                      required: true
                                  

                                  A YAML file named windows.yml

                                  users:
                                    - Administrator
                                    - Guest
                                    - Randy
                                  

                                  A YAML file named linux.yml

                                  users:
                                    - root
                                    - shadow
                                    - rmadison
                                  

                                  The control file:

                                  control 'system-users' do
                                    impact 0.8
                                    desc 'Confirm the proper users are created on the system'
                                  
                                    describe users do
                                      its('usernames') { should eq input('users') }
                                    end
                                  end
                                  

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

                                  inspec exec examples/profile-input --input-file examples/windows.yml
                                  

                                  And, on our Windows systems:

                                  inspec exec examples/profile-input --input-file examples/linux.yml
                                  

                                  # Running the baseline straight from Github

                                  In this module, we use NGINX for learning purposes. If you're interested in NGINX specifically, you may be interested in the MITRE nginx-srg-baselineopen in new window profile on GitHub.

                                  To execute the GitHub profile on your target system, run this inspec exec command.

                                  inspec exec https://github.com/mitre/nginx-baseline -t docker://DOCKER_CONTAINER_ID
                                  
                                  Edit this pageopen in new window
                                  Last update: 8/15/2022, 7:18:22 PM
                                  Contributors: Aaron Lippold,Emily Rodriguez,wdower
                                  Prev
                                  Exploring the InSpec Shell
                                  Next
                                  Generating InSpec Results
                                  Apache-2.0 | Copyright Β© 2022 - The MITRE Corporation
                                  Copyright Β© 2022 Aaron Lippold