Welcome! Feel free to email if you have questions or would like to get in touch.

You can also sign up to be notified of my free "Getting Started with CFEngine 3" webinars and other trainings.

Yours,
Aleksey Tsalolikhin
5 June 2016

www.verticalsysadmin.com

By Aleksey Tsalolikhin on 21 Dec 2009.

What is Cfengine?

Cfengine 3 is the original configuration management solution, but also the most newly refurbished of the Open Source configuration tools, and the only one based on actual research.

http://www.cfengine.org/

Let’s look at this more closely:

“Cfengine 3 is the original configuration management solution”

Table 1. Timeline of Release of Open Source Configuration Management Tools
CM Tool Released

Cfengine

1993

LCFG, DACS

1994

STAF, ISconf, PIKT

1998

Arusha Project

2001

Radmind

2002

OCS Inventory-NG

2003

Bcfg2, SmartFrog, Opsi

2004

Puppet, Quattor

2005

Chef

2009

“Cfengine is the most newly refurbished of the Open Source configuration tools”

Table 2. Reverse Timeline - Date of Most Recent Major Release Above 1.0
CM Tool Version Release Date

DACS

2.0

10-Jan-2009

Cfengine

3.0

1-Jan-2009

Opsi

3.0

2007

ISconf

4.0

2006

SmartFrog

3.0

2006? (v3.10 in 2006)

STAF

3.0

2005

LCFG

LCFGng

2002

So while Cfengine 3 was “the most newly refurbished Open Source configuration tool” when it was released, it is not any longer.

However the third release of Cfengine is significant, as, coupled with it’s long and continuing lifespan, it is an index to maturity.

Table 2 is shorter than Table 1 as some CM tools have not reached 2.0 yet.

Ongoing Development - Highlights

3.0.1 - First standalone release, independent of cfengine 2. Bug fixes in editfiles module. Additional change management options for change detection added. Package management added. Limits on backgrounding added to avoid resource contention during cfengine runs. Awareness of shifts: Morning, Afternoon, Evening, Night

3.0.2 - Enhancements and bug fixes in editfiles module Improvemens to language grammar to allow more abstraction and flexibility.

“Cfengine is the only configuration tool based on actual research.”

Mark Burgess (author of Cfengine) shared with Paul Anderson (author of LCFG) the 2003 SAGE Outstanding Achievement Award,

For professional contributions to the field of system administration through their ground-breaking work in system administration theory. As they practice it, system administration theory is a useful, emerging academic subdiscipline in which all four have been working for years.

http://www.sage.org/about/outstanding.html

However, there are no academic publications on the publications section on the LCFG web site, unlike Cfengine.org “Science” page.

Why I Chose Cfengine

Cfengine User Base

Table 3. Size of Cfengine Installations
Size of Site (Servers) Number of Such Sites

10’s

1294

100’s

344

1000’s

66

10000’s

8

Cfengine works equally well on a single laptop or 30,000 servers


— http://www.cfengine.com/pages/whatIsCfengine

The IT organization at AMD has been using Cfengine for over 5 years to administer enormous compute clusters in support of microprocessor development. We look forward to the new and innovative features in Cfengine Nova to further improve our operational efficiency.


— Paul Krizak, Senior Systems Engineer of Advanced Micro Devices (AMD)

What does Cfengine do and how does it do it?

Allow me to reference the diagram of Machine Life Cycle from Rémy Evard’s LISA 1997 “An Analysis of UNIX System Configuration”:

Machine Life Cycle

The primary principle of Cfengine is automatic convergence from Clean or Unknown states back to Configured.

Cfengine is idempotent in its operation, which means that “multiple applications of the operation do not change the result.” (Idempotence)

Therefore:

Cfengine(incorrect state) → correct state

Cfengine(correct state) → correct state

Cfengine runs regularly, bringing the system back to the correct state, or keeping it there.

Example

Making sure a crontab entry is present:

If it is, Cfengine does nothing. If it’s not, Cfengine adds it.

Result in either case: crontab entry is present.

How Does Cfengine Work?

Mostly Declarative Language

Cfengine has its own mostly declarative language. This describes how things should be; with a little bit of imperative (which describes how to do it, or what to do).

Cfengine’s declarative language is used to write configuration “policy rules” or “promises” of how the system should be configured.

How to get to the promised state is inherent or built-in to Cfengine.

Cfengine will be the “automatic system administrator” and make the changes necessary to get the system to “correct state”. For example, if a crontab is missing, it will add it.

