We will first start with some basic configuration of CFEngine, then we
will create a new branch in Git for the test environment, so we can do
all of our tests there, which if successful then we can bring to the
production environment. The test environment will be under the TEST
branch in Git, and our production environment configuration will in the
master branch of Git.

The basic configuration of CFEngine generally consists of these files.
Once we have that ready, later on we will add additional files with
promises, so we can extend CFEngine with more complex configuration.

promises.cf - The main CFEngine configuration file

update.cf - Contains promises for the agents, just to ensure that the
latest promises are updated on the clients.

failsafe.cf - This file is run by the agents if there are no
configuration files found. It is being used by the agents in order
to recover from a failure.

In the above configuration for promises.cf what we did is the following:

bundlesequence - this defines the bundlesequence to be executed by the agents.
Think of it as a list of actions, that the agents will perform when
cf-agent(8) is running.

inputs - defines a list of additional configuration files to be
included. This is where we will define our additional promises.

output_prefix - this sets the prefix you will see when running
cf-agent(8). The default value of this is community>

global vars - In the "g" bundle we define global variables,
that we can later refer to and use in other promises.

runagent body - defines a list of hosts/networks, that we can
connect to the running cf-serverd(8) and ask for executing of
cf-agent(8).

The bundlesequence as mentioned above defines a list of
promises (actions) that the agent will try to conform to, when running.

In the above configuration we define one global bundlesequence
that will be common to all clients.

This allows us to have a standard list of promises for agents, but also
allows us to define additional promises for a certain group of
clients if need to, as we will see in the example configurations in the
last chapter.

update.cf

The next file we need to setup is the update.cf one.

This file keeps the promises for updating the configuration on the
clients. The update promise usually needs to be the first one that
runs in order to ensure that the agents have the latest configuration.

One other thing to note about update.cf - this file generally does
not change at all, once configured properly.

It’s only purpose is to ensure that clients are having the latest
configuration files and nothing more. So once you have this
configured - you do not change it, at all.

In the above configuration for update.cf what we did is the following.

First we started with defining some local variables - u_workdir and
u_policyhost.

You might be wondering why we define the same variables, which we
already have in promises.cf, but with different names.

The reason for having these variables here as well is to make the
update.cf configuration self-contained, or in other words we do not
want update.cf to be dependent on other configuration files.

As mentioned earlier we generally do not want to do any changes to
this file, once we set it up correctly, since the only purpose of
this configuration is to properly update the policy files on the agents.

If we mess up the CFEngine configuration and distribute it to our
clients, what we want is that after fixing it, the update promise
will take care of properly updating the files on the clients as well.

We have then created a class called u_policy_servers which defines
our policy servers (CFEngine master servers).

We want to be able to know whether or not cf-agent(8) is running on
our clients or the master server itself.

With that being said, we do not update policy files on the master
server, but on the clients only. Having u_policy_servers class we
restrict the updating of policy files only to the clients.

Afterwards we defined promises for updating policy files and set of
permissions.

In the files section we have defined promises for updating the
policy files and also set proper permissions on files and
directories. The comments in each of them should be
pretty self-explanatory what the promise does.

In each of the promises, you will notice that we use the so
called promise bodies - this just represents a part of a promise,
which details and constrains it’s nature, and they are defined at the
end of the file - u_cf3_bin_files, u_workdir_perms, u_policy_copy,
etc.

Each of these promise bodies contains details about what they do.

failsafe.cf

This file is run by the agents when there are no
configuration files found.

Generally this file should contain promises for recovering from
failure, thus allowing our systems to self-heal.

In the above configuration we define the reports we want to have,
where to store them and what format to use for the reports.

classes.cf

We will use this file for user-defined CFEngine classes.

Classes in CFEngine refer to a specific context, e.g. we might have a
class called webservers which defines our web servers in the domain,
and later on we can make promises for that class, like for example
ensuring that the Apache configuration is installed, the server is
running, etc..

In the above configuration we start with very simple definition of
CFEngine classes, which we will later extend.

Here’s what has been done.

To the sysctl_jailed variable is assigned the result of the
command from execresult().

In classes we define two user-defined classes -
freebsd_jail and freebsd_host.

As mentioned earlier classes in CFEngine is a context.
User-defined classes are evaluated during run of cf-agent(8).

Depending on the value of sysctl_jailed we will have one of the
freebsd_jail or freebsd_host classes defined, depending whether
cf-agent(8) is running on a FreeBSD jail, or a host instead.

