Configuring SSHd (or any other service) with Cfengine 3

Today I had more time to have a closer look at Cfengine 3. Since I am still using Cfengine 2 at work due to a company policy, I would like to gain more knowledge about the latest version of the configuration management tool in my spare time. One day I am hopefully able to port our Cfengine scripts to version 3.
The right path to that goal is taking small steps, and one of the most important of them is getting to know how common Linux services (such as the SSHd) can be configured easely with Cfengine 3 methods.

Preperations
Editing config files with Cfengine 3 can be pretty easy when using pre-defined methods, such as set_config_values or set_variable_values. These methods are implemented in the latest versions of Cfengine 3 and most unfortunately they are not available through the Ubuntu pool yet. The official Ubuntu pool still contains the old version 3.1.5-1, that is why I visited the official Cfengine website, registered there and downloaded the pre-compiled Debian package for Cfengine 3.3.5.

The code snippet of this blog post will not work with older versions, so make sure that your Linux system is equipped with the latest stable release of Cfengine 3.

Writing the Cfengine 3 code

body common control {
        version         => "1.0";
        inputs          => { "cfengine_stdlib.cf" };
        bundlesequence  => { "configure_services" };
}

bundle agent configure_services {
  vars:
        #Which config file shall be configured?
        "files[sshd]" string => "/etc/ssh/sshd_config";

        #SSHd variables to be set
        "sshd[PasswordAuthentication]"  string => "yes";
        "sshd[UsePAM]"                  string => "yes";


  methods:
        "sshd" usebundle        => edit_sshd,
        comment                 => "Configure $(files[sshd])";
}

bundle agent edit_sshd {
  files:
        "$(configure_services.files[sshd])"
        handle          => "edit_sshd",
        comment         => "Ensure sshd is configured correctly",
        create          => "true",
        edit_line       => set_config_values("configure_services.sshd"),
        classes         => if_repaired("sshd_restart_required");

  commands:
        sshd_restart_required::
          "/etc/init.d/ssh restart"
                handle  => "restart_sshd",
                comment => "Restart SSHd for configuration changes";
}

This snippet looks too large for its task, but I ensure you that it is still a valid (and most important) scalable solution.
Now save the file with an editor of your choice (I used “example7.cf”) and make sure that it is located in the right directory (e.g. /etc/cfengine3).

Let’s see if the syntax is correct:

/var/cfengine/bin/cf-promises -f /etc/cfengine3/example7.cf 

In case you wonder: I installed an update of Cfengine 3 manually and therefore had to call the “cf-binaries” manually (and I was too lazy to update my $PATH variable).

No output or error message? Great, let’s run our Cfengine script then:

/var/cfengine/bin/cf-agent -f /etc/cfengine3/example7.cf 

Now have a look at your /etc/ssh/sshd_config file – it was updated and Cfengine also restarted the SSH daemon. Awesome!

Analyzing the Cfengine 3 code snippet
Well, what did we just do? At first, we defined our “body common control” section which directly refers to the bundle agent “configure_services”, which is shown below:

bundle agent configure_services {
  vars:
        #Which config file shall be configured?
        "files[sshd]" string => "/etc/ssh/sshd_config";

        #SSHd variables to be set
        "sshd[PasswordAuthentication]"  string => "yes";
        "sshd[UsePAM]"                  string => "yes";

This bundle agent is special bundle – it doesn’t do much and lets other bundles do the real work. Diego Zamboni from the Cfengine team calls such bundles “driver bundles”, a good name in my eyes. At the beginning of the driver bundle we defined a promise of the type “vars”. This promise contains the array “files[]” with only one value of the type “string”, namely the path to the config file we want to be edited.
The array “files[]” and its first “value container” (here. “[sshd]” can be called later with configure_services.files[sshd].

In the next lines we created a second array with the name sshd[] – it contains the name of the variables and their values which will be used for editing the config file later on. Note that I only used two values and of course you can enlarge the definition by all values which should be set inside the sshd_config file.

In the next part of the bundle agent “configure_services”…

  methods:
        "sshd" usebundle        => edit_sshd,
        comment                 => "Configure $(files[sshd])";
}

…we created a “methods” promise which are useful for encapsulating repeatedly used configuration issues and iterating over parameters. In this case, we only refer to the bundle agent “edit_sshd” and left a comment about what we are planning to do with this code.

Now let’s move on to the most interesting part of our code snippet:

bundle agent edit_sshd {
  files:
        "$(configure_services.files[sshd])"
        handle          => "edit_sshd",
        comment         => "Ensure sshd is configured correctly",
        create          => "true",
        edit_line       => set_config_values("configure_services.sshd"),
        classes         => if_repaired("sshd_restart_required");

This bundle agent called “edit_ssh” encapsulates a promise of the type “files”. Instead of namely providing the file to be edited, we refer to the array “files[]” which was defined in a previous bundle agent. This makes our code more flexible and already shows an attempt of making the whole Cfengine script more scalable.

The next to lines contain a “handle” and a “comment” which are useful for documentation and logging reasons. The “create” statement ensures that the configuration file is created if it doesn’t exist yet and “edit_line” does all the work for us. The bundle “set_config_values” is a pre-defined one (see cfengine_stdlib.cf in /etc/cfengine3) and uses our array “sshd[]” as a parameter. Since “sshd[]” contains the variable names and their values, the “set_config_values” bundle is able to ensure that those variables are set in the target configuration file. If a defined variable already exists, it is either commented out, corrected or left alone – it all depends on the existing state of the specific configuration variable definition. If the variable wasn’t there yet, the bundle “set_config_values” will add it. These properties make this bundle a very efficient one and you will find it to be very useful during writing your own Cfengine 3 scripts.

The last attribute of the “files” promise, “classes”, defines some sort of action to be taken if this promise is repaired (or more clear: this action will be called when Cfengine 3 edited our configuration file). The value of a “classes” attribute is called a class and can be used later for setting conditions, e.g. “if our file was edited, set a flag” (where this “flag” is of course our class).

With the class “sshd_restart_required” being set…

 commands:
        sshd_restart_required::
          "/etc/init.d/ssh restart"
                handle  => "restart_sshd",
                comment => "Restart SSHd for configuration changes";
}

…our promise of the type “commands” can be equipped with a condition. In our case it means that when “if_repaired” is true, the class “sshd_restart_required” is set and therefore a condition is defined as true. So, “when our condition sshd_restart_required is true, perform the defined actions below”. Our defined actions are issuing the command “/etc/init.d/ssh restart” and commenting it for our documentation or log file.

Summary
That’s it! I hope you find this blog post helpful. There are other ways for editing configuration files, but I think that this is a very elegant and scalable solution. You can enhance this code sample easely for fitting our own needs. E.g. it can be rewritten for editing multiple configuration files of multiple services.

As usual, you can download today’s Cfengine 3 code snippet here.

5 thoughts on “Configuring SSHd (or any other service) with Cfengine 3

  1. Thanks – this is helpful. I’m just getting started with CFEngine 3.5. Examples like this are the way I can learn rapidly.

Comments are closed.