File integrity monitoring with CFEngine

Posted by Nick Anderson
December 13, 2022

File integrity monitoring is an important aspect in managing your infrastructure. Tripwire and AIDE are often cited as necessary tools by compliance frameworks1,2,3. Of course CFEngine can manage a file to make sure it contains desired content, but did you know that CFEngine also has the capability to simply monitor a file for change? In this blog post we take a look at CFEngines’ changes attribute for files promises.

File promises, changes body

To monitor a file for change in CFEngine you must have a files promise with a changes body attached.

For example:

files:
  "/etc/motd"
    changes => default:detect_all_change;

Here we promise to watch /etc/motd and detect “all changes”. We can look at the documentation for changes bodies and the definition of body changes detect_all_change to see what it really means.

Using cf-locate from core/contrib makes it easy to find the definition of a body or bundle:

$ cf-locate "detect_all_change$" -f | ansi2txt
-> body or bundle matching 'detect_all_change$' found in /home/nickanderson/.cfagent/inputs/lib/files.cf:2054
body changes detect_all_change
# @brief Detect all file changes using the best hash method
#
# This is fierce, and will cost disk cycles
#
{
      hash           => "best";
      report_changes => "all";
      update_hashes  => "yes";
}

We can see from the definition that it will use the best available hashing algorithm (md5 on community, sha512 on enterprise) to hash the files content, that it will report all changes (options include all, stats, content, none) and that it will update hashes. This update of hashes is something that is common in CFEngine use patterns but atypical for other File Integrity Monitoring tools. Setting the value of update_hashes to false will result in CFEngine warning loudly about the differences each time it evaluates the file for change until the database is updated. The attribute report_diffs which is not included in files.cf above is specific to CFEngine Enterprise and defaults to false. When enabled, diffs are reported, letting us visualize the change(s) in Mission Portal.

A practical example

Now that we know how to express the desire to monitor a file for change, let’s check out how it functions in practice.

I added the following example policy which monitors /etc/motd for changes if it exists and emits information in inform_mode (when cf-agent is run with -I , --inform, or --log-level info, which is typically only when run manually):

bundle agent example_fim
{
  files:
      "/etc/motd"
        changes => default:my_fim,
        classes => default:results( "bundle", "FIM_etc_motd"),
        if => fileexists( "/etc/motd" );

  reports:
    inform_mode.FIM_etc_motd_reached::
      "Promise to monitor /etc/motd for change was reached";

    inform_mode.FIM_etc_motd_repaired::
      "Promise to monitor /etc/motd for change was repaired";
}
body changes my_fim
# @brief Detect file content changes using sha256
# and report the diff to CFEngine Enterprise
{
      hash           => "sha256";
      report_changes => "all";
      report_diffs   => "true";
      update_hashes  => "yes";
}

Running the policy manually we can see what the output looks like when a file is first noticed:

$ sudo cf-agent -KIf update.cf; sudo cf-agent -KI
    info: Stored sha512 hash for '/etc/motd' (SHA=18edda964ea45642cb610982df47e34e1d6fd1e23f7a1c811305902c90ca53e7dda634ed51945c080aca93fd8122768835328a94c08f72809620c80826bd4a8a)
    info: Wrote stat information for '/etc/motd' to database
R: Promise to monitor /etc/motd for change was reached
R: Promise to monitor /etc/motd for change was repaired

Since the file changes information was updated it’s seen as a promise repaired. Subsequent runs of the policy simply indicate the promise was reached but no change was detected:

$ sudo cf-agent -KIf update.cf; sudo cf-agent -KI
R: Promise to monitor /etc/motd for change was reached

Modifications are reported

Now, I’ll rewrite the content of /etc/motd and run the policy again to see what the output looks like when a change is observed:

sudo bash -c 'cat << EOF > /etc/motd
You are accessing a U.S. Government (USG) Information System (IS) that is provided for USG-authorized use only.

By using this IS (which includes any device attached to this IS), you consent to the following conditions:

-The USG routinely intercepts and monitors communications on this IS for purposes including, but not limited to, penetration testing, COMSEC monitoring, network operations and defense, personnel misconduct (PM), law enforcement (LE), and counterintelligence (CI) investigations.

-At any time, the USG may inspect and seize data stored on this IS.

-Communications using, or data stored on, this IS are not private, are subject to routine monitoring, interception, and search, and may be disclosed or used for any USG-authorized purpose.

-This IS includes security measures (e.g., authentication and access controls) to protect USG interests--not for your personal benefit or privacy.

-Notwithstanding the above, using this IS does not constitute consent to PM, LE or CI investigative searching or monitoring of the content of privileged communications, or work product, related to personal representation or services by attorneys, psychotherapists, or clergy, and their assistants. Such communications and work product are private and confidential. See User Agreement for details.
EOF'
$ sudo cf-agent -KIf update.cf; sudo cf-agent -KI
    info: Hash 'sha256' for '/etc/motd' changed!
    info: Updated sha256 hash for '/etc/motd' (SHA=998e7013530b712ebfc901d5e3279815b08534637dd52d080f7419ac042b2aa8)
    info: Recorded integrity changes in '/etc/motd'
    info: Reported on file changes to '/etc/motd'
    info: Copied file '/etc/motd' to '/etc/motd_cfchanges.cfnew' (mode '600')
    info: Backed up '/etc/motd_cfchanges' as '/etc/motd_cfchanges.cfsaved'
    info: Moved '/etc/motd_cfchanges.cfnew' to '/etc/motd_cfchanges'
  notice: Last modified time for '/etc/motd' changed 'Mon Dec 12 20:36:41 2022' -> 'Mon Dec 12 20:43:25 2022'
    info: Recorded mtime changes in '/etc/motd'
    info: Wrote stat information changes for '/etc/motd' to database
R: Promise to monitor /etc/motd for change was reached
R: Promise to monitor /etc/motd for change was repaired

Examine reports and details in Mission Portal

After a few minutes we can visit the File integrity monitoring report in Mission Portal:

Accessing the File integrity monitoring report File integrity monitoring report

Next we can adjust the filters to show only the file we are interested in:

Filter to limit files to /etc/motd

And we can adjust the types of change we wish to see, for example let’s show only file integrity monitoring reports that contain DIFF:

Filter to limit change type to DIFF

And finally, hovering over the Change details we are offered the opportunity to show the diff:

Hover over change details column to expose show diff button

Clicking the button we see a diff of the changes made to the file:

Diff of change to /etc/motd