Never Ending Security

It starts all here

CHEF: Configuration Management System


As your infrastructure requirements expand, managing each server by hand becomes an increasingly difficult task. This difficulty is compounded by the requirement for reproducibility, which becomes necessary if a node fails or if horizontal scaling is needed.

Configuration management solutions are designed to address these issues by turning your infrastructure administration into a code base. Instead of performing individual tasks on a number of machines, these tools allow you to commit your requirements to a central location where each component can connect, pull down their configuration, and apply it.

Chef is one of the most popular configuration management tools. It uses Ruby and handles configuration by packing details into what it calls recipes.

Chef provides a way to quickly deploy entire environments instead of only single applications. In any situation where you would install a piece of software and then modify its configuration files, Chef can be used to automate this process.

Configuration Management System: Chef Terminology

Configuration Management System Configuration Management System Configuration Management System Configuration Management System Configuration Management SystemIt is important to understand the different components that make up Chef.

Chef Operating Infrastructure

The Chef system is defined by the roles that each machine or resource plays in the deployment process:

  • Chef Server: This is the central location that stores configuration recipes, cookbooks, and node and workstation definitions. It is the central machine that every other machine in the organization will use for deployment configuration.
  • Chef Nodes: Chef nodes are the deployment targets that are configured by Chef. Each node represents a separate, contained machine environment that can be on physical hardware or virtualized.

These operating system environments each contain a Chef client application that can communicate with the Chef Server.

  • Chef Workstations: Chef workstations are where Chef configuration details are created or edited. The configuration files are then pushed to the Chef server, where they will be available to deploy to any nodes.

The configuration of these different components allows you to have multiple workstations and nodes. Nodes can be configured as soon as they are online and connected to the server.

While the above outline gives the impression that these are separate entities, it is possible for one machine to fulfill two or all of these roles.

Server Details

The server is the central control point that is accessed by all of the other chef machines, whether as a client or a manager. It is basically a large repository or database of all of the configuration details.

It handles connections and permissions from nodes and workstations and organizes data so that it can easily be pulled by clients. The server can also include a web interface in order to manage or configure some details.

Node Details

As mentioned above, a node can be a physical or virtual machine. Its only requirements are that it has access to the network and can communicate with the chef server. The user running the chef software also needs to be able to install software and make system changes.

Each node communicates with the central server using an application called chef-client. This handles pulling data off of the server and executing the configuration steps necessary to get the node into its final state. The chef-client program and the chef server communicate through the use of RSA key-based authentication.

Chef-client uses a tool called ohai to get statistics about the node. These are used in order to set up certain configuration details and populate variables contained within the files.

Workstation Details

A workstation has the tools necessary to create and modify configuration details for any of the available nodes and can communicate with the chef server to make these available.

An important tool to manage chef on a workstation is called knife. Knife acts as a gateway in which you can configure anything that would be stored on the server. It can manage nodes and configurations and can generally be used to access the server in a “chef-specific” way. While it would be possible to log into the server with SSH and make changes to all of the data that it handles manually, this is not really adhering to the processes that chef implements.

Configurations and definitions that are created and modified on a workstation are committed to version control and then pushed to the server. The repository is called the chef-repo. It holds all of the data needed for the configuration of chef.

Chef Repo File Structure

Chef handles its configuration and dependency information on a workstation within a specified directory structure. It is important to understand this hierarchy in order to effectively create recipes and push changes.

As we mentioned above, the server configuration files should be kept in version control in repository referred to as the “chef-repo”. This is just a normal directory that contains the chef files.

In this directory, we can find a structure that looks like this:

  • certificates/: Contains the SSL certificates that can be associated with clients for authentication.
  • chefignore: Lists the files and directories within the structure that should not be included in the push to the server.
  • config/: Contains one of the two repository configuration files
    • rake.rb: Defines some variable declarations for creating SSL certificates and some general options.
  • cookbooks/: Contains the cookbooks that configure the infrastructure for your organization.
  • data_bags/: Contains various data bags for your configuration.

Data bags are protected sub-directories that contain sensitive configuration details. They are only accessible to those nodes that have matching SSL certificates and contain JSON formated files with configuration details.

  • environments/: Contains a top-level location to contain details for deploying the environment.

Every environment that diverges from the default environment must be defined in this directory.

  • Rakefile: This file defines the tasks that chef can perform in its configurations.
  • roles/: Contains files that define the roles that can be assigned to nodes.

