Distributed Cfengineby Luke A. Kanies
In this article we are going to take the script we wrote in Introducing Cfengine and distribute it to all of our servers using cfengine. As an added bonus, we're going to pull both our cfengine configuration and the
sudoers file directly out of a versioning system. It's a simple additional step — something you should do with all centralized configuration files — and provides a convenient control point for modifying and auditing your configurations.
The goal here is to allow us to commit changes to our
sudoers files in one place and have those changes automatically propagated to all of our servers. You check the file out into your CVS sandbox, make your changes, and commit them, and the system will handle publishing those changes. We'll modify and distribute our cfengine configuration via the same mechanisms, which will allow us to expand the cfengine configuration as necessary without worrying about how to distribute the enhanced configuration.
Copying Our Configuration
There are two sides to every distribution mechanism: the client and server. The
cfagent binary is the client side of cfengine and
cfservd functions as the server. Cfengine has multiple layers of security checks, which results in a somewhat onerous transport setup. Unfortunately (but not surprisingly, given the difficulty of doing so), cfengine also does not always provide relevant error codes when something goes wrong.
To distribute our configurations we have to configure both the client and the server. For cfengine to run automatically we have to configure
cfexecd, a simple wrapper for
cfagent that sends output to system administrators via email.
Before we start that, though, let's put all of our files into CVS, since it's easy and important. It's probably possible to centralize configurations without a version control system, but I certainly would not want to try it. Since most versioning systems were created for developers, we aren't likely to use all of their features, which makes this task even easier.
I use CVS here because it's a de facto standard and is easy to use; the example should work with little modification with any other versioning system. This article assumes a working knowledge of CVS and a functional CVS repository. If you do not have these, CVS Home can likely provide any necessary help.
Version-Controlling Your Configuration
We're going to start with the configuration for cfengine itself, then add
sudoers file once we have cfengine working. It's unlikely
that the examples here will mesh perfectly with an existing repository, so
expect to modify them if you are already versioning files. Given that, let's
jump right in.
Create a temporary directory for your cfengine files, and create three files in it:
~ $ mkdir /tmp/cfinit/inputs ~ $ cd /tmp/cfinit/inputs /tmp/cfinit/inputs $ touch cfagent.conf cfservd.conf update.conf /tmp/cfinit/inputs $ cp /tmp/sudo.cf .
Notice we've included the
sudo.cf configuration we created in
the previous article. We're creating these files in an
subdirectory; as our configuration grows more sophisticated, we'll add other
cfengine files, such as modules and data files, and we need to support them now
so we don't have to change our system later.
Import those files into an appropriate CVS module:
~ $ cd /tmp/cfinit /tmp/cfinit $ cvs import -m "new cfengine configuration" \ config/cfengine LAK yay
The last two arguments to
cvs import are generally not useful
to system administrators. I use my initials for the second argument and a
somewhat random term for the third argument; I have not yet used either of
these arguments for anything meaningful once I have imported something.
Finally, put our
sudoers file into CVS:
~ $ mkdir /tmp/sudo ~ $ cp /etc/sudoers /tmp/sudo ~ $ cd /tmp/sudo /tmp/sudo $ cvs import -m "distributing" config/sudo LAK init
We've put it into the same tree as cfengine, so it will be easy to update both with a single CVS command.
Now decide where you want to store those files so that
can distribute them. I generally pick a location appropriate for the given
site and then link it back to
/cfengine, which makes it easy to
move things around if necessary.
~ $ mkdir /export/cfengine ~ $ cd /export/cfengine /export/cfengine $ cvs checkout config /export/cfengine $ ln -s /export/cfengine /cfengine
We've now laid the groundwork for distribution, in that we have all of the files centralized in a versioning system and we have them checked out into a central location from which clients can retrieve them. Our versioning system functions as a kind of funnel here, with all changes passing from people's sandboxes through the CVS repository and into the checked out store on the server. This eliminates most problems with two people modifying the same file and provides auditing and historical functions.
Since the cfengine configuration files that we've checked into CVS are empty, we need to check them out in a CVS sandbox to allow us to edit them:
~ $ mkdir cvs ~ $ cd ~/cvs ~/cvs $ cvs checkout config
A Note About Keys
Cfengine uses public/private key pairs similar to SSH's key pairs. Unlike SSH, which usually only has a key pair for the server, cfengine requires that both sides of a connection trust the other side. This means that for a cfengine connection to work, each side of the connection must have the public key for the other side. Also unlike SSH, cfengine does not normally run interactively, which means that there's no prompt for whether you want to accept a public key from a new host. There are several ways to work around this problem, but they mostly come down to one of two solutions: copying public keys manually, or explicitly trust the IP addresses of the clients, servers, or both.
It is possible to use another cfengine tool,
cfrun, to retrieve
a public key for a host manually. This can be an acceptable method for some
cases, but it requires extra setup and, being an interactive utility, does not
solve the automation problem. Hopefully we will discuss
cfrun in a later article.
One important note about explicitly trusting an IP address is that it is only ever used on the first connection. Once you've retrieved the public key for a machine through a trusted connection, trust is never used again. This means that if a machine's keys change, you must manually update the keys; you cannot use trust to do so.
I prefer solving the problem of key management with a combination of trust
and manual intervention. Because my server's public key should never change, I
distribute that key with my cfengine package. Use
cfkey to create
the key pair. Once you've run it on your server, distribute
/var/cfengine/ppkeys/localhost.pub from your server to your
192.168.0.2 is your server's IP address.
Add this public key to your CVS repository:
~/cvs/config/cfengine $ mkdir ppkeys ~/cvs/config/cfengine $ cvs add ppkeys ~/cvs/config/cfengine $ sudo cp /var/cfengine/ppkeys/localhost.pub \ ppkeys/root-192.168.0.2.pub ~/cvs/config/cfengine $ sudo chown $LOGNAME ppkeys/root-* ~/cvs/config/cfengine $ cvs add ppkeys/root-*
This will distribute our server's public key with our configuration, and sets an example for distributing other servers' public keys. Ironically, this is a mostly pointless exercise because I can't pull this file down unless trust is already set up and I can't trust without this key. It makes me feel better, though, so I do it anyway.
Now that our client can verify the authenticity of our server, we just need
to tell our server about the client. We're going to use trust for this end of
the connection, because this is a simple example. This trust relationship is
configured using the
TrustKeysFrom directive in
cfservd.conf. In most real-world configurations, I collect the
client key as part of my cfengine bootstrap process. I hope to delineate this
process in a later article focusing on bootstrapping cfengine.