Cfengine, developed by Mark Burgess at Oslo University College, is one of the most powerful system administration tools available today. In a useful deviation from most scripting tools, cfengine allows you to describe the desired state of a system rather than what you should do to a system. Cfengine itself takes care of testing compliance with that state and will do its best to correct any misconfigurations. It also includes powerful classing capabilities that allow you to group hosts into classes and create different states on each class of host. Like all tools, it has its drawbacks, but overall it should be considered the most important and most capable tool in the sysadmin toolbox today.
Cfengine configurations usually have great scope and much functionality, but
they all start somewhere. Generally, they start with simple yet important
aspects of system administration; my favorite launching point is
sudo, which is an important administration tool for selectively
allowing escalated privileges, usually allowing specific users to run specific
commands as the root user.
Hopefully, you're using
sudo to dole out superuser capabilities
on an as-needed basis. One of the negative aspects of relying on
sudo, however, is that if either the
sudo binary or
its config file have the incorrect permissions,
sudo will not
function correctly. This could mean you've lost the ability to maintain your
machine, possibly including the ability to fix
We'll use cfengine to make sure that
sudo is always SUID root
and only executable, that the
sudoers file is owned by the
root user and the
root group, and that it has read
permissions only for
You can think of a cfengine configuration as being a simple wrapper to many different scripts that are organized by different sections of a cfengine configuration.
A cfengine configuration is divided into functional areas called
actions. Cfengine has two types of actions:
functional actions that perform work, and
actions that control how cfengine functions. One of cfengine's simplest but
most useful actions is its
files action. This action is
responsible for verifying metadata on files, including permissions and
ownership. It also has tripwire-like functionality capable of warning you if
any files change. Let's start building a simple cfengine configuration using
this action to verify
sudo and its configuration file are set up
Start with the main configuration:
control: actionsequence = ( files )
The primary meta action of a cfengine configuration is the
control action. It's also the only required action. The minimum
content of this action is the definition of the
which determines which actions should execute and in what order. There's a lot
more to this section, but let's ignore it for now. It is important to note,
though, that there must be spaces around the parentheses in this definition.
That is largely the case with all cfengine variable definitions. This is one
of the few areas where cfengine is whitespace-sensitive.
Next let's create the portion that does the actual work:
files: /usr/local/bin/sudo owner=root group=root mode=4111 checksum=md5 action=fixall /etc/sudoers owner=root group=root mode=0440 action=fixall
This configuration just describes the metadata of
sudo and the
sudoers file, as you can see. There are three distinct areas of
each of these configurations: the filename (which must always come first on
the line starting a new file definition), the description of the tests to
perform, and the action. The tests and action can come in any order, but there
must always be an action and at least one test. Cfengine's
action has more than ten available tests, but the four we've used are the most
common. Notice also that cfengine essentially ignores whitespace; there are
probably some areas where you have to be careful, but the parser is generally
smart enough to tell when you are starting a new file description.
As to the action, there are nine different actions available, but they are
basically just a matrix of whether you want to fix problems or just warn about
them, and whether you want to operate on directories, files, or both.
fixall is sufficient. If you want to start using this
today, another important test is
recurse, which allows you to
specify how deeply into a directory cfengine should perform its tests, with
recurse=inf specifying infinite recursion (which is normally bad,
but assuming you don't have an infinite directory structure, it should work out
The tests we've used on
sudo and its configuration file are
slightly different. In both cases we're specifying the necessary permissions
and ownerships, and the specifics for them are obviously different. For
sudo itself, we've added
checksum=md5, so that
cfengine warn us if the binary changes at all. We have not done this for the
sudoers file because in our next article we're going to build a
cfengine configuration that distributes this file from a CVS repository.
Store this configuration somewhere relevant (I'll use
/tmp/sudo.cf for this example, because we're just going to change
it in the next article) and run:
# cfagent -vf /tmp/sudo.cf
I've added the
v (verbose) flag so you get some more
information. Obviously you'll need to run this as root, or it will not be able
to change the files. Less obviously, you should not use
run this command. Instead, launch a shell as root. Otherwise, if you've
configured cfengine to set the wrong permissions, you may make
sudo inoperable, which would be bad.
If you are running cfengine manually, you generally want to use the verbose flag. This is an especially enlightening capability when first beginning to use cfengine, because it does a good job of telling you what cfengine knows, giving you a path to correct any problems you might be having.
Now that we have a script to verify and correct the permissions for
sudo, we can distribute this script to all of our servers and run
it out of cron. This will essentially guarantee that
sudo is set
up correctly on all of our systems all of the time. It will also warn us if our
sudo binary changes for any reason.
However, distributing this file manually would be kind of a waste, as one of cfengine's biggest strengths is exactly in this area. In the next article, we will configure cfengine to pull its configuration from a central location and then execute this updated configuration. This saves you from having to distribute all your little cfengine scripts separately as well as from having a bunch of little cron jobs. In fact, cfengine originated specifically as a means of collecting a bunch of useful scripts into one tool with one common syntax.
Now that we've seen a simple example of running cfengine, we can delve a bit more deeply into the syntax of a cfengine configuration. As this series progresses, we will also create a structured configuration, one that is I hope will be easy to maintain and understand, but this structure is merely my recommended practice and is not something that cfengine requires.
The structural components of cfengine configurations are actions and classes. Actions are different functional areas within cfengine, each providing specific capabilities, and classes are effectively named booleans (variables that are either true or false) and are the primary mechanism for choosing which work to perform and where to perform it. (We won't use classes until the second article.)
These functional mainstays create a simple, common syntax that looks like this:
action: class:: [do stuff]
Whitespace does not (usually) matter in cfengine; actions are active until the next action is listed, and classes are active until the introduction of the next action or class.
The majority of cfengine actions are functional and perform specific types
of work, but there are some meta actions that deal with the operation of
cfengine itself. The most important of these is the
action; it configures of many of cfengine's features, it is the only required
action, it is the only place where you can set variables, and it is one of the
two actions that allows you to explicitly set classes. Two other actions for
configuration rather than function are
import, for importing other
cfengine configuration files (thus allowing you to split your configuration
into multiple files), and
groups (also known as
classes), which provides a different syntax for setting classes
and is sometimes much more efficient.
There are plenty of other details about the overall cfengine design, but rather than list them all here, we'll discover them through a series of progressively more sophisticated articles.
As mentioned earlier, cfengine focuses on describing the state a system should be in, rather than how to get to the correct state. All of cfengine's actions are idempotent, meaning that running them once is equivalent to running them multiple times. Another way of saying this is that cfengine actions will do something only if the current state is incorrect. For instance, if a file has incorrect permissions, then cfengine will fix the permissions, but if they are already correct, then cfengine will do nothing.
Because of the complex nature of changing the state of a system, cfengine relies on a process that Mark Burgess calls convergence. Rather than assuming that a single run of a cfengine script will result in a completely correct state, cfengine assumes that some changes will take multiple passes to enact. For this reason, every cfengine execution gets passed through twice. Usually, it's only the first pass that does any work, and on the second pass cfengine is smart enough not to repeat checks or actions immediately. However, there are plenty of cases where a changed state in the first pass results in an action taking place in the second pass, or one execution of cfengine changes the behavior of a later execution of cfengine (by generating an input file, for instance).
It's also worth noting here that cfengine has a built-in mechanism for making
sure configuration actions don't take place too frequently. This can
complicate your testing, but removing
/var/cfengine/cfengine_lock_db (which stores the locks) or running
cfagent with the
-K flag (which ignores the locks) will
When creating cfengine configuration files, keep in mind that you are always trying to describe how the system should look, you are not trying to describe how to make it so. It is a difficult transition from the normal practices of procedural programming to cfengine-style declarative programming, but it's one worth making.
We now have a basic introduction to cfengine, although short on
details, and we have a simple but useful script to guarantee that
sudo will always work as planned. In the next article in this
series, we will delve into cfengine's ability to copy files from a central
location. This will demonstrate cfengine's power to centralize your automation
functions and get you started automating with cfengine immediately.
Luke A. Kanies is an independent consultant and researcher specializing in Unix automation and configuration management.
Return to ONLamp.com.
Copyright © 2009 O'Reilly Media, Inc.