Feature Friday #12: special variables

Posted by Nick Anderson
May 31, 2024

Are you familiar with CFEngines special variables?

Probably you are familiar with sys variables like sys.fqhost (the fully qualified host name) and sys.policy_hub (the IP address of the machine the host is bootstrapped to) but I want to highlight a few other special variables you may not be so familiar with.

sys

Sys variables are derived from the system discovery done by the agent as it initializes.

sys.os_release - A data structure derived from /etc/os-release

/etc/os-release, introduced by systemd provides a nice record of the current distributions release information.1 CFEngine prefers information from this file for determining system classification like the definition of the redhat and debian classes. The file can also be extended with custom keys, like I have done on my system to set NORTHERN_TECH_OWNER=Nick Anderson. Since files information is exposed as a data container in this sys variable it can be useful for influencing policy behavior, like selecting additional Augments to load.2

bundle agent __main__
{
  reports:
    "My custom key 'NORTHERN_TECH_OWNER' contains $(sys.os_release[NORTHERN_TECH_OWNER])";
}
output
R: My custom key 'NORTHERN_TECH_OWNER' contains Nick Anderson

sys.policy_entry_filename - The first policy file read by the agent (policy entry point)

By default when CFEngine runs it tries to load $(sys.inputdir)/promises.cf so that will normally be the policy entry but if you override the policy to read first using the -f or --file option that file will be the policy entry. This variable can be useful for knowing from within policy if we are executing from an expected location.

For example here we have /tmp/feature-friday-sys.policy_entry_filename.cf:

/tmp/feature-friday-12/sys.policy_entry_filename.cf
bundle agent __main__
{
  reports:
      "The policy entry is not running from a standard location, instead it's running from $(sys.policy_entry_filename)"
        unless => and( or( strcmp( "/var/cfengine/inputs/promises.cf",
                                   "$(sys.policy_entry_filename)" ),
                           strcmp( "/var/cfengine/inputs/update.cf",
                                   "$(sys.policy_entry_filename)" )));
}

And running it we see the message that it’s not running from a standard location.

command
cf-agent -Kf /tmp/feature-friday-12/sys.policy_entry_filename.cf
output
R: The policy entry is not running from a standard location, instead it's running from /tmp/feature-friday-12/sys.policy_entry_filename.cf

sys.policy_entry_dirname - The directory in which the policy entry exists

Similar to sys.policy_entry_filename, sys.policy_entry_dirname expands to the full path to the directory in which the policy entry exists. This can be very useful for referencing additional policy files to load relative to the policy root in a dynamic way. This allows for workflows where you check out a policy set to a temporary location and run it making sure that the policy files loaded are the policy files released to the policy set and not policy files that reside in some standard location.

sys.key_digest - The hash of the executing agents public key

sys.key_digest can be useful for cases where you have assets that are related to a specific host instantiation. It can easily be paired with connection.key in an access promise to ensure that only that host identity has access to those files.

For example, here we have policy that copies host specific files that are stored in a directory named for the key digest from the policy server which shares the directory based on the connecting key.

bundle agent __main__
{
  files:
    "/tmp/my-files/."
      copy_from => remote_dcp( "/srv/host-specific-data/$(sys.key_digest)/.",
                               "$(sys.policy_hub)" ),
      depth_search => recurse( "inf" );
}
bundle server my_access_rules
{
  access:
    policy_server::
      "/srv/host-specific-data/$(connection.key)/."
        admit_keys => { "$(connection.key)" };
}

this

This variables are context dependent but primarily they are useful for referencing things relative to the currently executing bundle.

this.bundle - The current bundle name

this.bundle holds the current bundle name, this is often useful for reports promises to help you identify where the output is coming from.

/tmp/feature-friday-12/this.bundle.cf
bundle agent __main__
{
  methods:
    "paisley";
}
bundle agent paisley
{
  reports: "In bundle $(this.bundle)";
}
command
cf-agent -Kf /tmp/feature-friday-12/this.bundle.cf
output
R: In bundle paisley

this.namespace - The current bundle namespace

this.namespace often pairs well with this.bundle to ensure that you know which bundle is being referred to in the case where namespaces are in use.

/tmp/feature-friday-12/this.namespace.cf
bundle agent __main__
{
  methods:
    "go";
    "CFEngine:go";
}
bundle agent go
{
  reports:
    "Running from $(this.namespace):$(this.bundle)";
}
body file control
{
  namespace => "CFEngine";
}
bundle agent go
{
  reports:
    "Running from $(this.namespace):$(this.bundle)";
}
command
cf-agent -Kf /tmp/feature-friday-12/this.namespace.cf
output
R: Running from CFEngine:go
R: Running from default:go

this.promise_dirname - The current policy files directory

this.promise_dirname is the directory in which the currently executing policy file exists. It’s very useful for referencing assets like templates relative to the current policy file.

For example, here we have /tmp/feature-friday-12/this.promise_dirname.cf:

/tmp/feature-friday-12/this.promise_dirname.cf
bundle agent __main__
{
  reports:
    "This policy is running from $(this.promise_dirname)";
}

Running it we can see that it knows the directory in which it lives.

command
cf-agent -Kf /tmp/feature-friday-12/this.promise_dirname.cf
output
R: This policy is running from /tmp/feature-friday-12

connection

Connection variables are only available to cf-serverd they are useful in controlling which agents a directory is accessible by.

connection.key - The hash of the connecting hosts public key

As mentioned before, combined with sys.key_digest this can be very useful for configuring access rules that allow hosts to get only their own files.

For example:

bundle agent __main__
{
  files:
    "/tmp/my-files/."
      copy_from => remote_dcp(
        "/srv/host-specific-data/$(sys.key_digest)/.",
        "$(sys.policy_hub)"),
      depth_search => recurse( "inf" );
}
bundle server my_access_rules
{
  access:
    policy_server::
      "/srv/host-specific-data/$(connection.key)/."
        admit_keys => { "$(connection.key)" };
}

Happy Friday! 🎉


  1. Announcement of /etc/os-release http://0pointer.de/blog/projects/os-release ↩︎

  2. Blog post about using /etc/os-release for use in loading additional Augments https://cfengine.com/blog/2018/hacking-custom-variables-for-additional-augments-in-cfengine/ ↩︎