Feature Friday #5: cfbs

Posted by Nick Anderson
April 12, 2024

Do you maintain multiple policy sets? Do you leverage policy written by others? Ever wished for an easier way to upgrade your policy framework? cfbs can help to improve all of these cases.

cfbs is a command line tool that aims to help simplify managing a policy set and working with CFEngine Build, a website for finding and sharing modules. A policy set usually - but not always - builds on top of some base, like the Masterfiles Policy Framework (MPF). Custom policy is added on top, and you’re off to the races. When a new version of CFEngine is released, the best practice is to upgrade the MPF (assuming you are using it) as the first step in the upgrade process. If you have not modified the MPF, re-integrating the custom policy on top of the new MPF is a relatively straightforward process. If you have modified the MPF it’s 1) something you need to know that you did and 2) a process of managing your diffs against the newer version of the MPF.

For the case of a non-modified MPF upgrade, cfbs makes this very straightforward using cfbs upgrade. For the case of a modified MPF, cfbs provides an ordered series of steps that can better help to keep track of and maintain modifications.

Let’s take a look at the process of initializing a cfbs project with the MPF and adding some custom policy files. After installing cfbs from the Python Package Index (pip), running cfbs init is all you need to do. You’ll be prompted with a few questions (defaults can be accepted by pressing ENTER when prompted).

command
cfbs init
output
Please enter the name of this CFEngine Build project [Example project]:
Please enter the description of this CFEngine Build project [Example description]:
Do you want cfbs to initialize a git repository and make commits to it? [YES/y/no/n]
Please enter user name to use for git commits [Nick Anderson]:
Please enter user email to use for git commits [nick.anderson@northern.tech]:
Initialized empty Git repository in /home/nickanderson/northern.tech/cfengine/src/website/test/.git/
{
  "name": "Example project",
  "description": "Example description",
  "type": "policy-set",
  "git": true,
  "build": []
}

The default commit message is 'Initialized a new CFEngine Build project' - edit it? [yes/y/NO/n]
Committing using git:

[main (root-commit) 64d9389] Initialized a new CFEngine Build project
 1 file changed, 7 insertions(+)
 create mode 100644 cfbs.json

Initialized an empty project called 'Example project' in 'cfbs.json'
Do you wish to build on top of the default policy set, masterfiles? (Recommended) [YES/y/no/n]:
Added module: masterfiles
The default commit message is 'Added module 'masterfiles'' - edit it? [yes/y/NO/n]
Committing using git:

[main da7fbe0] Added module 'masterfiles'
 1 file changed, 16 insertions(+), 1 deletion(-)

At this point, we still don’t have a working policy set, because we need to build it. For that, we use the cfbs build command.

command
cfbs build
output
Modules:
001 masterfiles @ 80374429aa8d9f1d5afe952727ae5659caf5e9ef (Downloaded)

Steps:
001 masterfiles : run 'EXPLICIT_VERSION=3.21.4 EXPLICIT_RELEASE=1 ./prepare.sh -y'
001 masterfiles : copy './' 'masterfiles/'

Generating tarball...

Build complete, ready to deploy 🐿
 -> Directory: out/masterfiles
 -> Tarball:   out/masterfiles.tgz

To install on this machine: sudo cfbs install
To deploy on remote hub(s): cf-remote deploy

By now, our project directory contains the project file (cfbs.json), and the out directory where our completed build can be found in out/masterfiles contains a built policy set. If you’re familiar with the MPF, it should look familiar.

command
ls
output
cfbs.json  out
command
ls out/masterfiles | columns -c 50
output
cfbs.json                                           cfe_internal
controls                                            inventory
lib                                                 modules
promises.cf                                         services
standalone_self_upgrade.cf                          templates
update.cf

With a working base policy set, we can add some custom policy files to the project. Then, we can run cfbs add to integrate it into the project.

command
cat > hello-world.cf << EOF
bundle agent hello_world
#@ Emit hello world
{
  reports:
    "Hello World!";
}
EOF
command
cfbs add ./hello-world.cf
output
Which bundle should be evaluated (added to bundle sequence)?
 1. ./hello-world.cf:hello_world (default)
 2. (None)
 [1/2]: 1
