ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


Introducing Cfengine

by Luke A. Kanies
04/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
O'Reilly Open Source Convention.

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
By Shelley Powers, Jerry Peek, Tim O'Reilly, Mike Loukides

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.

Copyright © 2009 O'Reilly Media, Inc.