A Primer on CFEngine 3.6 Autorun

Posted by Mahesh Kumar
October 7, 2014

CFEngine recently released version 3.6, which makes deploying and using cfengine easier than ever before. The greatest improvement in 3.6, in my opinion, is by far the autorun feature. I’m going to demonstrate how to get a policy server set up with autorun properly configured.

Installing CFEngine 3.6.2

The first step is to install the CFEngine package, which I’m not going to cover. But I will say that I recomend using an existing repository. Instructions on how to set this up are here. Or you can get binary packages here. If you’re not using Linux (like myself) you can get binary packages from cfengineers.net.If you’re inclined to build from source I expect that you don’t need my help with that. Having installed the CFEngine package, the first thing to do is to generate keys. The keys may have already been generated for you, but running the command again won’t harm anything.

/var/cfengine/bin/cf-key

Setting up Masterfiles and Enabling Autorun

Next you’ll need a copy of masterfiles. If you downloaded a binary community package from cfengine.com you’ll find a copy in /var/cfengine/share/CoreBase/masterfiles. As of 3.6 the policy files have been decoupled from the core source code distribution so if you’re getting CFEngine from somewhere else it may not come with CoreBase. In this case this you’ll want to get a copy of the masterfiles repository at the tip of the branch for your version of CFEngine (in this case, 3.6.2), not from the master branch where the main development happens. There’s already development going on for 3.7 in master so for consistency and repeatability grab an archive of 3.6.2. Going this route you also need a copy of the CFEngine core source code (although you do not need to build it).

curl -LC - -o masterfiles-3.6.2.tar.gz https://github.com/cfengine/masterfiles/archive/3.6.2.tar.gz
curl -LC - -o core-3.6.2.tar.gz https://github.com/cfengine/core/archive/3.6.2.tar.gz
tar zxf masterfiles-3.6.2.tar.gz
tar zxf core-3.6.2.tar.gz

You’ll now have the main masterfiles distribution unpacked. This isn’t something that you can just copy into place, you need to run make to install it.

cd masterfiles-3.6.2
./autogen.sh --with-core=../core-3.6.2
make install INSTALL=/opt/local/bin/install datadir=/var/cfengine/masterfiles

Note: Here I’ve included the path to install. This is required for SmartOS. For other systems you can probably just run make install. At this point it’s time to bootstrap the server to itself.

/var/cfengine/bin/cf-agent -B <host_ip_address>

You should get a message here saying that the host has been successfully bootstrapped and a report stating ‘I’m a policy hub.’ To enable autorun simplet make the following change in def.cf.

-      "services_autorun" expression => "!any";
+      "services_autorun" expression => "any";

Note: There’s a bug in masterfiles-3.6.0, so make sure to use at least 3.6.2.

Using Autorun

With the default configuration autorun will search for any files in services/autorun/ with the tag autorun and execute it. At this point you can see autorun working for yourself.

/var/cfengine/bin/cf-agent -K -f update.cf
/var/cfengine/bin/cf-agent -Kv

Here I’ve enabled verbose mode. You can in the verbose output that autorun is working. Now, like Han Solo, I’ve make a couple of special modifications myself. I also like to leave the default files in pristine condition, as much as possible. This helps when upgrading. This is why I’ve only made very few changes to the default polcies. It also means that instead of using services/autorun.cf I’ll create a new autorun entry point. This entry point is the only bundle executed by the default autorun. I’ve saved this to services/autorun/digitalelf.cf

body file control
{
  agent::
    inputs => { @(digitalelf_autorun.inputs) };
}

bundle agent digitalelf_autorun
{
    meta:
        "tags" slist => { "autorun" };

    vars:
        "inputs" slist => findfiles("$(sys.masterdir)/services/autorun/*.cf");
        "bundle" slist => bundlesmatching(".*", "digitalelf");

    methods:
        "$(bundle)"
            usebundle => "$(bundle)",
            ifvarclass => "$(bundle)";

    reports:
    inform_mode::
        "digitalelf autorun is executing";
        "$(this.bundle): found bundle $(bundle) with tag 'digitalelf'";
}

This works exactly the same as autorun.cf, except that it looks for bundles matching digitalelf and only runs them if the bundle name matches a defined class. Also note that enabling inform_mode (i.e., cf-agent -I) will report which bundles have been discovered for automatic execution. For example I have the following services/autorun/any.cf.

bundle agent any {

meta:

    # You must uncomment this line to enable autorun.
    "tags" slist => { "digitalelf" };

vars:

    linux::
        "local_bin_dir" string => "/usr/local/bin/";

    smartos::
        "local_bin_dir" string => "/opt/local/bin/";

files:

    "/etc/motd"
        edit_line => insert_lines("Note: This host is managed by CFEngine."),
        handle => "declare_cfengine_in_motd",
        comment => "Make sure people know this host is managed by cfengine";

}

Since the tag is digitalelf it will be picked up by services/autorun/digitalelf.cf and because bundle name is any, it will match the class any in the methods promise, and therefore run. You can drop in bundles that match any existing hard class and it will automatically run. Want all linux or all debian hosts to have a particular configuration? There’s a bundle for that.

Extending Autorun

You may already be familiar with my cfengine layout for dynamic bundlesequence and bundle layering. My existing dynamic bundlesequence is largely obsolete with autorun, but I still extensively use bundle stack layering. I’ve incorporated the classifications from bundle common classify directly into the classes: promises of services/autorun/digitalelf.cf. I can trigger bundles by discovered hard classes or with any user defined class created in bundle agent digitalelf_autorun. By using autorun bundles based on defined classes you can define classes from any source. Hostname (like I do), LDAP, DNS, from the filesystem, network API calls, etc. This article was submitted by Brian Bennett, a Vertical Sysadmin consultant and trainer