Skip to content

feat: Add ClamAV container with automatic config wiring for PHP 8.4 and 8.5 - #410

Open
chris-snyder-totara wants to merge 1 commit into
totara:masterfrom
chris-snyder-totara:407-clamav-support
Open

chris-snyder-totara wants to merge 1 commit into
totara:masterfrom
chris-snyder-totara:407-clamav-support

Conversation

@chris-snyder-totara

@chris-snyder-totara chris-snyder-totara commented Aug 25, 2026 •

Copy link
Copy Markdown

Description

Adding ClamAV support to docker-dev so that we can test antivirus on Totara file uploads.

Testing Instructions

1. Check out this pull request

2. Get the images

The clamav container is the stock upstream image, so it is simply pulled. The PHP images are ours though, and they now contain clamdscan:

tbuild php-8.4

After the patch is released, an ordinary upgrade just needs a pull instead:

tpull

3. Start it, from your site directory

tup clamav

Signatures are baked into the image, so there is no large download. Wait for the healthcheck to go green — expect (healthy), usually under a minute:

docker ps --filter name=totara_clamav --format '{{.Names}} {{.Status}}'

4. Confirm the plumbing

texec php sh -c 'ls -l /run/clamav/clamd.sock; clamdscan --ping 1'

Expect a socket listing and PONG. If clamdscan is missing here, your PHP image is stale — redo step 1.

5. Confirm Totara enabled the plugin

Run from your site's server/ directory:

texec php php -r 'define("CLI_SCRIPT",true); require "config.php"; echo "antivirus active: ", var_export(\core\antivirus\manager::get_antivirus("clamav")->is_configured(), true), "\n";'

Expect antivirus active: true.

6. Confirm it actually blocks a virus

This writes the EICAR test file (the industry-standard harmless AV test string, not real malware) and runs it through Totara's own scan path:

texec php php -r 'define("CLI_SCRIPT",true); require "config.php"; $f=sys_get_temp_dir()."/eicar.txt"; file_put_contents($f, base64_decode("WDVPIVAlQEFQWzRcUFpYNTQoUF4pN0NDKTd9JEVJQ0FSLVNUQU5EQVJELUFOVElWSVJVUy1URVNULUZJTEUhJEgrSCo=")); try { \core\antivirus\manager::scan_file($f,"eicar.txt",true); echo "FAIL: virus not detected\n"; } catch (\core\antivirus\scanner_exception $e) { echo "PASS: virus detected and blocked\n"; } @unlink($f);'

Expect PASS: virus detected and blocked.
Then repeat through the UI: save that EICAR string as a .txt file on your host and upload it via any file picker. The upload should be rejected with a virus warning. This is the step that proves real web uploads are covered, since those land in the container's /tmp rather than the shared dataroot.

7. Confirm it stays out of the way when off

tdown clamav

Reload a page and re-run step 4 — expect false, and uploads to behave exactly as before. Also worth spot-checking a PHP 8.3 site while clamav is running: it should also report false, since the socket is only mounted on 8.4/8.5.

Known behaviours, not bugs

  • tdown clamav leaves the container as Exited (137). The upstream image does not forward SIGTERM to clamd, so docker kills it. Harmless — and because it also means clamd never removes its socket file, config-after.php probes for a live listener rather than trusting the file to be gone.
  • The antivirus settings in Site administration are greyed out. They are forced from config-after.php by design.

Checklist

  • Does what the author says it will do
  • Testing instructions are provided
  • Commit messages make sense and follow the conventional commit standard
  • No identified security issues
  • No identified maintenance issues
  • Any third-party libraries/dependencies use the MIT or Apache 2.0 license
  • Changes made are backwards compatible and will not break existing setups
  • Changes to scripts in the bin/ directory run correctly on both MacOS and WSL
  • Changes to containers can be built locally sucessfully (e.g. via tbuild container && tup container)
  • Containers/images are compatible with both AMD64 (Windows) and ARM64 (MacOS)
  • Changes made to config.php are compatible with our oldest supported Totara version, our newest Totara version, and Moodle

Comment thread clamav/Dockerfile Outdated
@chris-snyder-totara
chris-snyder-totara force-pushed the 407-clamav-support branch 2 times, most recently from 58ae857 to c5b0dac Compare August 26, 2026 05:01
@chris-snyder-totara

Copy link
Copy Markdown
Author

Rebased on master.

@codyfinegan
codyfinegan force-pushed the 407-clamav-support branch 2 times, most recently from faa44af to c37cba6 Compare October 8, 2026 19:53
@codyfinegan

Copy link
Copy Markdown
Member

Checked against the stock clamav/clamav:stable-debian13-slim image (/etc/clamav/clamd.conf, /init, clamdcheck.sh) and the antivirus_clamav plugin. The stack was not started.

Commandline mode with clamdscan --fdpass is the right choice. The plugin's unixsocket method sends nSCAN <path>, which would need clamd to read the PHP container's filesystem, and tcpsocket makes is_configured() return false.

Issues

  • clamav/clamd.conf replaces the whole stock file, not just the socket settings. The stock file also sets User clamav, LogFile /var/log/clamav/clamd.log and LogTime yes, so clamd now runs as root. The "Only the socket settings differ from the image default" comments in clamav/clamd.conf and compose/clamav.yml are wrong.
  • The socket only works because clamd runs as root. /init creates /run/clamav (clamav:clamav 775) only if [ ! -d "/run/clamav" ]. The named volume means the directory always exists, so it stays root:root 755. With User clamav restored, clamd could not create the socket. "creating /run/clamav is already handled by the stock entrypoint" is wrong. Root is fine for dev, but the comment should say so, or restore User clamav and fix the directory ownership.
  • The healthcheck does not use the unix socket. clamdcheck.sh runs echo PING | nc localhost 3310. The comment in compose/clamav.yml is wrong, and TCPSocket 3310 is needed by the healthcheck, not "for ad-hoc debugging only". Removing it based on that comment would make the container unhealthy.
  • StreamMaxLength does nothing here. It only limits INSTREAM. With --fdpass clamd scans the passed fd, so MaxFileSize / MaxScanSize apply. The "Must stay above upload_max_filesize" note should point at MaxFileSize. Uploads are 64M and the default MaxFileSize should be 100M (not checked in the image), so nothing breaks today.
  • 'tries' => 1 is not a Totara setting. It is not in the plugin's settings.php or scanner.php. Looks like a Moodle setting. Should be removed.

Minor

  • "the image already logs to stdout" is not what the stock config does, it logs to a file. Without LogFile, it is not clear what clamd writes to stdout. Worth checking docker logs totara_clamav after a scan.
  • The stale socket comment in config-after.php is right: PID 1 is tail -f /dev/null, which ignores SIGTERM, so docker stop waits 10s and kills it. The entrypoint unlinks /run/clamav/clamd.sock on start, so restarts are fine.
  • The README line does not say how to start ClamAV, or that the first start downloads signatures and can take minutes.

Fine

  • clamav-socket is declared in both compose files, Compose merges them.
  • PHP < 8.4 never mounts the socket, so the probe is false and nothing changes.
  • Socket 0666 in a 755 directory lets www-data connect.
  • COPY config/clamd.conf runs after the apt install, so it is not overwritten.
  • The Enchant commit looks fine.

@chris-snyder-totara

Copy link
Copy Markdown
Author

Everything should be addressed - running as root inside a container should be fine for this dev tool, so just documented that part.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants