Introducing Cfengine
by Luke A. Kanies04/15/2004
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.
The Very Beginning
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 sudo
itself!
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 root:root
.
Cfengine Philosophy and Terms
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 meta
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
correctly.
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 actionsequence
,
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 files
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.
Generally, 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
okay).
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.
Running the Script
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 sudo
to
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.
Overall Design
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 control
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.
Related Reading ![]() Unix Power Tools |
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.
The Basic Idea
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
help.
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.
Conclusion
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.