Chef Cookbook File Structure

Within the cookbooks directory in the chef-repo, sub-directories define specific cookbooks for applications. Within each separate application configuration directory is a structure that defines how this service should be installed and what changes must be made to make it work correctly.

Within the application, you will find files and definitions that define how an application must be installed and configured.

The metadata.rb or metadata.json files contains metadata information about the service. This includes basic information like the name of the cookbook and the version, but it also is the place where the dependency information is stored. If this cookbook depends on other cookbooks to be installed, it can list them in this file and chef will install and configure them prior to the current cookbook.

The attributes directory contains attribute definitions that can be used to override or define settings for the nodes that will have this service.

The definitions directory contains files that declare resources. This means that you can group functionality together under one heading.

The files directory describes how chef should distribute files throughout the node on which this cookbook is deployed.

The recipes directory contains the “recipes” that define how the service should be configured. Recipes are generally small files that configure specific aspects of the larger system. If a cookbook used to install and configure a web server, a recipe may enable a module or set up a sane firewall default.

The templates directory is used to provide more complex configuration management. You can provide entire configuration files that contain embedded Ruby commands. The variables that are printed can be defined in other files.

Configure the Chef Server

We will be setting up version 12 of Chef in this guide. Configuration can be significantly different between versions, so ensure that you are operating within the same major version number as this guide for best results.

The Chef documentation tells us that your Chef server should have at least 4 cores and 4 GB of RAM. It should also have a 64-bit operating system.

The workstation and nodes have very few requirements. We will use Ubuntu on those as well for consistency.

Ensure that the Server is Accessible by Hostname

Once you are logged into the server you plan on installing the Chef server onto, the first task you need to perform is to ensure that the hostname of the server is a resolvable fully qualified domain name (FQDN) or IP address. You can check this by typing:

hostname -f

The result should be an address where the server can be reached. If this is not the case, you can set this to a domain name or IP address where the server can be reached by editing this file:

sudo nano /etc/hosts

The file will look similar to this: current_hostname current_hostname_alias localhost

. . .

Modify the top line to reflect the fully qualified domain name or the IP address, followed by a space and any alias you want to use for your host. Add a line beneath the two lines shown that has your server’s public IP address in the first column, and the information that you modified at the end of the line to the end. It should look something like this: fqdn_or_IP_address host_alias localhost
IP_address fqdn_or_IP_address host_alias

Save and close the file when you are finished. You can check that the value was set correctly by typing:

hostname -f

The result should be a value that you can use to reach your Chef server from anywhere in your infrastructure.

Download and Install the Chef 12 Server software

Next, we can go ahead and download the Chef 12 server software. You can find the package that must be installed by visiting the Chef site. Specifically, for an Ubuntu installation, you can follow this link.


Back on your server, change to your home directory. Use the wget command to download the package:

cd ~

Once the download is complete, install the package by typing:

sudo dpkg -i chef-server-core_*.deb

This will install the base Chef 12 system onto the server. If you have selected a server with less powerful hardware than the recommended amount, this step may fail.

Once the installation is complete, you must call the reconfigure command, which configures the components that make up the server to work together in your specific environment:

sudo chef-server-ctl reconfigure

Create an Admin User and Organization

Next, we need to create an admin user. This will be the username that will have access to make changes to the infrastructure components in the organization we will be creating.

We can do this using the user-create subcommand of the chef-server-ctl command. The command requires a number of fields to be passed in during the creation process. The general syntax is:


We will include this information, and will also add -f, an additional flag, onto the end in order to specify a filename in which to output our new user’s private RSA key. We will need this in order to authenticate using the knife management command later.

For our example, we will create a user with the following information:

  • Username: admin
  • First Name: admin
  • Last Name: admin
  • Email:
  • Password: n0wherepass
  • Filename: admin.pem

The command needed to create a user with this information is (you should change this to reflect your information, especially the password):

sudo chef-server-ctl user-create admin admin admin n0wherepass -f admin.pem

You should now have a private key called admin.pem in your current directory.

Now that you have a user, you can create an organization with the org-create subcommand. An organization is simply a grouping of infrastructure and configuration within Chef. The command has the following general syntax:

chef-server-ctl org-create SHORTNAME LONGNAME --association_user USERNAME

