oreilly.comSafari Books Online.Conferences.


Distributed Cfengine
Pages: 1, 2, 3

Configuring the Distribution

OK, now we have the files and they're all version-controlled, and we have some confidence that our key exchange will work. It's time to configure the files for distribution.

Let's start with the server, since it's a bit easier.

# cfservd.conf
    # the name of our server is 'server'
    cfengine_server = ( server )

        # tcp_wrappers-like access control
        AllowConnectionsFrom = (

        TrustKeysFrom = (

    /var/cfengine/ppkeys/ *

        /cfengine    *

This is our first experience with cfengine's classes. You can think of them as Boolean (true or false values). Any incidence of a class in a configuration is essentially an if statement. This statement lasts until the action ends or until the introduction of another class.

Cfengine has many classes that it automatically sets, including the host name. This is why we're able to set the cfengine_server class based on our server name. Cfengine also provides a special action, groups, for setting classes. classes is an alias for that action, but since I usually use it to delineate groups of machines, I usually use the groups moniker.

We set the cfengine_server class only on our (wait for it...) cfengine server, creatively named server here. We could instead use the hostname throughout the configuration, but then it would be difficult to change servers, and as the configuration becomes more complex and servers take on multiple roles, it can become difficult to determine why a certain server has a certain trait. Using this class, it is always obvious the role of the server on which a rule operates.

There is not much more to it. We use our server class to trust and grant connectivity to a range of IP addresses — you can only trust or grant connectivity to IP addresses, not hostnames — and configure which files those clients can see. In addition to the main configuration tree, I've added an extra file, the cfengine public key, and have provided unrestricted access to it. We won't use that in this configuration, but it's a nice way of giving administrators the ability to collect a host's public key manually if there's a problem with the key exchange (which is common).

The AllowConnectionsFrom and TrustKeysFrom variables only work within cfservd. The admit action similarly only works within cfservd.

Commit these changes into CVS:

~/cvs/config/cfengine/inputs $ cvs commit cfservd.conf

Now, on to the client. There are two important tasks the client must perform before it can run normally. It must update its configuration, and it must make sure cfagent is capable of running. Both of those tasks take place within update.conf, which executes separately from the rest of the cfengine configuration. Let's deal with the functional aspects first. This configuration assumes that you have run cfkey to create the key pair and that cfagent is installed in /usr/local/sbin, which is the default.

# update.conf
	# the name of our server is 'server'
	cfengine_server = ( server )

    actionsequence = ( directories links )
    /var/cfengine/bin/cfagent -> /usr/local/sbin/cfagent

Cfengine was developed to operate well in an environment where machines automount binaries from a server. If you automount /usr/local, you may want to perform a copy instead of a link, so that cfengine will still work if the automount fails, but a link should suffice for most installations. Only cfexecd uses the link; it'd be nice to just configure cfexecd not to require it, but I don't know of a way to do so.

We could get more complicated if we wanted, but this is at least the minimum required to make sure cfengine works. Let's copy the configuration now:

# update.conf, take 2
    actionsequence = ( copy directories links )

    domain = ( ExecResult(/bin/domainname) )

    TrustKeysFrom = ( ) #
        SplayTime = ( 5 )

        workdir = ( /var/cfengine )
        configroot = ( /cfengine )
        server = ( )

    ${configroot}/config/cfengine    dest=${workdir}


    ${workdir}/bin/cfagent -> /usr/local/sbin/cfagent

This is where the configuration becomes a little confusing, because we've encountered two frustrating aspects of cfengine. There is no indication of whether we are dealing with a system variable (like domain) or a user-defined variable (like server), and some variables are case-sensitive (e.g., SplayTime) while others are not. Using the wrong case on a case-sensitive variable can be very confusing because you will receive neither a warning nor the behavior you expect.

Before we go through the new aspects of this configuration, we have to discuss the configuration of the domain variable. Cfengine relies heavily (a bit too heavily, sometimes) on the domain of the machines it runs on. It is absolutely imperative that both the cfengine client and server agree on the domain of the client. It doesn't matter if that agreement reflects reality, it only matters that both ends of the pipe agree. The client configures the domain through the domain variable and the server finds the domain by performing gethostbyaddr on the IP address of the client. As important as this variable is, though, be warned that cfengine almost always considers this the source of any problems related to trust, which can be confusing when the real problem is something like incorrect keys.

Thus, setting domain is our first task. If all of your hosts have the same domain, it's probably easier to set the domain via a static string, but if you use multiple subdomains, you need some means of retrieving the domain automatically. The example uses /bin/domainname, but you could just as easily pull the domain out of /etc/resolv.conf. This can result in a Catch-22 situation if you hope to use cfengine to manage domain configuration — you must have the domain set correctly to run cfengine, but you want to use cfengine to set the domain. The only answer I've found for that situation is to use a one-liner that attempts to collect a domain and sets a default if it fails.

The next variable we set is SplayTime. It is especially critical to set if you have many clients. This variable causes cfagent to sleep for a random amount of time up to a specified maximum; we set our SplayTime to 5, so our clients will sleep up to 5 minutes before contacting our server. This is a simplistic but usually sufficient form of load balancing; it should spread the client connections evenly over 5 minutes.

Note that only the clients have a SplayTime set; we want the server to run immediately, so it can update any necessary files and dole out the most recent versions when clients connect. Also, note the capitalization of SplayTime. Cfengine seems to be somewhat random in its case sensitivity, and many configuration parameters aren't case-sensitive. SplayTime is.

We also set some simple helper variables: the base directory of our local cfengine configuration, the base directory of the configuration on the server, and the name of our server. Then we define a simple copy statement. It's pretty self-explanatory, but we'll go through it just for clarity. Notice our use of the any class here; this is a special class that always matches, so it removes the effects of the previous class test.

Copy the Configuration

Like the files action, a copy statement needs a filename or directory. This filename is the source of the copy, usually on the remote server. You must at least specify a destination for the copy. Our example copies /cfengine/config/cfengine to /var/cfengine. Currently, the only directory in there is the inputs directory. /var/cfengine/inputs is the default location for the cfengine configuration; the cfengine binaries look in that directory for their respective configurations.

To tell cfengine to perform a remote copy, we specify the server to copy the files from. We further specify that cfengine should recursively copy the contents of the directory, so we'll have all of the subdirectories and their contents. Lastly we tell cfengine to ignore any files or directories named CVS, so as to avoid copying the CVS control directories. Although copying the CVS directories would not be a problem in this case, there are cases where it can be. Either way it's a waste of processing power and time, and you might eventually be ignoring enough CVS directories that it would make a difference.

Pages: 1, 2, 3

Next Pagearrow

Sponsored by: