Using Policy Analyzer to develop and debug CFEngine policy

Posted by Craig Comstock
March 29, 2021

I have a setup at home where I keep a local git server running on a Raspberry Pi 3 which contains personal/work journal, dotfiles and a personal policy repository. It was set up manually so before adding a new git repository for a family password store I set about retrofiting the configuration in CFEngine. The goal in this blog is to ensure that what I have already is managed by CFEngine and that what I want to add, /srv/git/passwords.git, is created.

My policy buddy, Nick Anderson, mentioned a strategy that he liked for working with CFEngine and policy: Inventory, Classify, then Promise.

Setup

For this setup I already had two physical servers: hub and git to work on. In order to make the steps easier to reproduce and follow along I will use our Vagrant Environment and use the host001 VM provided as the git server.

After running vagrant up you should have Mission Portal available with two hosts bootstrapped: hub and host001.

Mission Portal Inventory Report shows 2 hosts bootstrapped and reporting.

If you are impatient (and I suspect most of us are when playing around) you can shorten the feedback time for policy changes by configuring the agent schedule to every minute instead of the default of every 5 minutes.

In Augments (/var/cfengine/masterfiles/def.json) on hub add the following:

{
  "vars": {
    "control_executor_splaytime": "1",
    "control_executor_schedule": ["any::"],
    "control_hub_hub_schedule": ["any::"]
  }
}

The above is a complete and working augments file. If you have other customizations in there you will have to adjust accordingly.

After making this change, update the running policy as root with

/var/cfengine/bin/cf-agent -KIf update.cf

After installing and logging in to Mission Portal I visit the Policy Analyzer app tab on the lower-left and enable Policy Analyzer.

Mission Portal UI with Policy Analyzer Tab selected and Enable button present

Then wait 1-2 minutes and refresh the page. You should see a tree of your policy similar to this:

Policy Analyzer UI with policy tree view

I also need to setup my initial state on host001 with some directories. This is what I already have on my real git server.

mkdir -p /srv/git/dotfiles.git
mkdir -p /srv/git/journal.git
mkdir -p /srv/git/personal-policy.git

Inventory

Lets write the policy to promise a set of sub-dirs in /srv/git, or rather sort of “inventory” them the temporary way. :)

Find out what is on the system(s) that is interesting to me. In this case I want to know a few things about what I setup manually:

  • Which repo directories are setup in /srv/git?
  • Is there a git user and group on the system?
  • Are these directories owned by the git user and group?
  • Are the permissions as I want them on /srv/git directories?

In some cases I just want to know for a while what is going on and not monitor it over time. In this case I make promises for things and mark them as action_policy => "warn" in an action body so that I can see them as NOT KEPT in Policy Analyzer. At a later date I can change them to actuate their effects after I am confident they will “do the right thing”.

Assuming you are using the Vagrant environment, let’s set up git version control. This allows us to track our changes, and revert (undo) if necessary.

cd /var/cfengine/masterfiles
git init
git add .
git commit -m initial

Now let’s set up a minimal def.json to enable autorun services, the most common way to work on specific use cases in policy.

(This will also include the “update fast” bits we added earlier)

{
  "classes": {
    "services_autorun": ["any::"],
    "cfengine_internal_purge_policies": ["any::"],
  },
  "vars": {
    "control_executor_splaytime": "1",
    "control_executor_schedule": ["any::"],
    "control_hub_hub_schedule": ["any::"]
  }
}

And add the git_server policy at /var/cfengine/masterfiles/services/autorun/git_server.cf:

bundle agent git_server
{
  meta:
    "tags" slist => { "autorun" };
  vars:
    "_repos" slist => {
      "dotfiles",
      "journal",
      "personal-policy"
     };
    "_basedir" string => "/srv/git";
  files:
    "$(_basedir)$(const.dirsep)."
      create => "true",
      action => warn;
    "$(_basedir)$(const.dirsep)$(_repos).git$(const.dirsep)."
      create => "true",
      action => warn;
}

body action warn
{
  action_policy => "warn";
}

Wait for a few minutes for the policy to be updated both on hub and host001 and refresh Policy Analyzer. The red box with 5 indicates that there are 5 promises which are not kept (errors):

Policy Analyzer UI shows 5 not kept promises inside tree view.

One not kept promise is the methods promise which calls the git_server bundle. Since we are only calling the git_server bundle from this one methods promise we can ignore this not kept promise and focus on the underlying not kept promises in the git_server bundle. Another not kept promise is for the /srv/git/ and sub-directories not existing on the hub. This is because so far we have not classified this policy to only apply to host001 our “git” server.

Policy Analyzer UI shows details in a table for 5 not kept promises.

Classify

Now we will classify this policy so that it only applies to host001 by adding a class-guard to the meta promise which activates git_server.

  meta:
    host001::
      "tags" slist => { "autorun" };

Wait a few minutes and see that Policy Analyzer is once again clean and empty of not kept promises (no red warning counter box!).

Navigate to services/autorun/git_server.cf and select the Kept checkbox at the top of Policy Analyzer to see which promises are kept.

Policy Analyzer UI shows promises kept in tree view.

The table shows what we expect for the current state of host001:

Policy Analyzer UI shows details in a table for kept promises.

Now let’s add “passwords” to our list of git repos:

    "_repos" slist => {
      "dotfiles",
      "journal",
      "personal-policy",
      "passwords"
     };

Save that and wait a minute or two and refresh Policy Analyzer to see that we have Not Kept promises again.

Policy Analyzer UI shows 2 not kept promises inside tree view, 1 in lib and 1 in services.

Navigate again to services/autorun/git_server.cf in the tree. The table shows the specific promises:

Below the Policy Analyzer tree view and code view there is a table of results for the selected file. It shows 1 promise not kept - the passwords.git promise.

Promise

Now that we see clearly what is not kept and have checked that it’s what we want, we remove the action => warn in our policy and let CFEngine fix the situation.

  files:
    "$(_basedir)$(const.dirsep)."
      create => "true";
    "$(_basedir)$(const.dirsep)$(_repos).git$(const.dirsep)."
      create => "true";

Save that change, wait a few minutes (stand up! stretch! do a pushup or wiggle your ears!) and refresh Policy Analyzer.

Check the Repaired checkbox to see that the /srv/git/passwords.git repo directory has been created.

Policy Analyzer UI shows 2 not kept promises inside tree view, 1 in lib and 1 in services.

To Review

Policy Analyzer is a handy tool you can use to visualize your policy alongside the current last outcome of each promise.

While developing policy you can add an action body to any type of promise to see what would be broken and where so you can classify your promises properly and only make changes where appropriate:

bundle agent main
{
  files:
    "/my/dir/."
      create => "true",
      action => warn;
}

body action warn
{
  action_policy => "warn";
}

Try it!

Download CFEngine Enterprise free for up to 25 hosts and try out Policy Analyzer!

We’ll be working on more improvements as we go and would love to get your feedback as well.