The short name is the name that you will use to refer to the organization from within Chef. The long name is the actual name of the organization. The --association_user specifies the username that has access to administer the organization. Again, we will add the -f flag so that we can specify the name of the file to place the private key. The key that will be created is used to validate new clients as part of the organization until they can get their own unique client key.

We will create an organization with the following qualities:

  • Short Name: cyberpunk
  • Long Name: N0Where, Inc.
  • Association User: admin
  • Filename: cyberpunk-validator.pem

To create an organization with the above qualities, we will use the following command:

sudo chef-server-ctl org-create cyberpunk "N0Where, Inc." --association_user admin -f cyberpunk-validator.pem

Following this, you should have two .pem key files in your home directory. In our case, they will be called admin.pem andcyberpunk-validator.pem. We will need to connect to this server and download these keys to our workstation momentarily. For now though, our Chef server installation is complete.

Configure a Chef Workstation

Now that our Chef server is up and running, our next course of action is to configure a workstation. The actual infrastructure coordination and configuration does not take place on the Chef server. This work is done on a workstation which then uploads the data to the server to influence the Chef environment.

Clone the Chef Repo

The Chef configuration for your infrastructure is maintained in a hierarchical file structure known collectively as a Chef repo. The general structure of this can be found in a GitHub repository provided by the Chef team. We will use git to clone this repo onto our workstation to work as a basis for our infrastructure’s Chef repository.

First, we need to install git through the apt packaging tools. Update your packaging index and install the tool by typing:

sudo apt-get update
sudo apt-get install git

Once you have git installed, you can clone the Chef repository onto your machine. For this guide, we will simply clone it to our home directory:

cd ~
git clone

This will pull down the basic Chef repo structure into a directory called chef-repo in your home directory.

Putting your Chef Repo Under Version Control

The configurations authored within the Chef repo itself are best managed within a version control system in the same way that you would manage code. Since we cloned the repo above, a git repo has already been initialized.

To set your workstation up for new commits, you should do a few things.

First, set the name and email that git will use to tag any commits you make. This is a requirement for git to accept commits. We set this globally so that any git repo we create will use these values:

git config --global "Your Name"
git config --global ""

Next, we will tell git to ignore any information contained within the ~/chef-repo/.chef directory. We will create this directory in a few minutes to store some sensitive information. For now, we can add this location to our .gitignore file so that git does not store data that should not be exposed to other people:

echo ".chef" >> ~/chef-repo/.gitignore

Since we have made a change to the .gitignore file, we can go ahead and make our first new commit to the version control system. First, add all of the modified files to the current staging area:

cd ~/chef-repo
git add .

Now, commit the changes. We will use the -m flag to specify an in-line commit message describing the changes we are making:

git commit -m "Excluding the ./.chef directory from version control"

Our Chef repo is now under version control. As we author configurations for our infrastructure, we can use the above two commands to keep our git repo up-to-date.

Download and Install the Chef Development Kit

Next, we need to install the Chef Development Kit, a suite of software designed for Chef workstations. This includes many utilities that will be useful when designing configurations for your infrastructure. The tool we are interested in at this point is the bundled knifecommand, which can communicate with and control both the Chef server and any Chef clients.

We can find the Chef 12 Development Kit on the Chef website


Back on your workstation, change to your home directory. Again, use the wget command to download the package.

cd ~

Once the .deb package has been downloaded, you can install it by typing:

sudo dpkg -i chefdk_*.deb

After the installation, you can verify that all of the components are available in their expected location through the new chefcommand:

chef verify

If your workstation will primarily be used to manage Chef for your infrastructure, you will likely want to default to the version of Ruby installed with Chef. You can do this by modifying your .bash_profile so that Chef’s Ruby takes precedence:

echo 'eval "$(chef shell-init bash)"' >> ~/.bash_profile

Afterwards, you can source your .bash_profile file to set the correct environmental variables for the current session:

source ~/.bash_profile

If you wish to manage your Ruby versions independently, you can skip the above steps.

Download the Authentication Keys to the Workstation

At this point, your workstation has all of the software needed to interact with a Chef server and compose infrastructure configurations. However, it is not yet configured to interact with your Chef server and your environment.

We will use the scp utility to download the user key and the organization validator key that we created on the Chef server. Before do so, we will create a hidden directory where we will store these files:

mkdir ~/chef-repo/.chef

