Automated server security package watcher in PHP
Tags: vps, php, devops, admin
Published: 2020-07-13 06:26:56

Hi 👋,

I'm back and in this new article, we'll see how to set up a simple yet useful script that watches a (virtual private) server, notifying you (by email) always any security packages are available to be installed.

Also, as a bonus, the script will include a watcher for disk usage rate and whether the server needs a reboot, after any core update. 🙌 This's a very useful way to automate the server monitoring that works like a charm (at least for me 😅)! 

All the code examples given here are gonna be written in `PHP`, however, you should be able to implement it using any other server-side language of your choice. So, feel free to use any other language you feel more comfortable with. If you choose to use a different one, lemme know and I'll be more than happy to also share it here. 🚀

If you're in a hurry (or lazy of reading till the end), just get the [sample file here](https://gist.github.com/rogeriotaques/7ed47ef66631e8455a97c33423c54361). This will give you the file ready to be used. Just don't forget to change the variables' contents.

.
.
.
.
.
.

Wow, glad to see you prefer to keep reading it! 😇

Great! So, let's jump to the funny part ... the code! 😉

The entire script will be written in a single file, let's create a PHP file (I'm calling it `sec-watcher.php`, but you name it as your wish) and add our first lines:

```php
<?php 

/**
 * sec-watcher.php
 * Server security packages watcher.
 */

```

We may want to reuse this file across multiple servers, tho, let's put some variables to hold settings that can be easily changed (or automatically replaced in a CI/CD workflow) when deploying this to our servers.

```php

# Who is sending the notification
$sender = 'admin@domain.tld';

# To who the notification will be sent.
# Wanna notify more people? Simply put multiple addresses
# separated by commas (,).
$notify = 'you@domain.tld';

# Give a name to identify your server. Usually the hostname.
$server = 'myserver.com'; 

# What mounted disk should be checked.
# Use '/dev/disk1s1' when on macOS.
$mountedDisk = '/dev/sda'; 

# What's the maximum usage before sending a notification
# for disk over-usage (don't go too low or too high here).
$diskUsageBoundary = 80; # %

```

Don't forget to replace these variable contents (e.g `admin@domain.tld`, `you@domain.tld`, and `myserver.com`) with values that make sense to you, right? 😉


Next, let's check whether our server needs a reboot. Linux distributions create a file that indicates when the system needs a reboot. We can check for the existence of that file to identify if our machine needs it.

```php
// If this file exist, a reboot is required
$reboot = file_exists('/var/run/reboot-required');
```

Now, let's double-check if our server is running out of space on its disk. For that, we'll need to execute the command `df` and process its return.

We'll be looking for a specific pattern in the results that will contain the disk-usage rate and this will be emailed to you always the value is beyond your defined boundary.

```php
// Gather info about disk usage
$res = exec("df -h | grep {$mountedDisk}");
$res = preg_replace('/\s{2,}/', ' ', $res);

$intUsedRate = 0;
$usedRate    = null;

if (!empty($res) && $res) {
    list($mountPoint, $totalSize, $usedSpace, $freeSpace, $usedRate) = explode(' ', $res);
    $intUsedRate = (int) preg_replace('/[^0-9]/i', '', $usedRate);
}

// Clear used rate when not passed the boundary
if ($diskUsageBoundary > $intUsedRate) {
    $usedRate = null;
}
```

Finally, let's gather the necessary information to determine whether our server has pending security packages to be installed.

Similarly to what happens when a reboot is required, Linux distributions have a special file in the system which is updated with the summary aways the system has packages to be installed, including the security ones.

The approach here will be watching for that file to figure out what we want to know.

```php
// Gather info about available updates
$matches = [];
$note    = @file_get_contents('/var/lib/update-notifier/updates-available');
$note    = preg_replace("/\n/", " ", $note);
```

Great! With all the information we need in place, let's make sure our script really needs to email us ...

```php
// Set a flag to control whether to send or not send a notification
$shouldNotify = (bool) (preg_match("/\d+\supdates?/i", $note, $matches) || !empty($usedRate) || $reboot);
```

Awesome! Our script is almost done.

All we need to do now is test if the notification should be sent or not sent and perform it to finalize. Here you go ...

```php

// Notify when needed
if ($shouldNotify) {
    $headers = "From: {$sender} \n";

    $body = [
        "This is an automated message. \n",
    ];

    $count = (int) preg_replace("/[^0-9]/i", '', $matches[0]);

    if ($count) {
        $body[] = "{$count} pending security updates has been found.";
    }

    if (!empty($usedRate)) {
        $body[] = "{$mountedDisk} is {$usedRate} used. Needs an urgent review.";
        $body[] = "As general it is like: ";

        exec("df -h", $out);
        $body[] = "\t" . implode("\n\t", $out);
    }

    if ($reboot) {
        $body[] = "** A reboot is required. **";
    }

    $body[] = "Please review the '{$server}' server ASAP.";

    // echo implode("\n", $body) . "\n";
    mail($notify, "Server '{$server}' Pending Review", implode("\n", $body) . "\n", $headers, "-f {$sender}");
}
```

And just one more line to communicate we've done: 😅

```php
echo "Daily updates review complete. \n";
```

If you prefer, you can get the [file here] (https://gist.github.com/rogeriotaques/7ed47ef66631e8455a97c33423c54361) 👈

Buuuuut, hold on. The job is not done yet!

.
.
.
.
.

We'll need to properly define the file permissions and schedule a CRON to run that file on a regular basis (let's say once a day?) ... 

Upload the file to a directory of your preference on your server (remember, it doesn't need to be accessible from the web, right?).

Once you've uploaded it, we'll need to give its ownership to the `root` user and also instruct the system that our current (not privileged user) can run it.

So, from a terminal window (connected to your server, via SSH), run the following commands:

```sh
# This will change the ownership
$ sudo chown root:root /path/to/file/sec-watcher.php
```

Then, open the sudo config file (with `visudo`, please) and add an instruction to enable our user to call our file without the need for a password  ...

```sh
$ sudo visudo
```

When the `visudo` file is open, add this line:

```sh
YOUR-USER-HERE   ALL=(ALL) NOPASSWD: /home/YOUR-USER-HERE/path/to/file/sec-watcher.php
```

Don't forget to replace `YOUR-USER-HERE` and `/path/to/file/` with the user you are used to connecting to your server and the actual path to the directory you've chosen.

Finally ... yes, truly 😅 let's finish up by scheduling a CRON job that will call our file on a regular basis. Run the following command in your terminal window (that one connected to your server via SSH):

```sh
$ crontab -e
```

And add the following line ...

```sh
30 6 * * *  PHP -q /home/YOUR-USER-HERE/path/to/file/sec-watcher.php >/dev/null 2>&1
```

Save the file with `CTRL + X` and type `Y`.

Don't forget to replace `YOUR-USER-HERE` and `/path/to/file/` with the user you are used to connecting to your server and the actual path to the directory you've chosen.

That's it! 😎 Now, you just need to welcome the email notifications coming to your inbox, always your server has a package pending to be updated or whenever its disk is getting closer to be full!

I hope it can help you!

Happy coding. 🤘🚀