Module of the Week: jmcdonagh/clamav - Manage ClamAV and the freshclam service

Purpose Comprehensive clamav and freshclam classes
Module jmcdonagh/clamav
Puppet Version 2.6+
Platforms Ubuntu 10.04 LTS+

ClamAV is an open-source scanning engine for malware, virus, and trojan detection. It is often used in conjunction with an MTA such as Postfix. It comes with a built-in service for AV signature updates called freshclam.

This module is intended to offer a comprehensive interface for Puppet to configure both freshclam and clamav services. Each class takes many parameters, all of which are down-cased versions of clamav or freshclam configuration options. Both the clamav and clamav::freshclam classes are also intended to be easily removed from a system, by setting their $ensure parameters to ‘absent’.

Installing the module

Complexity Easy
Installation Time 5 minutes

This module is on Puppet Forge and can be installed with puppet’s module sub-command:

puppet module install jmcdonagh/clamav

Note that if you run this as a regular user, the module will be installed in your home directory. If you are root, it will probably go into /etc/puppet/modules.

Module and Resource Overview

First, you should either check out the code from GitHub (https://github.com/thesilentpenguin/puppet-clamav), or install the module with the module tool as outlined above and browse the code with your favorite editor. I recommend having the manifest open while I walk you through the code.

Let’s have a look at the clamav base class in manifests/init.pp. Scroll down to the beginning of the class and you will immediately notice that there are many parameters. These parameters were taken from the clamav man pages on Ubuntu 10.04 LTS systems, and down-cased to maintain status quo code style.

After all the parameters, we’ve got some sanity checks. We’ll fail the catalog compilation (using the fail() function) if we get passed any bizarre values or we’re trying to compile a catalog for an incompatible system. One of my stock checks is a minimum OS version. This is a pretty good practice for numerous reasons. For one, it helps consumers know where your code is intended to be used. That is absolutely crucial if you will be supporting this code. Before someone breaks a Solaris system using a Debian-only manifest, the compilation simply fails and outputs a clear error.

After the sanity checks, various variables are set based on the value of $ensure. This will alleviate the need to use selectors inside resource definitions, which can lead to hard-to-read and thus hard-to-understand code. Here is the code that I am referring to which sets all of these variables based on $ensure:

   if ($ensure == "present") {
  	$file_notify    = Service["clamav-daemon"]
  	$file_require   = Package["clamav-daemon"]
  	$svc_before	= undef
  	$svc_enable	= "true"
  	$svc_ensure	= "running"
  	$svc_require    = Package["clamav-daemon"]
   } else {
  	$file_notify    = undef
  	$file_require   = undef
  	$svc_before	= Package["clamav-daemon"]
  	$svc_enable	= "false"
  	$svc_ensure	= "stopped"
  	$svc_require    = undef
   }

You may have noticed that some of these variables appear to be relationship targets. This is because I always attempt to include the ability to remove all of a class’ resources properly. You’ll notice that, if removing the clamav class by setting $ensure to ‘absent’, the resource requirements go the opposite direction of the way they go with $ensure set to ‘present’. You might get away with using $ensure in this manner and not reversing relationships, but for me it’s not worth taking that chance, and I just do it right the first time.

Now onto the actual resources. The clamav and freshclam classes are what I call FPS classes. That is, File-Package-Service classes. What I mean by this is that there is a Package to install, configuration File(s) to set up, and a Service to manage. The Service and File(s) Require(s) the Package, and the File(s) Notify the Service. This paradigm is likely common to you if you have been using Puppet for some time now.

So first you’ll notice clam’s configuration file. This is a template that references all the class parameters to configure clamav. It uses some of the variables defined above to set up relationships and notify the service.

Next is the Package resources, which include a couple of extras. I include the dev packages because a lot of my work involves the rubygem ecosystem. This means that when I have a requirement for software on a system, I often need the -dev packages to compile extensions. Remember, now that you have parameterized classes at your disposal, it is trivial to add boolean features to your modules such as ‘$dev_packages = “true”’. That could serve as an option to include or not include the management of the -dev packages.

Finally we have the service. Not much here, mostly all configured via the variables defined based on $ensure. All our resources look nice and compact thanks to that variable setting stanza.

The clamav::freshclam class is also an FPS class. It is similar in the sense that it has every freshclam configuration option down-cased as a class parameter. The only noticeable difference between the two classes is the actual clamav and freshclam configuration options.

Testing the module

This module is most easily tested by using ‘include clamav’, which will by default set up clamav on the current system.

puppet apply -e ‘include clamav’ clamav/manifests/init.pp

If you do this on an incompatible system, you should see something like this:

Your OS (Darwin) is not supported by this code! at /etc/puppet/modules/clamav/manifests/init.pp:104 on node goldmember.microcosm.thesilentpenguin.com

After this successfully finishes on a compatible system, you should be able to see the clamav daemon running:

[~] > sudo service clamav-daemon status
 * clamd is running

You can test the config by scanning a test file by using the clamdscan program:

[~] > clamdscan website.erb
/home/jmcdonagh/website.erb: OK

----------- SCAN SUMMARY -----------
Infected files: 0
Time: 0.017 sec (0 m 0 s)

The freshclam class can be tested in the same way.

Configuring the module

Complexity Easy
Installation Time 5 minutes

The module configuration should be sane by default. Every parameter available in the Ubuntu 10.04 LTS repo version of clam should be available. The default values for the various parameters should match the defaults in Ubuntu 10.04 LTS. If you need to tweak any setting whatsoever, simply look up the configuration option in the clam man page, down-case all upper-case letters, and pass that as a parameter to the class. For example:

class {
   “clamav”:
      ensure    => “present”,
      logsyslog => “true”;
}

Would set up clamav with the LogSyslog clamav configuration option set to true.

Example usage

The same usage outlined above for testing is a typical use case of clamav. Simply enable those classes in the node definition and you will have a working clam setup, ready to check for malicious content coming in through e-mail. You could even potentially integrate clam with other services, like scanning user uploads to your custom webapp. Freshclam will update from the canonical repo every day with the default settings, ensuring you have the latest virus definitions.

Conclusion

To me, this module is an ideal example of module design. It is small in scope, yet as complete as possible of an interface to the clam configuration. This allows for you to easily set up clamav and freshclam on a node with nearly no tweaking or external dependencies necessary. If you end up needing some tweaking, as you have seen it is trivial to make small changes to clam with this module. It also has some basic compatibility checks to ensure your consumers know where this module will work.

This module would probably work on Debian 6, but I haven’t tested. To add Red Hat support some variables for the package and service name(s) would probably have to be added, and all of my gigs that use clam are on Ubuntu 10.04 LTS.

Additional Resources

Leave a comment