The method that you use to connect to the Chef server will determine how exactly we go about downloading the keys. Follow the method below that matches your setup:


If you connect to your Chef server through SSH using password-based authentication, the scp command will work without significant modification.

On your workstation, specify the username and domain name or IP address used to connect to the Chef server. Follow this immediately with a colon (:) and the path to the file you wish to download. After adding a space, indicate the directory on the local computer where you wish the download the files to be placed (~/chef-repo/.chef in our case).

If you log into the Chef server using the root user account, your commands will look something like this. Remember to change both the domain name or IP address and the name of the key files you are trying to download to match your environment:

scp root@server_domain_or_IP:/root/admin.pem ~/chef-repo/.chef
scp root@server_domain_or_IP:/root/cyberpunk-validator.pem ~/chef-repo/.chef

If you connect to your Chef server using a non-root user, the commands will look more like this:

scp username@server_domain_or_IP:/home/username/admin.pem ~/chef-repo/.chef
scp username@server_domain_or_IP:/home/username/cyberpunk-validator.pem ~/chef-repo/.chef


If, instead, you connect to your Chef server using SSH keys, you will need to perform some additional steps.

You will need to add the SSH keys you use to connect to the Chef server to an SSH agent. OpenSSH, the standard SSH suite, includes an SSH agent that can be started by typing:

eval $(ssh-agent)

You should see output that looks like this (the number will likely be different):

Agent pid 13881

Once the agent is started, you can add your SSH key to it:

Identity added: /home/demo/.ssh/id_rsa (rsa w/o comment)

This will keep your SSH key stored in memory. Now, you can forward the stored key to your workstation as you connect by using the -Aoption with ssh. This will allow you to connect to any computer from your workstation as if you were connecting from your local computer:

ssh -A username@workstation_domain_or_IP

Now, you can connect to your Chef server without needing a password using the forwarded SSH credentials. If the keys on your Chef server were available through the root user, the commands you will need will look similar to this. Remember to change the Chef server domain name or IP address and the key names as needed:

scp root@server_domain_or_IP:/root/admin.pem ~/chef-repo/.chef
scp root@server_domain_or_IP:/root/cyberpunk-validator.pem ~/chef-repo/.chef

If the SSH key configured for the Chef server instead is used to authenticate you to a regular user account, your commands will look like this instead:

scp username@server_domain_or_IP:/home/username/admin.pem ~/chef-repo/.chef
scp username@server_domain_or_IP:/home/username/cyberpunk-validator.pem ~/chef-repo/.chef

Configuring Knife to Manage your Chef Environment

Now that you have your Chef credentials available on your workstation, we can configure the knife command with the information it needs to connect to and control your Chef infrastructure. This is done through a knife.rb file that we will place in the ~/chef-repo/.chef directory along with our keys.

Open up a file called knife.rb in that directory in your text editor:

nano ~/chef-repo/.chef/knife.rb

In this file, paste the following information:

current_dir = File.dirname(__FILE__)
log_level                :info
log_location             STDOUT
node_name                "name_for_workstation"
client_key               "#{current_dir}/name_of_user_key"
validation_client_name   "organization_validator_name"
validation_key           "#{current_dir}/organization_validator_key"
chef_server_url          "https://server_domain_or_IP/organizations/organization_name"
syntax_check_cache_path  "#{ENV['HOME']}/.chef/syntaxcache"
cookbook_path            ["#{current_dir}/../cookbooks"]

The following items should be adjusted to suit your infrastructure:

  • node_name: This specifies the name that knife will use to connect to your Chef server. This should match your user name.
  • client_key: This should be the name and path to the user key that you copied over from the Chef server. We can use the #{current_dir} snippet to fill in the path if the key is in the same directory as the knife.rb file.
  • validation_client_name: This is the name of the validation client that knife will use to bootstrap new nodes. This will take the form of your organization short name, followed by -validator.
  • validation_key: Like the client_key, this includes the name and path to the validation key you copied from the Chef server. Again, you can use the #{current_dir} Ruby snippet to specify the current directory if the validation key is in the same directory as the knife.rb file.
  • chef_server_url: This is the URL where the Chef server can be reached. It should begin withhttps://, followed by your Chef server’s domain name or IP address. Afterwards, the path to your organization should be specified by appending /organizations/your_organization_name.

For our guide, the knife.rb file will look similar to this. You still need to adjust the server’s domain name or IP address if you are following along:

current_dir = File.dirname(__FILE__)
log_level                :info
log_location             STDOUT
node_name                "admin"
client_key               "#{current_dir}/admin.pem"
validation_client_name   "cyberpunk-validator"
validation_key           "#{current_dir}/cyberpunk-validator.pem"
chef_server_url          "https://server_domain_or_IP/organizations/cyberpunk"
syntax_check_cache_path  "#{ENV['HOME']}/.chef/syntaxcache"
cookbook_path            ["#{current_dir}/../cookbooks"]

When you are finished, save and close the knife.rb file.

Now, we will test the configuration file by trying out a simple knife command. We need to be in our~/chef-repo directory for our configuration file to be read correctly:

cd ~/chef-repo
knife client list

This first attempt should fail with an error that looks like this:

ERROR: SSL Validation failure connecting to host: server_domain_or_IP - SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed
ERROR: Could not establish a secure connection to the server.
Use `knife ssl check` to troubleshoot your SSL configuration.
If your Chef Server uses a self-signed certificate, you can use
`knife ssl fetch` to make knife trust the server's certificates.

Original Exception: OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed

This occurs because we do not have our Chef server’s SSL certificate on our workstation. We can acquire this by typing:

knife ssl fetch

This should add the Chef server’s certificate file to a list in our ~/chef-repo/.chef directory:

WARNING: Certificates from server_domain_or_IP will be fetched and placed in your trusted_cert
directory (/home/demo/chef-repo/.chef/trusted_certs).

Knife has no means to verify these are the correct certificates. You should
verify the authenticity of these certificates after downloading.

Adding certificate for server_domain_or_IP in /home/demo/chef-repo/.chef/trusted_certs/server_domain_or_IP.crt

After the SSL certificate has been fetched, the previous command should now work:

knife client list

If the above command correctly returns, your workstation is now set up to control your Chef environment.

Bootstrapping a New Node with Knife

With our Chef server and workstation configured, we can begin using Chef to configure new servers within our infrastructure.

This happens through a process called “bootstrapping” in which the Chef client executable is installed on the new computer and the organizational validator key is passed along as well. The new node then contacts the Chef server with the validator key and, in return, receives its own unique client key and any configuration that has been assigned to it. This process gets the new server into its initial state and sets it up for any future management.

To connect to the new server, we will need a few pieces of information about the new node:

  • The domain name or IP address where it can be reached
  • The username used to complete administrative actions. This can be either root, or a user configured with sudoprivileges.
  • A method of logging in as the above user. This can be either the password, or the ability to use an SSH key.
  • A method of performing administrative tasks. For root users, this is unnecessary. For users relying on sudoprivileges, a password is generally necessary.

The general syntax of the command will be:

knife bootstrap node_domain_or_IP [options]

Some common options you may end up using are:

  • -x: Used to specify the username to authenticate with through SSH. This is usually required.
  • -N: The new name for the node, as displayed within Chef. Leaving this out will usually result in the hostname being used for the Chef node name.
  • -P: Used to specify the password for the username on the remote server. This is necessary if eitherthe SSH session requires password authentication or if the username requires a password for sudocommands.
  • --sudo: If the username on the remote server will need to use sudo to perform administrative actions, this flag is needed. By default, it will prompt for the sudo password.
  • --use-sudo-password: If you are already providing the password for the user with the -P flag, using this flag in addition to the --sudo flag will use the -P password without prompting.
  • -A: This option forwards SSH keys to the remote host to login rather than using password authentication.

When using the -A option, you must start an SSH agent on your local computer, add the SSH key that can be used to connect to the new node, and forward that information to your workstation by connecting with the -A flag initially.

Using the above information, it is possible to construct the correct bootstrapping commands for a variety of situations.

For example, to bootstrap a node with the name “testing”, using the username demo, which is configured with sudo privileges, and which needs a password for SSH and the sudo validation, we can type:

knife bootstrap node_domain_or_IP -N testing -x demo -P password --sudo --use-sudo-password

If we want to bootstrap using the root user, with SSH key authentication using keys available on the workstation, and wish to keep use the node’s hostname as the Chef node name, we can type:

knife bootstrap node_domain_or_IP -x root -A

If we want to use SSH keys to authenticate to a sudo user, we will still need to provide a password using the -P flag, the --sudoflag, and the --use-sudo-password flag to avoid prompts:

