Is your CFEngine policy set a Frankensteinian monster of outdated files and custom tweaks? Take a look at tooling we have in development (cfbs analyze
) to help shed light on the makeup of your policy set.
In this episode the team showcases a new tool for analyzing CFEngine policy sets. Learn how to identify modifications, missing files, and stale policies from old MPF versions and hear about additional features we can look forward to when it’s fully released.
Video
The video recording is available on YouTube:
At the end of every webinar, we stop the recording for a nice and relaxed, off-the-record chat with attendees. Join the next webinar to not miss this discussion.
Take it for a spin
First, we need to get cfbs
installed. Remember this is a work in progress, we need to grab it from a GitHub pull request to the cfbs repo, #218 specifically.
Before I install from this branch I first want to remove any pre-existing install of cfbs
.
pipx uninstall cfbs
uninstalled cfbs! ✨ 🌟 ✨
Next we install from the checked out branch:
pipx install .
installed package cfbs 2.4.3+351.git.g41c9be7, installed using Python 3.12.3
These apps are now globally available
- cfbs
Now that we have the development version of cfbs
installed lets grab the oldest version of the MPF we can easily get with cfbs (3.21.0
).
OLDEST_MPF_IN_CFBS="$(cf-remote list | head -n1 | awk '{print $NF}')"
URL_OLDEST_MPF_IN_CFBS="$(cf-remote --version ${OLDEST_MPF_IN_CFBS} list masterfiles | tail -n1)"
TARBALL_FILENAME_OLDEST_MPF_IN_CFBS="$(basename ${URL_OLDEST_MPF_IN_CFBS})"
wget ${URL_OLDEST_MPF_IN_CFBS}
tar --no-same-permissions --no-overwrite-dir -mzxf ${TARBALL_FILENAME_OLDEST_MPF_IN_CFBS}
mv masterfiles "masterfiles-${OLDEST_MPF_IN_CFBS}"
--2025-03-27 12:25:02-- https://cfengine-package-repos.s3.amazonaws.com/enterprise/Enterprise-3.21.0/misc/cfengine-masterfiles-3.21.0-1.pkg.tar.gz
Resolving cfengine-package-repos.s3.amazonaws.com (cfengine-package-repos.s3.amazonaws.com)... 52.216.216.81, 3.5.25.199, 52.216.219.9, ...
Connecting to cfengine-package-repos.s3.amazonaws.com (cfengine-package-repos.s3.amazonaws.com)|52.216.216.81|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 212907 (208K) [application/x-tar]
Saving to: ‘cfengine-masterfiles-3.21.0-1.pkg.tar.gz’
cfengine- 0%[ ] 0 --.-KB/s
cfengine-masterfile 100%[===================>] 207.92K --.-KB/s in 0.1s
2025-03-27 12:25:03 (1.58 MB/s) - ‘cfengine-masterfiles-3.21.0-1.pkg.tar.gz’ saved [212907/212907]
Let’s analyze it.
cfbs analyze "./masterfiles-${OLDEST_MPF_IN_CFBS}"
Policy set path: ./masterfiles-3.21.0
Same filepath versions distribution: [('3.21.0', 130), ('3.21.1', 127), ('3.21.2', 111), ('3.22.0', 110), ('3.21.3', 107), ('3.18.4', 106), ('3.18.3', 106), ('3.20.0', 104), ('3.18.5', 97), ('3.23.0', 96), ('3.21.4', 96), ('3.18.6', 96), ('3.18.2', 94), ('3.21.5', 89), ('3.21.6', 86), ('3.18.7', 85), ('3.24.0', 81), ('3.18.8', 80), ('3.19.0', 79), ('3.25.0', 78), ('3.24.1', 78), ('3.18.1', 78), ('3.18.0', 71), ('3.17.0', 55), ('3.15.7', 54), ('3.15.6', 54), ('3.15.5', 49), ('3.16.0', 36), ('3.15.4', 35), ('3.15.3', 34), ('3.15.2', 33), ('3.15.1', 31), ('3.15.0', 30), ('3.12.7', 29), ('3.12.6', 28), ('3.12.5', 28), ('3.12.4', 28), ('3.12.3', 26), ('3.15.0b1', 25), ('3.14.0', 23), ('3.12.2', 21), ('3.13.0', 20), ('3.12.1', 20), ('3.12.0', 19), ('3.12.0b1', 19), ('3.10.7', 15), ('3.10.6', 15), ('3.10.5', 15), ('3.10.4', 15), ('3.10.3', 14), ('3.11.0', 11), ('3.11.0b1', 11), ('3.10.2', 11), ('3.10.1', 11), ('3.10.0', 11), ('3.10.0b1', 11), ('3.9.2', 9), ('3.7.8', 9), ('3.7.7', 8), ('3.7.6', 7), ('3.7.5', 7)]
Same filepath highest versions distribution: [('3.25.0', 78), ('3.21.1', 16), ('3.23.0', 15), ('3.22.0', 14), ('3.24.0', 3), ('3.21.0', 3), ('3.21.4', 1)]
Reference version: 3.21.0
No files are missing from the version.
No files of the version are modified.
No files are from a different version.
No files are not from any version.
Now let’s seed some typical modifications to the base.
# Trigger Files missing from the version:
rm -f "./masterfiles-${OLDEST_MPF_IN_CFBS}/services/autorun/hello.cf"
# Trigger Files from the version but with modifications:
echo 'bundle agent mpf_main
{
reports:
"Customized services/main.cf";
}
bundle agent hello_world
{
reports:
"Hello World!";
}' > "./masterfiles-${OLDEST_MPF_IN_CFBS}/services/main.cf"
# Trigger Files not from any version (with both custom content and path):
echo '{
"variables": {
"default:def.bundlesequence_end": [ "hello_world" ]
}
}' > "./masterfiles-${OLDEST_MPF_IN_CFBS}/def.json"
# Files from a different version, with modifications:
mkdir -p "./masterfiles-${OLDEST_MPF_IN_CFBS}/lib/3.7/"
echo "# Oops, we neglected to clean up this file from an old MPF version. " > "./masterfiles-${OLDEST_MPF_IN_CFBS}/lib/3.7/services.cf"
If we analyze it again:
cfbs analyze "./masterfiles-${OLDEST_MPF_IN_CFBS}"
We see that it correctly identified that services/autorun/hello.cf
was missing, services/main.cf
had been modified, lib/3.7/services.cf
seemed to be a modified version from an old MPF version, and def.json
was a custom addition.
Policy set path: ./masterfiles-3.21.0
Same filepath versions distribution: [('3.21.0', 128), ('3.21.1', 125), ('3.21.2', 109), ('3.22.0', 108), ('3.21.3', 105), ('3.18.4', 105), ('3.18.3', 105), ('3.20.0', 102), ('3.18.5', 96), ('3.18.6', 95), ('3.23.0', 94), ('3.21.4', 94), ('3.18.2', 93), ('3.21.5', 87), ('3.21.6', 84), ('3.18.7', 84), ('3.24.0', 79), ('3.18.8', 79), ('3.19.0', 78), ('3.18.1', 77), ('3.25.0', 76), ('3.24.1', 76), ('3.18.0', 70), ('3.17.0', 54), ('3.15.7', 53), ('3.15.6', 53), ('3.15.5', 48), ('3.16.0', 35), ('3.15.4', 34), ('3.15.3', 33), ('3.15.2', 32), ('3.15.1', 30), ('3.15.0', 29), ('3.12.7', 28), ('3.12.6', 27), ('3.12.5', 27), ('3.12.4', 27), ('3.12.3', 25), ('3.15.0b1', 24), ('3.14.0', 22), ('3.12.2', 20), ('3.13.0', 19), ('3.12.1', 19), ('3.12.0', 18), ('3.12.0b1', 18), ('3.10.7', 14), ('3.10.6', 14), ('3.10.5', 14), ('3.10.4', 14), ('3.10.3', 13), ('3.11.0', 10), ('3.11.0b1', 10), ('3.10.2', 10), ('3.10.1', 10), ('3.10.0', 10), ('3.10.0b1', 10), ('3.9.2', 8), ('3.7.8', 8), ('3.7.7', 7), ('3.7.6', 6), ('3.7.5', 6)]
Same filepath highest versions distribution: [('3.25.0', 76), ('3.21.1', 16), ('3.23.0', 15), ('3.22.0', 14), ('3.24.0', 3), ('3.21.0', 3), ('3.21.4', 1)]
Reference version: 3.21.0
Files missing from the version:
└── services/autorun/hello.cf
Files from the version but with modifications:
└── services/main.cf
Files from a different version, with modifications:
└── ('lib/3.7/services.cf', ['3.7.8', '3.7.7', '3.7.6', '3.7.5'])
Files not from any version (with both custom content and path):
└── def.json
Post Show Discussion
How to suppress errors from promises not kept?
For example, a commands promise to add a user to a group. It’s expected that it may fail.
- Generically, avoid making the promise when it’s not necessary. CFEngine does this internally but when running commands for things it’s the users responsibility to properly contextualize promises. E.g. first probe to find out if the group already exists to determine if it actually needs to be added or not.
- Consider overriding the interpretation of return codes for that specific promise.
- Consider using the groups custom promise type. It’s written in Python, so much more accessible if the implementation needs to be tweaked to support your specific case.
Also, keep an eye out for a new function findlocalusers()
on the horizon.
Various topics
- Fairphone Love: Craig shared his enthusiasm for Fairphone, a repairable and customizable phone. They even discussed installing alternative operating systems on it. Craig may or may not be a Fairphone sponsor. The jury is still out.
- Hardware Woes: There were tales of hardware failures, including a dead Ryzen processor.
- Android devices: Craig is investigating a Google Play packages promise implementation.
- Dumber Smart Phones: Cat S22 flip phone for minimizing social media exposure. But is there something similar that works well with Android Auto?
Links
- Connect on LinkedIn w/ Cody, Craig, Herman, or Nick
- cfbs analyze in development
- groups custom promise type
- Fairphone 4
- Cat S22 flip phone
- All Episodes