Added module: ./hello-world.cf
The default commit message is 'Added module './hello-world.cf'' - edit it? [yes/y/NO/n]
Committing using git:

[main bc51c13] Added module './hello-world.cf'
 2 files changed, 17 insertions(+)
 create mode 100644 hello-world.cf

As you can see from the above output, cfbs prompted us to ask if the bundle should be added to the bundle sequence, since we want that bundle to run, not just use it from other policy we selected the appropriate option. We selected the default commit message and the module was added. We can now see that reflected in the cfbs status command output.

command
cfbs status
output
Name:        Example project
Description: Example description
File:        cfbs.json

Modules:
001 masterfiles      @ 80374429aa8d9f1d5afe952727ae5659caf5e9ef (Downloaded)
002 ./hello-world.cf @ local                                    (Copied)

Now, when we build the project using the cfbs build command, we will find the hello-world.cf policy file in the result.

command
find out/masterfiles -name "hello-world.cf"
output
out/masterfiles/services/cfbs/hello-world.cf

Where that file lands in the resulting policy set is governed by the project file (cfbs.json), let’s have a look.

{
  "name": "Example project",
  "description": "Example description",
  "type": "policy-set",
  "git": true,
  "build": [
    {
      "name": "masterfiles",
      "description": "Official CFEngine Masterfiles Policy Framework (MPF).",
      "tags": ["supported", "base"],
      "repo": "https://github.com/cfengine/masterfiles",
      "by": "https://github.com/cfengine",
      "version": "3.21.4",
      "commit": "80374429aa8d9f1d5afe952727ae5659caf5e9ef",
      "added_by": "cfbs add",
      "steps": [
        "run EXPLICIT_VERSION=3.21.4 EXPLICIT_RELEASE=1 ./prepare.sh -y",
        "copy ./ ./"
      ]
    },
    {
      "name": "./hello-world.cf",
      "description": "Local policy file added using cfbs command line",
      "tags": ["local"],
      "added_by": "cfbs add",
      "steps": [
        "copy ./hello-world.cf services/cfbs/hello-world.cf",
        "policy_files services/cfbs/hello-world.cf",
        "bundles hello_world"
      ]
    }
  ]
}

Note the policy_files build step for ./hello-world.cf targets services/cfbs/hello-world.cf. You can adjust the build steps to get the desired layout.

If you manage just a single policy set, the benefits may not be immediately apparent, but that’s not all cfbs does. It’s also a great way to find and integrate policy written by others.

For example, there is a lynis module providing policy to install and run CISOfy Lynis as well as a companion compliance-report-lynis module that provides a report in the CFEngine Enterprises Mission Portal showing a breakdown of all the detected findings. We can find these modules using the command cfbs search lynis:

command
cfbs search lynis
output
compliance-report-lynis - Compliance report with Lynis checks.
lynis - Automates the installation, running, and reporting of CISOfy's lynis system audits.

And install them with cfbs add:

command
cfbs add compliance-report-lynis
output
Added module: compliance-report-imports (Dependency of compliance-report-lynis)
Added module: lynis (Dependency of compliance-report-lynis)
Added module: autorun (Dependency of compliance-report-imports)
Added module: compliance-report-lynis
The default commit message is:

        Added 4 modules

         - Added module 'lynis'
         - Added module 'compliance-report-imports'
         - Added module 'compliance-report-lynis'
         - Added module 'autorun'

Edit it? [yes/y/NO/n]
Committing using git:

[main 066636b] Added 4 modules
 1 file changed, 54 insertions(+)

CFEngine Enterprise users can leverage a subset of features in Mission Portal via the Build app. In Mission Portal, you can set up and switch between multiple projects, each connected to a specific branch in a git repository.

Screenshot of setting up a Build project with git repo, branch, etc.

You can search for, install, and upgrade modules from the CFEngine Build Index and remove (but not add) modules added from other sources, with a single click.

Screenshot of adding a module to a Build project

Check out our blog post on migrating to cfbs.

Happy Friday! 🎉