The real system administrator’s job is to configure the “automatic system administrator” and monitor it’s function. The “automatic system administrator” can add a cron job or install a new software package, or remove a user from one host as easily as from tens of thousands of hosts.

It is a very powerful tool and should be used with care.

Cfengine Design: Resilience

Cfengine pulls down its config from a policy server, or if the policy server is unavailable or has not been configured, from local cache at /var/cfengine/inputs.

In other words, it can run standalone, as as part of a distributed system.

Therefore Cfengine will keep running even if there is a network outage or a policy server outage. It will use its cached copy of the last known policy.

Cfengine Design: Scalability

Each host is responsible for carrying out checks and maintenance on itself, based on its local copy of policy. Cfengine could scale to an infinite number of hosts.

Cfengine Design: Simplicity

The design is intended to keep the number of [configuration parts] simple for the user — and hide the details in a library they never really look at.

— Mark Burgess

Look for a new major Cfengine 3 library to be released soon.

Cfengine Design: Have C, Will Travel

Cfengine 3 is small and lightweight in its process size (cf-agent is 1.9 MB) and in network traffic; it does not have very many requirements; it is used on everything from supercomputers to embedded devices.

Cfengine Classes

Classes are “if/then” tests but the test itself is hidden “under the hood” of Cfengine. There is no way to say “if/then” in Cfengine except with classes.

Example - this shell script will only be executed on Linux systems:

shellcommands:

    linux:: "/var/cfengine/inputs/sh/my_script.sh"

There are a number of built-in classes, like the linux class above; they can also be explicitly defined.

What’s New in Cfengine 3?

Language Cleanup

The language, which in Cfengine 2 has grown organically quite varied and inconsistent, has been cleaned up and made very homogenous and consistent.

Promises and Patterns

Cfengine 3 is about Promises and Patterns of Promises.

Promises

A promise is a Cfengine policy statement - for example, that /etc/shadow is only readable by root - and it implies Cfengine will endeavor to keep that promise.

Patterns

I asked Mark to clarify for us what he means by “patterns” in Cfengine 3. Here is his answer:

A “configuration” is a design arrangement or a pattern you make with system resources. The cfengine language makes it easy to describe and implement patterns using tools like lists, bundles and regular expressions. While promises are things that are kept, the efficiencies of configuration come from how the promises form simple re-usable patterns.

Power

The purpose of Cfengine is to provide a simple and powerful configuration management tool flexible enough to handle security monitoring, policy compliance auditing, change management, configuration documentation, etc. You decide how to use Cfengine.

Getting Started

There is a lot to Cfengine; the list (including the author) very were helpful and responsive and helped me get started. It’s comparable to learning a new programming language, rather than how to use a new application.

The following documents are key - they are under the Documentation tab on the Cfengine web site:

Upgrading from Cfengine 2

Cfengine 3 is operationally backwards compatible with Cfengine 2, but the language is not. There will be a translation guide for policies to ease transition from 2 to 3 released at Christmas.

Cfengine 3 is not a drop-in replacement for 2, but a more powerful system.

Installing Cfengine 3

Download http://www.cfengine.org/tarballs/cfengine-3.0.2.tar.gz and follow the INSTALL instructions.

It may not build out of the box - I ran into two snags, on CentOS 5.3 (64-bit).

First, I had to remove the 32-bit versions of Berkeley DB, leaving only the 64-bit versions.

rpm -e db4-devel.i386 db4.i386

Second, I had to install graphviz-devel.x86_64 or the make would bomb out - it’s an unstated dependency (as of 3.0.2 - due to be fixed in 3.0.3 in January 2010).

After Cfengine is built and installed, set up a working directory, complete with binaries and config files; and generate a key-pair:

