A Primer on CFEngine 3.6 Autorun
Update: For CFEngine 3.6.2.
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. Or for SmartOS try my repository here (IPv6 only). 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 gain 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";
reports:
inform_mode::
"Bundle $(this.bundle) is running via autorun.";
}
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. Again,
enabling inform_mode
(cf-agent -I
) will report that this bundle is in fact
being triggered.
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.