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
groups:
# the name of our server is 'server'
cfengine_server = ( server )
control:
cfengine_server::
# tcp_wrappers-like access control
AllowConnectionsFrom = (
192.168.0.0/24
)
TrustKeysFrom = (
192.168.0.0/24
)
admit:
/var/cfengine/ppkeys/localhost.pub *.domain.com
cfengine_server::
/cfengine *.domain.com
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
groups:
# the name of our server is 'server'
cfengine_server = ( server )
control:
actionsequence = ( directories links )
directories:
/var/cfengine/bin
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
control:
actionsequence = ( copy directories links )
domain = ( ExecResult(/bin/domainname) )
TrustKeysFrom = ( 192.168.0.2 ) # server.domain.com
!cfengine_server::
SplayTime = ( 5 )
any::
workdir = ( /var/cfengine )
configroot = ( /cfengine )
server = ( server.domain.com )
copy:
${configroot}/config/cfengine dest=${workdir}
recurse=inf
ignore=CVS
server=${server}
directories:
/var/cfengine/bin
links:
${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.



