Generally, cf-agent
runs as a privileged user. But did you know that you can also run as an unprivileged user?
A major benefit of running cf-agent
unprivileged is the ability to prototype policies during development. However, attempting to execute cf-agent
as an unprivileged user without proper configuration will result in errors. Let’s create /tmp/feature-friday-25.cf
with the following content:
bundle agent main
{
reports: "Happy Friday!";
}
Now, let’s try running that policy with cf-agent
as an unprivileged user:
cf-agent -Kf /tmp/feature-friday-25.cf
error: cf-promises needs to be installed in /home/nick/.cfagent/bin for pre-validation of full configuration
error: Failsafe condition triggered. Interactive session detected, skipping failsafe.cf execution.
error: Error reading CFEngine policy. Exiting...
When cf-agent
executes, it wants to use cf-promises
from ~/.cfagent/bin
, which is empty:
ls -al ~/.cfagent/bin/
total 8
drwx------ 2 nick nick 4096 Mar 8 09:53 .
drwx------ 11 nick nick 4096 Mar 8 09:53 ..
So that the user’s binaries stay in sync with the package on the system, it’s best to create a symbolic link from ~/.cfagent/bin
to /var/cfengine/bin
:
rm -rf ~/.cfagent/bin && ln -s /var/cfengine/bin ~/.cfagent/bin
ls ~/.cfagent/bin/cf-promises
/home/nick/.cfagent/bin/cf-promises
If you run the policy again, you will get an error if the policy file is writable by others:
cf-agent -Kf /tmp/feature-friday-25.cf
error: File /tmp/feature-friday-25.cf (owner 1001) is writable by others (security exception)
However, after adjusting the permissions - so that only the owner can write to the file - it runs without any issues:
chmod 600 /tmp/feature-friday-25.cf
cf-agent -Kf /tmp/feature-friday-25.cf
R: Happy Friday!
Furthermore, seeding the standard library in ~/.cfagent/inputs
would be convenient. Let’s use cf-remote
to place the standard library for the version of CFEngine that is currently installed:
MPF_TARBALL_URL=$(cf-remote --version $(cf-agent --version | head -n 1 | cut -d' ' -f3) list masterfiles | tail -n 1)
MPF_TARBALL_FILENAME=$(basename "$MPF_TARBALL_URL")
TMPDIR=$(mktemp -d --suffix=-seed-mpf-stdlib)
curl "$MPF_TARBALL_URL" --output-dir "$TMPDIR" --output "$MPF_TARBALL_FILENAME" --silent
tar --directory "$HOME/.cfagent/inputs" --gunzip --extract --file $TMPDIR/$MPF_TARBALL_FILENAME ./masterfiles/lib --strip-components 2
rm -rf "$TMPDIR"
Now, we can adjust our sample policy to include the standard library and use something from it:
body file control
{
inputs => { "$(sys.libdir)/stdlib.cf" };
}
bundle agent main
{
reports:
"Happy Friday!";
"This policy contains:" printfile => cat( "$(this.promise_filename)" );
}
cf-agent -Kf /tmp/feature-friday-25.cf
R: Happy Friday!
R: This policy contains:
R: body file control
R: {
R: inputs => { "$(sys.libdir)/stdlib.cf" };
R: }
R: bundle agent main
R: {
R: reports:
R:
R: "Happy Friday!";
R: "This policy contains:" printfile => cat( "$(this.promise_filename)" );
R: }
However, there are some limitations. Anything that requires elevated privileges will not work since cf-agent
won’t elevate privileges to take action. For example, here we adjusted the policy to set the ownership of the policy file to root
:
body file control
{
inputs => { "$(sys.libdir)/stdlib.cf" };
}
bundle agent main
{
files:
"$(this.promise_filename)"
perms => owner( "root" );
reports:
"Happy Friday!";
"This policy contains:" printfile => cat( "$(this.promise_filename)" );
}
Your OS says “Get out of here!” And CFEngine emits errors.
cf-agent -Kf /tmp/feature-friday-25.cf
error: Cannot set ownership on file '/tmp/feature-friday-25.cf'. (chown: Operation not permitted)
error: Cannot set ownership on file '/tmp/feature-friday-25.cf'. (chown: Operation not permitted)
error: Errors encountered when actuating files promise '/tmp/feature-friday-25.cf'
R: Happy Friday!
R: This policy contains:
R: body file control
R: {
R: inputs => { "$(sys.libdir)/stdlib.cf" };
R: }
R: bundle agent main
R: {
R: files:
R: "$(this.promise_filename)"
R: perms => owner( "root" );
R:
R: reports:
R:
R: "Happy Friday!";
R: "This policy contains:" printfile => cat( "$(this.promise_filename)" );
R: }
So, while there is no capability to leverage unprivileged execution to manage resources that require elevated privileges. It’s undoubtedly handy for prototyping policy. And it might save you from some future unintended regrets.
Happy Friday! 🎉