Using these classes later on we can have promises for our FreeBSD
jails and hosts, where for example we want to have one
configuration of a package installed on a host, and another one on a
FreeBSD jail.

We’ve defined the policy_servers class which will contain the
policy servers only.

cleanup.cf

In this file we will have configuration for tidying up old files and
directories that are no longer needed.

library.cf

It simply contains pieces of reusable code, that you can use in your promise bodies.

After installing the CFEngine package on your FreeBSD system this file
is installed in /usr/local/share/doc/cfengine3/inputs/cfengine_stdlib.cf,
so just simply copy it to your working tree and rename it as library.cf

And that was our basic CFEngine configuration. If you’ve followed the
handbook by this step, you should have a working basic
configuration of CFEngine.

Commiting the configuration to Git

The last thing to do is to push this configuration to the Git
repository, and then clone it from on the CFEngine master servers.

In the beginning of this handbook we’ve mentioned that we are going to
have two environments - a test one, and a production one.

Each time we want to bring something to production we are going to
deploy the changes to the test environment first, and when they are
verified to be OK - we bring the changes to the production
environment as well.

The test environment configuration will be under the test branch in
Git, and the configuration for our production environment will be
under the master branch in Git.

So, considering you have followed the handbook to this step,
let’s now add the configuration we’ve prepared to Git’s master
branch, which is our production environment configuration.

Remember that all of the above CFEngine configuration files we’ve
prepared already need to be under the inputs directory,
so considering that you are now in that directory, let’s add the
files to Git.

The above commands add the files we’ve created to Git,
commits them to the local Git repository, and then pushes them to the
remote Git repository in the master branch of Git.

Now we are going to create a new branch in Git for our test
environment and push the changes there as well,
so that we have our TEST environment configuration prepared also.

$ git checkout -b TEST

The changes that we need to do for the test environment are
just a few - we need to change the policy server hostname from
cfengine.example.org to cfengine-test.example.org and set the
proper IP address of the cfengine-test.example.org policy server.

Below are the files that you need to update for the test environment:

cf-execd.cf

cf-serverd.cf

classes.cf

failsafe.cf

promises.cf

update.cf

Once you are ready with the updates of the above configuration
files, let’s add them to the TEST branch of Git and push the
changes to the remote repository.

Creating the authentication keys

Similarly to OpenSSH,
CFEngine uses a client-server authentication using keys, which are
called ppkeys.

Each client needs to have the public key of the
CFEngine master server, and the server needs to have the
public key of each client.

To create the ppkeys on the policy servers, login to them and execute:

# cf-key
Making a key pair for cfengine, please wait, this could take a minute...

Along with the ppkeys of CFEngine the above command will also
create the needed directories of the CFEngine trusted work directory,
which were explained before.

Now let’s add the ppkeys of the policy servers to the
Git repository as well.

On our local machine where we do all our changes to the
CFEngine configuration using Git, we will now add the public
ppkeys of the CFEngine policy servers to their corresponding branches.

First, secure copy the /var/cfengine/ppkeys/localhost.pub files from
the policy servers to your local machine and rename them
to root-x.x.x.x.pub and root-y.y.y.y.pub, where x.x.x.x and
y.y.y.y are the IP addresses of the your policy servers -
the test and production one.

In CFEngine 2 the public ppkeys were in the form of
root-x.x.x.x.pub.

CFEngine now uses an MD5 hash instead of the IP address of the host,
but we can still use the legacy form since it allows us to easily
recognize a ppkey for a host only using it’s IP address.

When the CFEngine servers notices that the ppkeys are in the legacy
form, they will be automatically converted to the new form.

First time run of CFEngine!

Now it is time that we start up CFEngine, and get it to actually do
something for us.

Login to the policy servers and then let’s start up cf-agent(8):

$ sudo cf-agent -v

The above command will start cf-agent(8) in verbose mode,
and will also try to conform to the promises we’ve made before
in our configuration.

Inspect the output of the cf-agent(8) run for any errors or
misconfigurations.

A simple way to confirm that cf-agent(8) conforms to our
promises is to check after the agent run that the cf-serverd(8) and
cf-execd(8) processes were started by the agent,
since we made promise in our configuration that we want these
processes running on the policy servers.

Now that we have the basic configuration and setup, let’s add
clients to our configuration, which we’ll see how to do in the
next chapter of the handbook.