mkdir -p /var/cfengine/inputs /var/cfengine/bin/
cp /usr/local/sbin/cf-* /var/cfengine/bin/
cp /usr/local/share/doc/cfengine/inputs/*cf /var/cfengine/inputs/
/usr/local/sbin/cf-key

Then, to run Cfengine as a mortal user (the recommended way to start getting your feet), set up another local working directory, complete with binaries and config files:

mkdir ~/.cfagent
mkdir ~/.cfagent/bin
cp /usr/local/sbin/cf-* ~/.cfagent/bin
mkdir ~/.cfagent/inputs
cp /usr/local/share/doc/cfengine/inputs/*cf ~/.cfagent/inputs

Now we can start creating policy files and running Cfengine against them:

vi ~/.cfagent/inputs/test.cf
cf-agent -f ~/.cfagent/inputs/test.cf

Basic Grammar

The basic grammar of Cfengine 3 looks like this:

      type:

        classes::

         "promiser" -> { "promisee1", "promisee2", ... }

            attribute_1 => value_1,
            attribute_2 => value_2,
            ...
            attribute_n => value_n;

Example:

    commands:

       "/bin/echo hello world";

The promise type is “commands”, the promiser is the command string.

List of promise types
  • commands - Run external commands

  • files - Handle files (permissions, copying, etc.)

  • edit_line - Handle files (content)

  • interfaces - Network configuration

  • methods - Methods are compound promises that refer to whole bundles of promises.

  • packages - Package management

  • processes - Process management

  • storage - Disk and filesystem management

Another example:

    files:

       "/tmp/test_plain" -> "John Smith",

            comment => "Make sure John's /tmp/test_plain exists",
            create  => "true";

Here we have the promisee on the right of the → sign.

The promisee is “the abstract object to whom the promise is made”. This is for documenation. The commercial version of cfengine uses promisees to generate automated knowledge maps. The object can be the handle of another promise, recognizing an interest in the outcome, or an affected person who you might want to contact in case of emergency.

Example of a handle - it’s just an id tag:
    files:

       "/tmp/test_plain" -> "John Smith",

            handle => "file_check",
            comment => "Make sure John's /tmp/test_plain exists",
            create  => "true";

Handles can be used to document dependencies:

files:

  "/tmp/testcopy"

    depends_on    => { "file_check" },
    copy_from     => mycopy("/tmp/test_plain");

These are contrived examples; they document John Smith as somehow involved in this activity and show how handles can be used to document dependencies.

The above are not working examples. But this is, and you can run it to get familiar with Cfengine 3:

Make sure /tmp/test_plain exists
########################################################
body common control

{
version => "1.0";
bundlesequence  => { "test1"  };
}

########################################################

bundle agent test1

{


    files:

       "/tmp/test_plain" -> "John Smith",

            comment => "Make sure /tmp/test_plain exists",
            create  => "true";

}

First is the mandatory control section, where you can set certain values, and specify which promise bundles to run.

The next section is a promise bundle, which means a group of one or more promises. The bundle type is “agent” which means it will result in action on the part of Cfengine, or will be picked up by the Cfengine agent when it runs (cf-agent).

Each promise bundle has a name, and that name is referenced in bundlesequence in control.

So what the above promise bundle says is a promise that /tmp/test_plain exists; and specifies John Smith as an involved party.

Put the above in test.cf and run it with

cf-agent -f test.cf

If the file does not exist, Cfengine will create it.

Another working example - make sure ntpd and portmap are running:
body common control

{
version => "1.0";
bundlesequence  => { "check_service_running"  };
}



bundle agent check_service_running {

    vars:
        "service" slist => {"ntpd", "portmap"};
        "daemon_path" string => "/etc/init.d";

    processes:

            "$(service)"
                comment => "Check processes running for '$(service)'",
                restart_class => "restart_$(service)";

    commands:

        "${daemon_path}/${service} start"
            comment => "Execute the start command for the service",
            ifvarclass => "restart_${service}";

}

This uses variables (lists and strings); and when cf-agent hits the $(service) variable, which is a list, it loops over the list. This implicit looping is part of the power of Cfengine.

Another working example, to control the contents of /etc/resolv.conf:
body common control
{
    version => "1.0";
    bundlesequence  => { "checkresolver" };
}


bundle agent checkresolver
{
    vars:

          "resolvers" slist => { "128.39.89.10", "158.36.85.10", "129.241.1.99" };

    files:

          "$(sys.resolv)"

               edit_line => resolvconf("iu.hio.no cfengine.com",@(checkresolver.resolvers));
}


bundle edit_line resolvconf(search,list)
{
    delete_lines:

        "search.*";

    insert_lines:

         "search $(search)";
         "$(list)";
}

“body common control{}” is the control promise body, which affects the operational behavior of Cfengine.

In it, we define version, a version string, used in errors and reports.

Next we have a promise bundle “checkresolver{}” which uses:

An slist, a list of scalar strings,

$(sys.resolv), a system variable (built in to Cfengine). It is alterable. Path to our resolv.conf file.

checkresolver{} calls the promise bundle “resolvconf{}” which is of the editline promise type. Cfengine has built-in functionality for editing files, as this is a common system administration task, and editlines promises handle that. For example:

The delete_lines promise will make sure we don’t end up with duplicate “search” lines which would be invalid for /etc/resolv.conf.

The insert_lines promise will make sure the file contains the specified data, which are a string (the search path) and an array (list of resolvers).

Note: we have to reference the @resolvers array using it’s full name, @checkresolver.resolvers, otherwise resolvconf will fail to find a @resolvers array within its own scope. The @resolvers array is in the scope of “checkresolver{}”.

Body and Bundle

You may have noticed the above Cfengine configuration is in two kinds of parts: a body or a bundle.

Bundle

A promise bundle is a collection of promises.

Body

A promise body is the part of a promise which details and constrains its nature.

Another type of container in cfengine 3 is a “body” part. Body parts exist to hide complex parameter information in reusable containers. The right hand side of some attribute assignments use body containers to reduce the amount of in-line information and preserve readability. You cannot choose where to use bodies: either they are used or they are not used for a particular kind of attribute. What you can choose, however, is the name and number of parameters for the body; and you can make as many of them as you like.

Cfengine 3 Reference Manual

The body of a promise explains what it is about. Think of the body of a contract, or the body of a document. Cfengine “body” declarations divide up these details into standardized, paramaterizable, library units, so we can just write:

copy_from => my_secure_cp("myfile","myserver")

instead of:

body copy_from my_secure_cp(from,server)
{
source      => "$(from)";
servers     => { "$(server)" };
compare     => "digest";
encrypt     => "true";
verify      => "true";
force_ipv4  => "false";
collapse_destination_dir => "false";
copy_size => irange("0","50000");
findertype => "MacOSX";
# etc etc
}

Think of the declarations in the promise as:

(cfengine-word) => (user-data-pattern)

body cfengine-word user-data-pattern
{
details
}

Here is a working example:

test_copy.cf
body common control
{
bundlesequence => { "testcopy" };
version => "1.2.3";
inputs => { "library.cf" };
}


bundle agent testcopy
{
files:
"/home/aleksey/testcopy1"
copy_from =>
my_copy_body_with_options("/home/aleksey/testcopy2","192.168.1.10");
# testcopy1 is the promiser - it's the system resource that the promise concerns,
# or that will be affected by the promise.  testcopy1 promises to be a copy of
# testcopy2, or a copy from testcopy2. It's a remote copy, the server is 192.168.1.10.

}


body copy_from my_copy_body_with_options(sourcefile,sourceserver)
{
source => "$(sourcefile)";
servers => { "$(sourceserver)" };
copy_backup => "true";
purge => "true";
trustkey        => "true";  # trust previously unknown host for a key exchange
compare     => "digest";
encrypt     => "true";
verify      => "true";
force_ipv4  => "false";
collapse_destination_dir => "false";
copy_size => irange("0","50000");
# etc. etc.
}

Note: In order to get this example to work, I had to:

  • Modify “runagent control{}” and “server control{}” in promises.cf to remove mention of localhost (127.0.0.1 and ::1), replacing them with the IP address of the primary interface, 192.168.1.10 (I was advised on the list that while doing it as localhost should work, it would be easier to get it working using the primary IP address);

  • Modify “server control{}” in promises.cf to add a non-root user to “allowusers” because I was running cf-serverd as a non-root user — the file transfer would not work as a root user, BTW, but I was running all my examples as non-root anyway;

Here is the part of promises.cf I modified:

body runagent control
{
hosts => {
          "192.168.1.10"
          # , "myhost.example.com:5308", ...
         };

}

#######################################################

body server control

{
allowconnects         => { "192.168.1.10" };
allowallconnects      => { "192.168.1.10" };
trustkeysfrom         => { "192.168.1.10" };

# Make updates and runs happen in one

cfruncommand          => "$(sys.workdir)/bin/cf-agent -f failsafe.cf &&
$(sys.workdir)/bin/cf-agent";
allowusers            => { "root" , "aleksey" };
}
  • I also had to modify “server access_rules{}” in site.cf to set up the ACL to allow the file transfer - I learned to do that by running cf-serverd in verbose mode with debugging set to 1.

Here are my changes to site.cf:

#######################################################
# Server configuration
#######################################################

bundle server access_rules()
{
access:

  "/home/aleksey/testcopy2"

    admit   => { "192.168.1.10" };

roles:

  ".*"  authorize => { "aleksey" };
}
  • Generate a keypair for my cf-serverd:

cf-key
  • Start cf-serverd in verbose (non-forked) mode, debug level 1:

cf-serverd -v -d 1
  • Run my example code to copy the file from cf-serverd:

cf-agent -v -f test_copy.cf -K

Learn More