Wielding Chef for system automation in Linux

June 22, 2021

The last automation framework we will explore is Chef. Chef is a slightly more hands-on and development-oriented automation framework than the previous ones, but powerful nonetheless. It has commercial backing by the similarly named company Chef.

How Chef works

Chef has a slightly more extensive approach to automation and requires slightly more work to get up and running. Once set up, however, it offers a very flexible and programmable environment wherein infrastructure automation can be worked out.

There are three types of systems in the Chef architecture:

  • The Chef server acts as the central hub on which the automation code is maintained, and which interacts with the remote systems to apply the changes.
  • The Chef workstation is an endpoint on which administrators and engineers develop Chef recipes (code) and cookbooks and interact with the Chef server. There can be multiple Chef workstations per Chef environment.
  • The Chef client is an agent running on the remote systems (nodes) managed by the Chef environment.

Developers create automation code in recipes, which are like tasks. Multiple recipes are bundled in a cookbook and uploaded to the Chef server before the recipes can be applied to one or more nodes. Cookbooks can be compared with modules in the previous automation frameworks.

The Chef clients and server use public key-based authentication and encryption for their interactions. It is the client that takes the initiative, connecting to the server to download the latest cookbooks and other resources, after which it calculates and applies the latest changes, sending feedback on these changes back to the server.

Installing and configuring Chef

A full Chef installation requires a few components to be installed. The Chef workstation and the Chef server need to be installed by the administrator, whereas the Chef agents will be installed by Chef later.

Installing the Chef workstation

To install and use Chef, first download the Chef workstation. All Chef software can be downloaded from https://downloads.chef.io. For CentOS, the Chef workstation is available as an RPM, which can be installed using yum.

However, unlike common packaged software, the Chef workstation dependencies are not explicitly listed as RPM dependencies, causing the software to be installed without its necessary libraries. At the end of the installation, the RPM file will execute a post-installation script that checks the dependencies and reports on the missing libraries:

master ~# yum install chef-workstation-0.17.5-1.el7.x86_64.rpm

The dependencies, currently, require the following CentOS packages to be installed:

master ~# yum install libX11-xcb libXcomposite libXcursor libXdamage nss gdk-pixbuf2 gtk3 libXScrnSaver alsa-lib git

After the installation, run chef -v (as a regular, non-root user) to verify whether all dependencies are met:

master ~$ chef -v

The command should output the versions of the included Chef components.

Installing and configuring the Chef server core

The second installation is the Chef server core. This software is again made available as RPM:

  • Install the Chef server core using yum:
master ~# yum install chef-server-core-13.2.0-1.el7.x86_64.rpm

After the installation finishes, we need to configure it for our environment.

  • Create a directory named /var/opt/chef. We will use this directory to store the cryptographic keys to authenticate against the Chef server:
master ~# mkdir /var/opt/chef
  • Next, configure the Chef server using chef-server-ctl:
master ~# chef-server-ctl reconfigure

This will set up the Chef server on the current system. This setup can take a while to complete, but once finished, we can continue with creating a user account inside of Chef.

  • Let’s create an account called chefadmin for the user lisa on this system and give it a custom password:
master ~# chef-server-ctl user-create chefadmin Lisa McCarthy lisa@ppubssa3ed.internal.genfic.local pw4chef --filename /var/opt/chef/chefadmin.pem
  • Create an organization unit inside the Chef configuration, which we associate with the newly created user:
master ~# chef-server-ctl org-create ppubssa3ed "Packt Pub SSA 3rd Edition" --association_user chefadmin --filename /var/opt/chef/ppubssa3ed-validator.pem

With this done, the server administration itself is all done, and we can start creating our development environment.

Preparing the development environment

As mentioned earlier on, Chef is somewhat more development-oriented than the previous automation frameworks. The user that will interact with Chef (using the Chef workstation) needs to establish a development environment first:

  • We previously created an account called chefadmin for the user lisa. Now, log in as the user lisa and create a development environment in the user’s home directory:
master ~$ mkdir chef
master ~$ cd chef
master ~$ git init
  • We create a Git-enabled environment as the Chef utilities require it. If you have no active Git configuration yet, you might need to add your email and name:
master ~$ git config --global user.email "lisa@ppubssa3ed.internal.genfic.local"
master ~$ git config --global user.name "Lisa McCarthy"
  • Next, create the Chef knife configuration as .chef/knife.rb within this environment (so ~/chef/.chef/knife.rb in our example):
master ~$ mkdir .chef
master ~$ vim .chef/knife.rb
current_dir = File.dirname(__FILE__)
log_level		:info
log_location	STDOUT
node_name		"chefadmin"
client_key		"/var/opt/chef/chefadmin.pem"
chef_server_url	"https://ppubssa3ed/organizations/ppubssa3ed"
cookbook_path	["#{current_dir}/../cookbooks"]

This configuration references the key used previously as well as the organization we created. If the Chef workstation is a different system than the Chef server, don’t forget to copy over the key (chefadmin.pem in our example) and adjust the configuration accordingly.

  • Download the certificates that the Chef server uses (these certificates are self-signed certificates) and then check the SSL connection:
master ~$ knife ssl fetch
master ~$ knife ssl check
  • If the checks are successful, we can commit the changes:
master ~$ git add -A
master ~$ git commit -m 'Chef configuration baseline'

We are now ready to start our recipe and cookbook development.

Creating the SELinux cookbook

The cookbook we are going to develop will contain the various SELinux configuration entries, which are then assigned to the remote node:

  • Let’s start by creating a cookbook called packt_selinux:
master ~$ mkdir cookbooks
master ~$ cd cookbooks
master ~$ chef generate cookbook packt_selinux
master ~$ cd packt_selinux

This command creates the default files for the cookbook, of which we will handle metadata.rb and recipes/default.rb. The metadata.rb file contains information about the cookbook and, while it is not necessary for our example, it is sensible to edit and update this file immediately. Later, we will adjust this file to include dependency information toward other cookbooks.

  • The recipes/default.rb file contains the actual logic we want to apply to the remote systems. Let’s create a definition for the /usr/share/selinux/custom directory:
master ~$ vim recipes/default.rb
directory '/usr/share/selinux/custom' do
  owner 'root'
  group 'root'
  mode '0755'
  action :create
end
  • Now upload the cookbook to the Chef server:
master ~$ knife cookbook upload packt_selinux
  • We can query the available cookbooks on the Chef server with the list subcommand:
master ~$ knife cookbook list
packt_selinux   0.1.0
  • With the cookbook available, let’s bootstrap the target node. Bootstrapping only needs to occur once, but must be triggered from a Chef authenticated user:
master ~$ knife bootstrap rem1 --ssh-user root --node-name rem1
  • This ensures the Chef server knows the remote system. We can query the nodes using knife node list and get more details about a node with the show subcommand:
master ~$ knife node show rem1
  • Assign the packt_selinux recipe to the node using the run_list add subcommand:
master ~$ knife node run_list add rem1 'recipe[packt_selinux]'

Adding the recipe to the node list does not automatically trigger the requested update. For this, the remote node’s administrator needs to ensure that the chef-client binary executes either regularly (through a cron job or similar) or starts as a daemon.

  • For our purposes, we will trigger the chef-client command on the remote system to download and apply the latest changes:
remote ~# chef-client

The output of chef-client should show how it found and applied the changes listed in the recipe.

If this command returns successfully, then Chef is ready to manage the remote system using the cookbook we’ve developed.

Assigning SELinux contexts to filesystem resources with Chef

Chef has limited native support for SELinux contexts. When instructed to create or modify files on nodes, it will relabel those files according to the present file context definitions on the nodes. We can, however, subscribe to events defined in the recipe, and trigger appropriate actions when they occur. For instance, to explicitly set the context of a directory, we can create something like this:

execute 'set_selinux_custom_context' do
  command '/usr/bin/chcon -t usr_t /usr/share/selinux/custom'
  action :nothing
  subscribes :run, 'directory[/usr/share/selinux/custom]', :immediately
end

After adding this to the recipes/default.rb file, we first need to upload the updated cookbook to the server:

master ~$ knife cookbook upload packt_selinux

Afterward, we can rerun chef-client on the remote node to apply this updated recipe. If the directory was previously already created, the recipe will not change anything as the subscription will not be triggered.

Loading custom SELinux policies with Chef

Let’s update our recipe to include the logic to load a custom policy. We will use two blocks in our recipe, one to upload the test.cil file to the node, and another one to load it, but only if it was not loaded previously:

cookbook_file 'https://510848-1853064-raikfcquaxqncofqfm.stackpathdns.com/usr/share/selinux/custom/test.cil' do
  source 'test.cil'
  owner 'root'
  group 'root'
  mode '0755'
  action :create
end
bash 'load_test_cil' do
  code '/usr/sbin/semodule -i /usr/share/selinux/custom/test.cil'
  not_if '/usr/sbin/semodule -l | grep -q ^test$'
  only_if { ::File.exists?('https://510848-1853064-raikfcquaxqncofqfm.stackpathdns.com/usr/share/selinux/custom/test.cil') }
end

Put the test.cil file in a folder called files inside the packt_selinux cookbook directory, before uploading the updated cookbook and reapplying the changes using chef-client.

Using Chef’s out-of-the-box SELinux support

While Chef itself has limited out-of-the-box SELinux support, cookbooks are available online on Chef Supermarket (where the Chef community manages and distributes their custom cookbooks). Chef (the company) maintains the selinux cookbook itself, which allows managing the SELinux state of a system, whereas the selinux_policy cookbook addresses a few other SELinux settings.

Let’s download and install the selinux and selinux_policy cookbooks:

master ~$ knife supermarket install selinux_policy
master ~$ knife supermarket install selinux
master ~$ knife cookbook upload selinux_policy
master ~$ knife cookbook upload selinux

Next, adjust the metadata.rb file of our own cookbook to include the dependency to this newly added cookbook:

depends 'selinux_policy'
depends 'selinux'

We can now use some of the predefined recipes to handle SELinux configuration settings:

  • With selinux_state, we can place the system in an enforcing or permissive state:
selinux_state "SELinux enforcing" do
  action :enforcing
end
  • The selinux_policy_boolean recipe can configure an SELinux boolean value:
selinux_policy_boolean 'httpd_builtin_scripting' do
  value false
end
  • With selinux_policy_port, a custom SELinux port mapping can be defined:
selinux_policy_port '10122' do
  protocol 'tcp'
  secontext 'ssh_port_t'
end
  • A file context definition can be set using selinux_policy_fcontext:
selinux_policy_fcontext '/srv/web(/.*)?' do
  secontext 'httpd_sys_content_t'
end
  • An SELinux domain can be put in permissive mode using the  selinux_policy_permissive  recipe:
selinux_policy_permissive 'zoneminder_t' do
end

Don’t forget to upload the changed cookbook before calling chef-client on the remote systems.

Related Articles

No Results Found

The page you requested could not be found. Try refining your search, or use the navigation above to locate the post.

Lorem ipsum dolor sit amet consectetur

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

2 × 5 =