The Audit Cookbook: A How-To

The audit cookbook is a tool used to run InSpec tests and send the results to chef-compliance (either directly or via chef-server) or to chef-visibility in an automated way.

We recently took on an overhaul of the audit cookbook to rewrite the content using chef handlers.

What changed

  • The audit cookbook is now using a chef handler, so you should no longer see that any resources were updated at the end of the run. Idempotency for the win!
  • Much of the extra profile aggregation logic has been removed; we are now doing a straight call to the inspec runner, similar to what is done in kitchen-inspec
  • The way profiles are specified in the audit attributes has been changed, in an effort to harmonize profile locations across our tools
  • There is now an attribute for fetcher. This enables the audit cookbook to fetch a profile from chef-compliance via chef-server, while reporting to whatever reporter you’d like (i.e. chef-visibility)
  • You can now choose to send the json report to a file on disk
  • You can now specify multiple reporters

Chef Handler

While the change to using a chef handler doesn’t have any impact on how you set up the audit cookbook, it’s worth mentioning that you should now see the output at the end of the audit cookbook run look something like the following after you have run it once:

InSpec Runner

This is another one that doesn’t have any impact on how you set up to use the audit cookbook, but it’s good information to know. The InSpec runner is the thing that grabs the profile locations and begins the process of executing them. In the audit cookbook, that call is here. Why is that something to know? Because, if you’re trying to use the audit cookbook to run some profiles and something is going wildly wrong, you can easily test if your profile location is incorrect by attempting to run it with inspec exec PROFILE. This is also the same way kitchen-inspec works.

How to Define Profile Locations

This is a bigger change. In an attempt to harmonize profile locations, we have switched to passing an array of hashes. We support path, url, git, compliance, and supermarket. name can be used in conjunction with any of these. a reference to name on its own defaults to supermarket. So in your .kitchen.yml, you will now define your profiles like this:

  suites:
    attributes:
      audit:
        profiles:
            - path: test/recipes/master   # local path
            - name: hardening/ssh-hardening   # defaults to supermarket
            - name: os-hardening   # name and url
              url: https://github.com/dev-sec/tests-os-hardening/archive/master.zip
            - git: https://github.com/dev-sec/tests-ssh-hardening.git   # git location
            - name: ssh   # profile on supermarket, a little more explicitly defined
              supermarket: hardening/ssh-hardening
            - name: ssh-hardening  # the 'name' part is optional. it can be included in any of these definitions
              git: https://github.com/dev-sec/tests-ssh-hardening.git
            - name: linux   # profile from chef-compliance
              compliance: base/linux
            - name: master-local   # local path to profile with a name defined
              path: test/recipes/master

Why this whole ‘name’ thing, you ask? Good question! The name is what we use to fill in the profile information. If you’re familiar with the InSpec command line, you would recognize it as:

These ‘names’ are also used when using InSpec’s include_controls functionality, as can be seen here.

Defined in your cookbook, Chef runtime attributes for profile locations look like this:

* Not all of the above locations are defined here below, for the sake of brevity.
  "audit": {
    "profiles": [
      {
        "path": "test/recipes/master"
      },
      {
        "name": "hardening/ssh-hardening"
      },
      {
        "name": "os-hardening",
        "url": "https://github.com/dev-sec/tests-os-hardening/archive/master.zip"
      },
      {
        "git": "https://github.com/dev-sec/tests-ssh-hardening.git"
      },
      {
        "name": "linux",
        "compliance": "base/linux"
      }
    ]

The Fetcher Attribute

A fetcher attribute, but why? Well, sometimes you want to report the results of a profile from chef-compliance in chef-visibility. But, there’s a problem…we don’t have access to those chef-compliance profiles. So how do you do that? You use the fetcher attribute! Setting the fetcher attribute to chef-server allows the audit cookbook to fetch the desired profile so this can happen! Here’s an example for ya:

  attributes:
    audit:
      fetcher: 'chef-server'
      collector: 'chef-visibility'
      inspec_version: 1.2.1
      profiles:
        - name: ssh
          compliance: base/ssh

The json File Reporter

Ever want to have a record of those profile results on disk? Well now you can! Just set the collector attribute to ‘json-file’, and a file named ‘inspec-{timestamp}.json’ will be placed on the filesystem.

Multiple Reporters

You can now set the audit cookbook up to send results to multiple reporters. Need to have your data report to chef-compliance and chef-visibility? Want to also have those results saved to a json file? We got ya covered!

  collector:
    - 'chef-visibility'
    - 'json-file'
    - 'chef-compliance'

Everything Else

All the functionality you know and love from the audit cookbook is still there. That means, in case you were worried, interval reporting still exists, as does profile upload. To enable interval reporting, just set the default['audit']['interval']['enabled'] attribute to true, and set your preferred timing using the default['audit']['interval']['time'] attribute. When the interval enabled attribute is true, we create a simple file named report_timing.json and read the create time of that file to calculate whether or not the profile is overdue to run.

Upload functionality is still there too. So, if you want to upload a profile to chef-compliance, you can do that. There a great example cookbook here.

Real-Life Examples

chef-visibility

  ---
  driver:
    name: vagrant

  provisioner:
    name: chef_zero

  platforms:
    - name: ubuntu-14.04
    - name: centos-7.2

  suites:
    - name: default
      run_list:
        - recipe[test-profiles::default]
      attributes:
        audit:
          collector: 'chef-visibility'
          inspec_version: 1.2.1
          profiles:
            - git: https://github.com/dev-sec/tests-ssh-hardening.git
            - name: ssh
              supermarket: hardening/ssh-hardening

with your data_collector.server_url and data_collector.token defined in your client.rb like this:

        data_collector.server_url ENV['DATA_COLLECTOR_ENDPOINT']
        data_collector.token "93a49a4f2482c64126f7b6015e6b0f30284287ee4054ff8807fb63d9cbd1c506"

chef-compliance

  ---
  driver:
    name: vagrant

  provisioner:
    name: chef_zero

  platforms:
    - name: ubuntu-14.04
    - name: centos-7.2

  suites:
    - name: default
      run_list:
        - recipe[test-profiles::default]
      attributes:
        audit:
          collector: 'chef-compliance'
          server: "https://192.168.33.201/api"
          inspec_version: 1.2.1
          insecure: true
          refresh_token: '2/mEiUPY9xalpq_laGdVKylDy6jxV_yxG8mQJBCITrOuZOrL5DGKKDhRm-PDfk0IMR2p9sKOR2uWEFbPdoq-Bxdg=='
          owner: admin
          profiles:
            - name: ssh
              compliance: base/ssh

chef-server

  ---
  driver:
    name: vagrant

  provisioner:
    name: chef_zero

  platforms:
    - name: ubuntu-14.04
    - name: centos-7.2

  suites:
    - name: default
      run_list:
        - recipe[test-profiles::default]
      attributes:
        audit:
          collector: 'chef-server'
          inspec_version: 1.2.1
          profiles:
            - name: ssh
              compliance: base/ssh

reporting to chef-visibility and saving reports to disk using chef-compliance, external (url), and local profiles

  ---
  driver:
    name: vagrant

  provisioner:
    name: chef_zero

  platforms:
    - name: ubuntu-14.04
    - name: centos-7.2

  suites:
    - name: default
      run_list:
        - recipe[test-profiles::default]
      attributes:
        audit:
          fetcher: 'chef-server'
          collector:
            - 'chef-visibility'
            - 'json-file'
          inspec_version: 1.2.1
          profiles:
            - name: ssh
              compliance: base/ssh
            - name: ssh-hardening
              git: https://github.com/dev-sec/tests-ssh-hardening.git
            - name: local-master
              path: test/master
* Be sure to include data_collector.server_url and data_collector.token in your client.rb whenever you set the collector to chef-visibility

Victoria Jeffrey

Victoria is a Software Engineer at Chef. Currently based in the Miami/Ft. Lauderdale area, she's been with Chef for 1.5 years, focused on InSpec and Chef's commercial offering, Chef Automate.