knife bootstrap node_domain_or_IP -x demo -A -P password --sudo --use-sudo-password -N name

If you are in the above scenario, but do not mind being promted for the sudo password, you can instead just type this:

knife bootstrap node_domain_or_IP -x demo -A --sudo -N name

Once your new node is bootstrapped, you should have a new client:

knife client list

You should also have a new node of the same name:

knife node list

You can use the above procedure to easily set up new Chef clients on any number of new servers.

Basic Cookbook Concepts

Cookbooks serve as the fundamental unit of configuration and policy details that Chef uses to bring a node into a specific state. This just means that Chef uses cookbooks to perform work and make sure things are as they should be on the node.

Cookbooks are usually used to handle one specific service, application, or functionality. For instance, a cookbook can be created to use NTP to set and sync the node’s time with a specific server. It may install and configure a database application. Cookbooks are basically packages for infrastructure choices.

Cookbooks are created on the workstation and then uploaded to a Chef server. From there, recipes and policies described within the cookbook can be assigned to nodes as part of the node’s “run-list”. A run-list is a sequential list of recipes and roles that are run on a node by chef-client in order to bring the node into compliance with the policy you set for it.

In this way, the configuration details that you write in your cookbook are applied to the nodes you want to adhere to the scenario described in the cookbook.

Cookbooks are organized in a directory structure that is completely self-contained. There are many different directories and files that are used for different purposes. Let’s go over some of the more important ones now.


A recipe is the main workhorse of the cookbook. A cookbook can contain more than one recipe, or depend on outside recipes. Recipes are used to declare the state of different resources.

Chef resources describe a part of the system and its desired state. For instance, a resource could say “the package x should be installed”. Another resource may say “the x service should be running”.

A recipe is a list related resources that tell Chef how the system should look if it implements the recipe. When Chef runs the recipe, it checks each resource for compliance to the declared state. If the system matches, it moves on to the next resource, otherwise, it attempts to move the resource into the given state.

Resources can be of many different types. You can learn about the different resource types here. Some common ones are:

  • package: Used to manage packages on a node
  • service: Used to manage services on a node
  • user: Manage users on the node
  • group: Manage groups
  • template: Manage files with embedded ruby templates
  • cookbook_file: Transfer files from the files subdirectory in the cookbook to a location on the node
  • file: Manage contents of a file on node
  • directory: Manage directories on node
  • execute: Execute a command on the node
  • cron: Edit an existing cron file on the node


Attributes in Chef are basically settings. Think of them as simple key-value pairs for anything you might want to use in your cookbook.

There are several different kinds of attributes that can be applied, each with a different level of precedence over the final settings that a node operates under. At the cookbook level, we generally define the default attributes of the service or system we are configuring. These can be overridden later by more specific values for a specific node.

When creating a cookbook, we can set attributes for our service in the attributes subdirectory of our cookbook. We can then reference these values in other parts of our cookbook.


The files subdirectory within the cookbook contains any static files that we will be placing on the nodes that use the cookbook.

For instance, any simple configuration files that we are not likely to modify can be placed, in their entirety, in the files subdirectory. A recipe can then declare a resource that moves the files from that directory into their final location on the node.


Templates are similar to files, but they are not static. Template files end with the .erb extension, meaning that they contain embedded Ruby.

These are mainly used to substitute attribute values into the file to create the final file version that will be placed on the node.

For example, if we have an attribute that defines the default port for a service, the template file can call to insert the attribute at the point in the file where the port is declared. Using this technique, you can easily create configuration files, while keeping the actual variables that you wish to change elsewhere.


The metadata.rb file is used, not surprisingly, to manage the metadata about a package. This includes things like the name of the package, a description, etc.

It also includes things like dependency information, where you can specify which cookbooks this cookbook needs to operate. This will allow the Chef server to build the run-list for the nodes correctly and ensure that all of the pieces are transfered correctly.

Create a Simple Cookbook

To demonstrate some of the work flow involved in working with cookbooks, we will create a cookbook of our own. This will be a very simple cookbook that installs and configures the Nginx web server on our node.

To begin, we need to go to our ~/chef-repo directory on our workstation:

cd ~/chef-repo

Once there, we can create a cookbook by using knife. Knife is a tool used to configure most interactions with the Chef system. We can use it to perform work on our workstation and also to connect with the Chef server or individual nodes.

