In a past article we discussed using systemd timer units to schedule jobs and their pros and cons over crond. In today's article we are examining another unit file called path units. Systemd path units allow you to monitor files and directories (paths) for an event. Once the event triggers, systemd can execute a script via a service unit. Let's create an example configuration.

Creating a Systemd Path Unit

In this example we will create a path unit to send an email alert if the /etc/passwd file changes on our system. In order to create a functional path unit we need three files.

  • A script that sends the email alert
  • Service unit to launch the script when an event is observed
  • Path unit to monitor the file or directory

Create a Script for Email Alerts

Let's start by creating a simple script that will send an email alert to our administrators. Create a file called email-alert.sh in the /usr/local/bin/ directory with the following contents.

#!/bin/bash
mail -S sendwait -s "ETC PASSWD CHANGED ON $(hostname)" [email protected] < /etc/passwd

NOTE: This assumes your system is configured correctly to send email and you have mailx installed.

Now we have to make the script executable by running chmod, like so:

$ chmod +x email-alert.sh 

Create Service Unit File to Execute Script

The service unit will be responsible for running the script when the path unit observes changes to the file. In this example, we are creating the service unit with only the basic options for simplicity.

Create a file called passwd-mon.service in the /etc/systemd/system/ directory with the following contents:

[Unit] 
Description="Run script to send email alert"

[Service]
ExecStart=/usr/local/bin/email-alert.sh

There are many other options for a service unit which is outside the scope of this tutorial.

Create a Path Unit File

The last file needed to complete our configuration is the path unit file itself. The unit file needs to be created in the same directory as it's corresponding service file, /etc/systemd/system in this case. It should also have the same name, but with a .path extension.

Create a file called passwd-mon.path in the /etc/systemd/system/ directory with the following contents.

[Unit]
Description="Monitor the /etc/passwd file for changes"

[Path]
PathModified=/etc/passwd
Unit=passwd-mon.service

[Install]
WantedBy=multi-user.target

The contents of the[Path] section is the meat and potatoes of the path unit. PathModified tells the unit to watch for changes to the file. The Unit declaration tells it which service to activate if a change occurs. WantedBy basically sets a dependency resulting in our timer starting when the system has reached the multi-user target (runlevel 3).

Available Path Options for Monitoring Files & Directories

We chose to use PathModified for our path unit, but other options exist. Here is an overview of the other options available.

  • PathExists - If file/directory exists activate the unit.
  • PathExistsGlob - If at least one file/directory exists that matches the specified globbing pattern.
  • PathChanged - If file/directory changes activate the unit.
  • PathModified - Similar to PathChanged, but watches for simple writes.
  • DirectoryNotEmpty - If directory contains at least one file activate the unit.

There are three additional options that can be used in the [Path] stanza.

  • Unit - Unit to activate when above path events trigger.
  • MakeDirectory - Create directory to be monitored (boolean)
  • DirectoryMode - Use specified mode to create directories (octal notation).

Verify the Syntax of Your Unit Files

You can use the systemd-analyze command with the verify option to check the correctness of your unit files. It will help point out any syntax errors.

$ sudo systemd-analyze verify /etc/systemd/system/passwd-mon.*

If you have any fatal errors, they will be highlighted red.

Start Your systemd Path Unit

You are now ready to start your path unit and begin monitoring your files. You can start it with systemctl, just like a normal service.

$ sudo systemctl start passwd-mon.path

Now we check the status and ensure it is running without any issues.

$ sudo systemctl status passwd-mon.path
● passwd-mon.path - "Monitor the /etc/passwd file for changes"
   Loaded: loaded (/etc/systemd/system/passwd-mon.path; disabled; vendor preset: disabled)
   Active: active (waiting) since Sat 2020-01-25 13:30:20 EST; 6s ago

Jan 25 13:30:20 putor systemd[1]: Started "Monitor the /etc/passwd file for changes".

Enabling Your systemd Path Unit at Boot

Enabling your path unit to start on boot is the done the same way you enable services at boot, like so:

$sudo systemctl enable passwd-mon.path

Testing Your New Path Unit

Now we have our new path unit running and monitoring our file. If a user is added, deleted, or other changes made to a users account (change home dir, default shell, etc) the /etc/passwd file will be updated. The path unit will detect the change to the file, which in turn will trigger the service unit.

Let's add a user and see what happens.

[savona@putor ~]$ date
Sat 25 Jan 2020 02:27:12 PM EST
[savona@putor ~]$ sudo useradd test

We can see in the logs that the service unit executed successfully.

Jan 25 14:27:19 putor systemd[1]: Started "Run script to send email alert".
Jan 25 14:27:19 putor systemd[1]: passwd-mon.service: Succeeded.

We can also see in the postfix logs, that the email sent successfully.

Jan 25 14:27:19 putor postfix/pickup[1491]: D25F02E15BB: uid=0 from=<root>
Jan 25 14:27:19 putor postfix/cleanup[4293]: D25F02E15BB: message-id=<20200125192719.D25F02E15BB@putor>
Jan 25 14:27:19 putor postfix/qmgr[1492]: D25F02E15BB: from=<root@putor>, size=3826, nrcpt=1 (queue active)
Jan 25 14:27:20 putor postfix/smtp[4294]: D25F02E15BB: to=<*********@********.net>, relay=mail.******.net[*.*.*.*]:465, delay=0.64, delays=0.05/0.01/0.43/0.14, dsn=2.0.0, status=sent (250 OK id=1ivR5U-002kSc-Cq)
Jan 25 14:27:20 putor postfix/qmgr[1492]: D25F02E15BB: removed

Looks like everything went off without a hitch.

Checking Your Path Unit Logs with Journalctl

One of the advantages of using systemd path (or timer) units is that they operate like normal services. This means you can control them with systemctl. In addition their logs are available in the systemd journal via journalctl.

To check the logs generated by your path unit, use journalctl with the -u option.

[savona@putor ~]$ sudo journalctl -u passwd-mon.path
-- Logs begin at Fri 2019-11-08 00:08:01 EST, end at Sun 2020-01-26 02:43:59 EST. --
Jan 25 13:10:57 putor systemd[1]: Started "Monitor the /etc/passwd file for changes".
Jan 25 13:30:00 putor systemd[1]: passwd-mon.path: Succeeded.
...OUTPUT TRUNCATED...

You can also use journalctl to check on your .service unit as well. For more information on using journalctl read "Viewing logs with journaltcl".

Conclusion

In this example we set up a systemd path unit to monitor the /etc/passwd file for changes. We created a service unit that fires off a script to send an email when the file changes.

In all of my testing this seems to work great. However, keep in mind that although I chose to use the /etc/passwd file as an example, I would not recommend this as a security feature. There are products out there designed for file integrity checking. For example, aide is a file integrity checker designed specifically for that purpose. It is far better equipped to be used as a security tool.

There are some real world examples of when these would come in handy. For example, I run a script every 5 minutes (via cron) that looks in a folder for a file it can process. I would probably be better off using a path unit for that job.

Resources and Links