Cfengine 3 Snippets Part 1: DenyHosts

I’ve recently begun looking into configuration management with cfengine 3. I’ve ignored this growing sub-field of system administration for too long and I just can’t ignore it anymore. After spending quite some time researching the philosophies, methods and different tools out there, I settled on starting out with cfengine 3. There’s no special reason that I chose cfengine instead of puppet, bcfg2, chef or AutomateIT. I haven’t used any of these tools and thus I cannot pass judgement on them or their methods. All these projects seem to have intelligent and highly motivated people behind them. I simply gravitated towards cfengine because of its strong academic background and the fact that version 3 now represents the most recent and modern research in the field by Mark Burgess et. al.

As part of my learning experience with cfengine, I’ve decided to start posting some of the code that I’ve begun developing in the hopes that by writing about it, I can learn better, faster and maybe even receive some helpful comments from readers along the way. Beware, I’m a cfengine newbie and so what I post here should NOT be copy and pasted into your environment unless you’re ok with the potential of wildly breaking things!

The first snippet of code I want to discuss is related to managing our DenyHosts configuration. As part of our “security policy”, I would like to ensure that every RedHat/CentOS system is running a properly configured DenyHosts instance. Here is what I’ve come up with so far.

################################################################################
#
# FILE: denyhosts.cf
# DESC: Install, update, configure and ensure DenyHosts is running
# DATE: May 2010
#
#################################################################################

bundle agent denyhosts
{

packages:

  "denyhosts" -> "Security policy"
    comment               => "Ensure denyhosts is installed once a week",
    package_policy        => "add",
    package_method        => yum,
    package_architectures => { "noarch" },
    action                => if_elapsed("10080");

  Night::

  "denyhosts" -> "Security policy"
    comment               => "Check for update to denyhosts every 24 hours (and only at night)",
    package_policy        => "update",
    package_method        => yum,
    package_architectures => { "noarch" },
    action                => if_elapsed("1440");

files:

  "/etc/denyhosts.conf" -> "Security policy"
    comment   => "Standard base DenyHosts configuration",
    copy_from => mycopy("$(g.confdir)/denyhosts/denyhosts.conf", "$(g.cfserver)"),
    classes   => cdefine("denyhosts_restart", "denyhosts_conf_copy_failed"),
    perms     => mo("400", "root"),
    action    => if_elapsed("1440");

processes:

  "python /usr/bin/denyhosts.py" -> "Security policy"
    comment       => "Define denyhosts_restart class if denyhost is NOT running",
    restart_class => canonify("denyhosts_restart");

commands:

  "/sbin/service denyhosts restart" -> "Security policy"
     comment    => "Restarting DenyHosts after configuration change or death",
     ifvarclass => canonify("denyhosts_restart");

}

If you’re familiar with cfengine at all, you’ll quickly realize this is not a complete configuration. I am relying on the cfengine standard library for several body definitions as well as custom site variables defined in the common bundle named “g” (not shown). And of course, there are no control bodies, bundlesequence or many other things that make up a complete cfengine configuration, hence “snippet”.

Let’s ignore what’s lacking for now and focus on the meat of the promises.

Packages

The first part of the denyhosts bundle is dealing with packages. I’m making two promises regarding the “denyhosts” package. The first promise is that the package has been added to the system via yum and the second is that the package is up to date via yum. I’m not entirely clear on how to best manage promises like this yet so perhaps I’m missing some cute shorthand for both adding and keeping packages up to date. For now, I’ll stick with two separate promises.

You’ll also notice that I’m only checking to see if the package is installed once a week (via action => if_elapsed) and only checking to see if the package is up to date once every 24 hours (if_elapsed, again). The update promise is also subject to the Night class to ensure that package updates only occur at night and not during the work day. This is just a matter of preference. I’d prefer if updates occur at night, you may not.

Since I’m using a “smart” package manager to ensure that denyhosts is installed, I can count on having yum resolve any dependencies (such as python) for me automatically. I would loath to describe every dependency for every package I want control over by hand.

Files

There is only one file that I’m concerned with when it comes to denyhosts and that is the denyhosts configuration file in /etc/denyhosts.conf. Instead of doing file edits on the default denyhosts.conf file provided in the denyhosts packages that I’ve promised to install, I simply copy a pre-defined configuration from my cfengine server. This file has our site’s default denyhosts configuration all ready to go. If I needed to customize the configuration on a per-host or per-host-type basis, I could copy the base file to a temporary location then perform edits on the temp file and write out the changes to the final location of the default configuration file or simply maintain several pre-configured versions of denyhosts.conf and copy the appropriate file.

Also of note about files is that if the file promise must be repaired (if the file must be copied because it’s changed), I’m setting a class to be defined so that DenyHosts can be restarted. More on that later.

Processes

In this case, I’m only checking to see if the DenyHosts python process is running or not. If DenyHosts is running, we do nothing. If DenyHosts is not running, we define a class using the same name as the class we define if we have to copy the configuration file.

Commands

Finally, in the commands section we tell cfengine how and when to restart DenyHosts. If the “denyhosts_restart” class is defined, we instruct cfengine to restart DenyHosts with the “/sbin/service denyhosts restart” command. The canonify and cdefine special functions in cfengine provide a very powerful way of defining some rather complex relationships.

What is Missing?

Well, probably a lot of stuff. One obvious thing is that I’m not promising that DenyHosts is set to startup at boot time using the hosts’ native init system. Of course, this shouldn’t be a big deal because cfengine will start it up if it’s not running at the next cf-agent run, but perhaps it would be nice to make that promise anyways.

I’m also not using many (or any!) classes to limit the scope of where (to what hosts) these promises will apply. Right now, I’m just working with a test environment so it’s easy to get away with that but I’m learning that it’s good to be as explicit at possible from the start when building promises.

UPDATE: Ah, how could I forget.. Reporting is totally missing! I knew I was setting some of those classes for a reason. In the next installment, I’ll include the most basic of reporting functionality.

I think that’s all for now. Please critique my amateur use of cfengine 3 in the comments, I want to hear from you!

Leave a Reply

Your email address will not be published. Required fields are marked *