The general syntax for creating a cookbook is:

knife cookbook create cookbook_name

Since our cookbook will deal with installing and configuring Nginx, we will name our cookbook appropriately:

knife cookbook create nginx
** Creating cookbook nginx
** Creating README for cookbook: nginx
** Creating CHANGELOG for cookbook: nginx
** Creating metadata for cookbook: nginx

What knife does here is builds a simple structure within our cookbooks directory for our new cookbook. We can see our cookbook structure by navigating into the cookbooks directory, and into the directory with the cookbook name.

cd cookbooks/nginx
attributes  definitions  files  libraries  metadata.rb  providers  recipes  resources  templates

As you can see, this has created a folder and file structure that we can use to build our cookbook. Let’s begin with the biggest chunk of the configuration, the recipe.

Create a Simple Recipe

If we go into the recipes subdirectory, we can see that there is already a file called default.rb inside:

cd recipes

This is the recipe that will be run if you reference the “nginx” recipe. This is where we will be adding our code.

Open the file with your text editor:

nano default.rb
# Cookbook Name:: nginx
# Recipe:: default
# Copyright 2014, YOUR_COMPANY_NAME
# All rights reserved - Do Not Redistribute

The only thing that is in this file currently is a comment header.

We can begin by planning the things that need to happen for our Nginx web server to get up and running the way that we want it to. We do this by configuring “resources”. Resources do not describe how to do something; they simply describe what a part of the system should look like when it is complete.

First of all, we obviously need to make sure the software is installed. We can do this by creating a “package” resource first.

package 'nginx' do
  action :install

This little piece of code defines a package resource for Nginx. The first line begins with the type of resource (package) and the name of the resource (‘nginx’). The rest is a group of actions and parameters that declare what we want to happen with the resource.

In this resource, we see action :install. This line tells Chef that the resource we are describing should be installed. The node that runs this recipe will check that Nginx is installed. If it is, it will check that off the list of things to do. If not, it will install the program using the methods available on the client system and then check it off.

After we install the service, we probably want to adjust its current state on the node. By default, Ubuntu does not start Nginx after installation, so we will want to change that:

service 'nginx' do
  action [ :enable, :start ]

Here, we see a resource of the “service” type. This declares that for the Nginx service component (the part that allows us to manage the server with init or upstart), we want to start the service right now, and also enable it to start automatically when the machine is restarted.

The final resource we will be declaring is the actual file that we will be serving. Since this is just a simple file that we will not be modifying, we can simply declare the location where we want the file and tell it where in the cookbook to get the file:

cookbook_file "/usr/share/nginx/www/index.html" do
  source "index.html"
  mode "0644"

We use the “cookbook_file” resource type to tell Chef that this file is available within the cookbook itself and can be transfered as-is to the location. In our example, we are transferring a file into Nginx’s document root.

In our case, we specify the file name that we are trying to create in the first line. In the “source” line, we tell it the name of the file to look for within the cookbook. Chef looks for this file within the “files/default” subdirectory in the cookbook.

The “mode” line sets the permissions on the file we are creating. In this case, we are allowing the root user read and write permissions and everyone else read permissions.

Save and close this file when you are finished.

Creating the Index file

As you saw above, we defined a “cookbook_file” resource which should move a file called “index.html” into the document root on the node. We need to create this file.

We should put this file in the “files/default” subdirectory of our cookbook. Go there now by typing:

cd ~/chef-repo/cookbooks/nginx/files/default

Inside this directory, we will create the file we referenced:

nano index.html

This file will just be a really simple HTML document meant to demonstrate that our resources have operated the way we wanted them to.

Paste this into the file:

    <title>Hello there</title>
    <h1>This is a test</h1>
    <p>Please work!</p>

Save and close the file when you are finished.

Create a Helper Cookbook

When our node tries to run the cookbook that we’ve created as it is now, chances are, it will fail.

That is because it will attempt to install Nginx from the Ubuntu repositories, and the package database on our node is most likely out-of-date. Usually, we run “sudo apt-get update” prior to running package commands.

To address this issue, we can create a simple cookbook whose only purpose is to ensure that the package database is updated.

We can do this using the same knife syntax we used before. Let’s call this cookbook “apt”:

knife cookbook create apt

This will create the same kind of directory structure that we had when we first started with our Nginx cookbook.

Let’s cut straight to the chase and edit the default recipe for our new cookbook.

nano ~/chef-repo/cookbooks/apt/recipes/default.rb

In this file, we will declare an “execute” resource. This is simply a way of defining a command that we want to run on the node.

Our resource looks like this:

execute "apt-get update" do
  command "apt-get update"

The first line gives a name for our resource. In our case, we are calling the resource this for simplicity’s sake. If the “command” attribute is defined (as we have done), then this is the actual command that is executed.

Since these are exactly the same, it does not matter in the slightest.

Save and close the file.

Now that we have our new cookbook, there are a number of ways that we can make sure that we execute this before our Nginx cookbook. We could add it to the node’s run-list before the Nginx cookbook, but we can also tie it into the Nginx cookbook itself.

This is probably the better option because we will not have to remember to add the “apt” cookbook before the “nginx” cookbook on every node we want to configure for Nginx.

We need to adjust a few things in the Nginx cookbook to make this happen. First, let’s open the Nginx recipe file again:

nano ~/chef-repo/cookbooks/nginx/recipes/default.rb

At the top of this cookbook, before the other resources that we have defined, we can read in the “apt” default recipe by typing:

include_recipe "apt"

package 'nginx' do
  action :install

service 'nginx' do
  action [ :enable, :start ]

cookbook_file "/usr/share/nginx/www/index.html" do
  source "index.html"
  mode "0644"

Save and close the file.

The other file that we need to edit is the metadata.rb file. This file is checked when the Chef server sends the run-list to the node, to see which other recipes should be added to the run-list.

Open the file now:

nano ~/chef-repo/cookbooks/nginx/metadata.rb

At the bottom of the file, you can add this line:

name             'nginx'
maintainer       'YOUR_COMPANY_NAME'
maintainer_email 'YOUR_EMAIL'
license          'All rights reserved'
description      'Installs/Configures nginx'
long_description, ''))
version          '0.1.0'

depends "apt"

With that finished, our Nginx cookbook now relies on our apt cookbook to take care of the package database update.

Add the Cookbook to your Node

Now that our basic cookbooks are complete, we can upload them to our chef server.

We can do that individually by typing:

knife cookbook upload apt
knife cookbook upload nginx

Or, we can upload everything by typing:

knife cookbook upload -a

Either way, our recipes will be uploaded to the Chef server.

Now, we can modify the run-list of our nodes. We can do this easily by typing:

knife node edit name_of_node

If you need to find the name of your available nodes, you can type:

knife node list

For our purposes, when we type this, we get a file that looks like this:

knife node edit client1
  "name": "client1",
  "chef_environment": "_default",
  "normal": {
    "tags": [

  "run_list": [


You may need to set your EDITOR environmental variable before this works. You can do this by typing:

export EDITOR=name_of_editor

As you can see, this is a simple JSON document that describes some aspects of our node. We can see a “run_list” array, which is currently empty.

We can add our Nginx cookbook to that array using the format:


When we are finished, our file should look like this:

  "name": "client1",
  "chef_environment": "_default",
  "normal": {
    "tags": [

  "run_list": [

Save and close the file to implement the new settings.

Now, we can SSH into our node and run the Chef client software. This will cause the client to check into the Chef server. Once it does this, it will see the new run-list that has been assigned it.

SSH into your node and then run this:

sudo chef-client
Starting Chef Client, version 11.8.2
resolving cookbooks for run list: ["nginx"]
Synchronizing Cookbooks:
  - apt
  - nginx
Compiling Cookbooks...
Converging 4 resources
Recipe: apt::default
  * execute[apt-get update] action run
    - execute apt-get update

Recipe: nginx::default
  * package[nginx] action install (up to date)
  * service[nginx] action enable
    - enable service service[nginx]

  * service[nginx] action start (up to date)
  * cookbook_file[/usr/share/nginx/www/index.html] action create (up to date)
Chef Client finished, 2 resources updated

As you can see, our apt cookbook was sent over and run as well, even though it wasn’t in the run-list we created. That is because Chef intelligently resolved dependencies and modified the actual run-list before executing it on the node.

Note: There are various methods of ensuring that one cookbook or recipe is run before another. Adding a dependency is only one choice, and other methods may be preferred.

We can verify that this works by going to our node’s IP address or domain name:


Leave a Reply

Please log in using one of these methods to post your comment: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s