Skip to content

Instantly share code, notes, and snippets.

@jamesc
Last active August 29, 2015 14:10
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamesc/133ef2c724d4b11e57f0 to your computer and use it in GitHub Desktop.
Save jamesc/133ef2c724d4b11e57f0 to your computer and use it in GitHub Desktop.
Chef Client audit mode

Audit Mode

Audit mode is an new phase in Chef which allows you to evaluate custom rules, defined in your recipes, on every node during each chef-client run. Use audits to ensure nodes fall into existing "known states" categories even before Chef converges, and to validate your infrastructure after Chef converges.

Running audits

Audits are written inside your recipes and Chef will run all the audit groups it finds in the expanded run list. You can have recipes that only include audits, or you can add audits to existing recipes.

Configuration and command-line options

Auditing occurs as its own phase during a full chef-client run, running independently of convergence. By default, audit mode always runs after chef-client converges. Client can be configured to only converge or only audit as well.

Converge-only mode

You can tell chef-clientto skip audits using the command line flag --no-audit-mode, or by adding the line audit_mode :disabled to your config file.

Audit-only mode

To configure chef-client to only run audits, use the command line flag --audit-mode, or add the line audit_mode :audit_only to your config file.

Reporting

Chef's resource reporter sends converge and audit data back to the Chef server after each client run. Two reports are generated, one for converge results and another for audit results. If there is an error in the converge phase, the audit phase, or both, then each report will contain the error details, respectively.

Error handling

Errors in the converge phase will not affect the execution of the audit phase. Further, errors in the audit phase will not prevent sending reporting data from the converge phase. The phases are run independently and errors are collected and provided to the error handlers once each phase is finished.

Syntax

Controls group

Audits are written inside your recipe files. Audit groups define rules you expect your infrastructure to align with. To distinguish audit groups from other recipe resources, use controls. Audits can only be defined within controls, which creates an audit group. The controls method requires a "name" for the group, which serves to identify one audit group from another.

# cookbook: example
# recipe: default

controls "a thing" do
  # define audits here
end

Basic structure: control, it

Within an audit group, you can define your audit rules. Rules are declared using it. Rules can be grouped together with the control method. You can have multiple control statements in an audit group, and nest control statements together.

Example: One rule

When I include this example::default recipe in my run list

# cookbook: example
# recipe: default

controls "MySQL" do
  it "is installed" do
  end
end

and I run chef-client with audit mode enabled, I expect to see

MySQL
  is installed

Example: One group, one rule

When I include this example::control recipe in my run list

# cookbook: example
# recipe: control

controls "MySQL" do
  control "config directory" do
    it "exists with correct permissions" do
    end
  end
end

and I run chef-client with audit mode enabled, I expect to see

MySQL
  config directory
    exists with correct permissions

Example: Nested groups

When I include this example::nested_control recipe in my run list

# cookbook: example
# recipe: nested_control

controls "MySQL" do
  it "is installed" do
  end
  control "config directory" do
    it "exists with correct permissions" do
    end
    control "config directory file" do
      it "contains the required configuration" do
      end
    end
  end
end

and I run chef-client with audit mode enabled, I expect to see

MySQL
  is installed
  config directory
    exists with correct permissions
    config directory file
      contains the required configuration

Types and matchers

Audit rules are written using Serverspec types and matchers, in conjunction with RSpec's built-in matchers. Matchers are used with expect(..).to for positive rules expect(..).to_not for negative rules. The expect is given the type, or object, you want to assert matchers on.

(An important note when looking at the Serverspec documentation: the examples use :should syntax, which we do not support. Use :expect syntax instead.)

Example: A package is installed

When I include this example::mysql recipe in my run list

# cookbook: example
# recipe: mysql

controls "mysql audit" do
  control "mysql package" do
    it "is installed" do
      expect(package("mysql")).to be_installed.with_version("5.6")
    end
  end
end

and I run chef-client with audit mode enabled, I expect to see

mysql audit
  mysql package
    is installed

Example: A package is not installed

You can also use matchers to verify that a type does not have a property with to_not. When I include this example::postgres recipe in my run list

# cookbook: example
# recipe: postgres

controls "postgres audit" do
  control "postgres package" do
    it "is not installed" do
      expect(package("postgresql")).to_not be_installed
    end
  end
end

and I run chef-client with audit mode enabled, I expect to see

postgres audit
  postgres package
    is not installed

Example: A service is running

The service type helps you ensure that the packages you installed are enabled and running. When I include this example::mysql_service recipe in my run list

# cookbook: example
# recipe: postgres

controls "mysql service audit" do
  control "mysql service" do
    it "is enabled" do
      expect(service("mysql")).to be_enabled
    end
    it "is running" do
      expect(service("mysql")).to be_running
    end
  end
end

and I run chef-client with audit-mode enabled, I expect to see

mysql service audit
  mysql service
    is enabled
    is running

Example: A file has permissions and contents

There are many other types and matchers available for writing audit rules. When I include this example::config recipe in my run list

# cookbook: example
# recipe: config

controls "mysql config" do
  control "mysql config file" do
    let(:config_file) { file("/etc/mysql/my.cnf") }
    it "exists with correct permissions" do
      expect(config_file).to be_file
      expect(config_file).to be_mode(0400)
    end
    it "contains required configuration" do
      expect(its(:contents)).to match(/default-time-zone='UTC'/)
    end
  end
end

and I run chef-client with audit mode enabled, I expect to see

mysql config
  mysql config file
    exists with correct permissions
    contains required configuration

Duplicate audit groups

Having two audit groups with the same name is explicitly forbidden and will raise an error. Be sure to give controls descriptive names to avoid such collisions.

Example: Duplicate controls

When I include this example::duplicate recipe in my run list:

# cookbook: example
# recipe: duplicate
controls "mysql audit" do

  control "mysql package" do
    it "is installed" do
      expect(package("mysql")).to be_installed.with_version("5.6")
    end
  end

end

controls "mysql audit" do

  control "mysql package" do
    it "is installed" do
      expect(package("mysql")).to be_installed.with_version("5.6")
    end
  end

end

and I run chef-client with audit mode enabled, I expect to see output similar to

Compiling Cookbooks...

================================================================================
Recipe Compile Error in /var/chef/cache/cookbooks/tyler-mysql/recipes/default.rb
================================================================================

Chef::Exceptions::AuditControlGroupDuplicate
--------------------------------------------
Audit control group with name 'mysql audit' has already been defined

Cookbook Trace:
---------------
  /var/chef/cache/cookbooks/tyler-mysql/recipes/default.rb:29:in `from_file'

Relevant File Content:
----------------------
/var/chef/cache/cookbooks/example/recipes/duplicate.rb:

 22:      it "is installed" do
 23:        expect(package("mysql")).to be_installed.with_version("5.6")
 24:      end
 25:    end
 26:
 27:  end
 28:
 29>> controls "mysql audit" do
 30:
 31:    control "mysql package" do
 32:      it "is installed" do
 33:        expect(package("mysql")).to be_installed.with_version("5.6")
 34:      end
 35:    end
 36:
 37:  end
 38:

Failure output

When an audit fails, Chef will provide feedback in the logs to help you diagnose what went wrong. Suppose you ran example::default, but Postgres was installed on the node. You would see output similar to

mysql audit
  mysql package
    is installed

postgres audit
  postgres package
    is not installed (FAILED - 1)

mysql config
  mysql config file
    exists with correct permissions
    contains required configuration

Failures:
  1) postgres audit postgres package is not installed
     Failure/Error: expect(package("postgres")).not_to be_installed
       expected Package "postgres" not to be installed
     # Backtrace (excluded for this example)

The failure output should include a backtrace to help you identify where the audit failed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment