Deploy ClamAV on Upsun, on one-time scan

Deploy ClamAV on Upsun, on one-time scan

February 14, 2025· Mickael Gaillard
Mickael Gaillard
·Reading time: 8 minutes

What is ClamAV?

ClamAV (Clam AntiVirus) is an open-source antivirus engine designed for detecting malwares, viruses, and other malicious threats. It is widely used for scanning file servers, and web applications. ClamAV is known for its lightweight nature and ability to be integrated into various security systems.

Logo ClamAV

Why integrate ClamAV into your web application?

Integrating ClamAV into a web application, provides several security benefits:

  • Protect Uploaded Files: If your application allows users to upload files, scanning them for viruses ensures that no malicious files enter your system.
  • Regulatory Compliance: Many industries require businesses to scan files for malware as part of cybersecurity compliance.
  • Prevent System Contamination: Detecting and removing infected files early prevents malware from spreading across your infrastructure.
  • Increase User Trust: Ensuring that files are clean enhances trust in your platform, especially in cloud-based environments like Upsun.

Using ClamAV with service mode vs one-time scan mode

There are two possible (and cumulative) policy approaches to file verification with ClamAV:

  • One-time scan mode: This mode of ClamAV allows the user to scan an entire file tree without distinction. A pro is that all files can be scanned, however this does take a considerably long amount of time to process all the verifiers - which some may consider to be a con.
  • Service mode: This mode of ClamAV allows the user to scan specific files on demand. eg. the user uploads 3 new files and scans only these. A pro is that this process is very fast, due to the limitation of files to be processed, however a con is that it also requires communication between the application and the antivirus.

This tutorial covers how to run ClamAV in one-time scan mode.

Setting up ClamAV

Add ClamAV binary

To use ClamAV on Upsun, you need to install the ClamAV binaries.
Let’s use Upsun composable-image to add ClamAV to our application container:

.upsun/config.yaml
1
2
3
4
5
6
7
8
applications:
  clamav:
    source:
      root: "/"

    # Composable image feature
    stack:
      - "clamav"    # <= ClamAV binaries https://search.nixos.org/packages?channel=24.11&show=clamav

Add ClamAV configuration

After adding the ClamAV binaries, you need to configure ClamAV. This involves modifying two configuration files to add on the reposotory project, each corresponding to one of the two main commands:

graph LR;
    Upsun-->ClamAV;
    ClamAV-->freshclam(freshclam);
    ClamAV-->clamav(clamscan);
    freshclam(freshclam)-->freshclam.conf([freshclam.conf]);
    clamav(clamscan)-->clamd.conf([clamd.conf]);

This includes updating the virus database (aka freshclam).

etc/freshclam.conf
# Log section
LogFileMaxSize 5M
LogTime yes
LogRotate yes

# Database upsate
DNSDatabaseInfo current.cvd.clamav.net
DatabaseMirror db.local.clamav.net
DatabaseMirror database.clamav.net
MaxAttempts 5

# Configure path
DatabaseDirectory /app/var/lib
NotifyClamd /app/var/etc/clamd.conf

As well as the configuration of ClamAV itself:

etc/clamd.conf
# Log section
LogFile /app/var/log/clamav.log
LogFileMaxSize 5M
LogTime yes

# Configure path
DatabaseDirectory /app/var/lib

The storage locations for the virus database and the configuration files have been modified to adapt them to Upsun. These modifications were made by adding DatabaseDirectory and NotifyClamd to the mount points with write permissions.

Add mount endpoint for storage

Since ClamAV needs to write to the disk, we must add appropriate mount points with the necessary write permissions.

.upsun/config.yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
applications:
  clamav:
    source:
      root: "/"

    stack:
      - "clamav"

    mounts:
      # Mount of data to scan.
      'data':
        source: storage
        source_path: data
      # Mount of configuration/database for ClamAV.
      'var':
        source: storage
        source_path: var

Additionally, these mount points must have a specific directory structure.

            • To do this, let’s combine everything into a shell script to set up this structure (on the reposotory project), that will be executed during each deployment to ensure the structure’s existence.

              scripts/clam_install.sh
              #!/usr/bin/env bash
              # -*- coding: utf-8 -*-
              
              echo "Prepare folder for clamav..."
              mkdir -p \
                  "${PLATFORM_APP_DIR}/var/log" \
                  "${PLATFORM_APP_DIR}/var/lib" \
                  "${PLATFORM_APP_DIR}/var/etc"
              
              echo "Move config on mount..."
              cp "${PLATFORM_APP_DIR}/etc/clamd.conf" "${PLATFORM_APP_DIR}/var/etc/"

              You will note the use of the built-in variable PLATFORM_APP_DIR to define the application’s default path.

              And let’s add execution permissions to the script.

              Terminal
              chmod +x scripts/clam_install.sh

              This script can only be executed once the mount points are mounted, which is done in Upsun during the deployment step.
              To automate this, we will add the script call to the deployment hooks.

              .upsun/config.yaml
               1
               2
               3
               4
               5
               6
               7
               8
               9
              10
              11
              12
              13
              14
              15
              16
              17
              18
              19
              20
              21
              
              applications:
                clamav:
                  source:
                    root: "/"
              
                  stack:
                    - "clamav"
              
                  mounts:
                    'data':
                      source: storage
                      source_path: data
                    'var':
                      source: storage
                      source_path: var
              
                  # Hook to create sctructure folder for ClamAV
                  hooks:
                    deploy: |
                      set -eu
                      ./scripts/clam_install.sh        

              Deploying ClamAV on Upsun

              We need to push these additions to the Upsun project. (If you don’t have one, create a project on Upsun.)

              Terminal
              git add .
              git commit -m "Add ClamAV on the project"
              git push

              Finally, ClamAV requires a minimum set of resources, so we need to fine-tune the resources allocated to ClamAV:

              • Disk : > 512Mb
              • Memory : > 1.5Gb

              Test the ClamAV integration on Upsun

              After the deployment, test that everything works as expected by opening an SSH connection:

              Terminal
              upsun ssh

              Let’s start by triggering the update of ClamAV’s virus database.

              SSH on Upsun
              web@clamav.0:~/$ freshclam --config-file="${PLATFORM_APP_DIR}/etc/freshclam.conf"
              
              Mon Feb 17 15:50:25 2025 -> ClamAV update process started at Mon Feb 17 15:50:25 2025
              Mon Feb 17 15:50:25 2025 -> daily database available for download (remote version: 27552)
              Time:    1.1s, ETA:    0.0s [========================>]   61.56MiB/61.56MiB
              Mon Feb 17 15:50:28 2025 -> Testing database: '/app/var/lib/tmp.3f810acf2f/clamav-0bcf78864fd8e25406deda53ee394581.tmp-daily.cvd' ...
              Mon Feb 17 15:50:43 2025 -> Database test passed.
              Mon Feb 17 15:50:43 2025 -> daily.cvd updated (version: 27552, sigs: 2072975, f-level: 90, builder: raynman)
              Mon Feb 17 15:50:43 2025 -> main database available for download (remote version: 62)
              Time:    4.4s, ETA:    0.0s [========================>]  162.58MiB/162.58MiB
              Mon Feb 17 15:50:59 2025 -> Testing database: '/app/var/lib/tmp.3f810acf2f/clamav-86fc871948269630ae1178e89bbe3e82.tmp-main.cvd' ...
              Mon Feb 17 15:51:17 2025 -> Database test passed.
              Mon Feb 17 15:51:17 2025 -> main.cvd updated (version: 62, sigs: 6647427, f-level: 90, builder: sigmgr)
              Mon Feb 17 15:51:17 2025 -> bytecode database available for download (remote version: 335)
              Time:    0.3s, ETA:    0.0s [========================>]  282.94KiB/282.94KiB
              Mon Feb 17 15:51:17 2025 -> Testing database: '/app/var/lib/tmp.3f810acf2f/clamav-e5e2c8f801634258bc2f8d8119dbdd44.tmp-bytecode.cvd' ...
              Mon Feb 17 15:51:17 2025 -> Database test passed.
              Mon Feb 17 15:51:17 2025 -> bytecode.cvd updated (version: 335, sigs: 86, f-level: 90, builder: raynman)

              To check that it’s working as expected, let’s create a file that simulates an infected file:

              SSH on Upsun
              web@clamav.0:~/$ echo 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' > \
                  "${PLATFORM_APP_DIR}/data/folder2scan/fake.txt"

              Now, let’s run a scan:

              SSH on Upsun
              web@clamav.0:~/$ clamscan  \
                  --database="${PLATFORM_APP_DIR}/var/lib" \
                  --log="${PLATFORM_APP_DIR}/var/log/scan.log" \
                  --move="${PLATFORM_APP_DIR}/data/quarantine" \
                  --recursive \
                  "${PLATFORM_APP_DIR}/data/folder2scan"
              
              Loading:    20s, ETA:   0s [========================>]    8.70M/8.70M sigs       
              Compiling:   6s, ETA:   0s [========================>]       41/41 tasks 
              
              /app/data/scan/fake.txt: Eicar-Signature FOUND
              /app/data/scan/fake.txt: moved to '/app/data/quarantine/fake.txt'
              
              ----------- SCAN SUMMARY -----------
              Known viruses: 8704755
              Engine version: 1.3.2
              Scanned directories: 1
              Scanned files: 1
              Infected files: 1
              Data scanned: 0.00 MB
              Data read: 0.00 MB (ratio 0.00:1)
              Time: 28.929 sec (0 m 28 s)
              Start Date: 2025:02:17 15:57:55
              End Date:   2025:02:17 15:58:24

              Automating updates and scans

              In order to automate the database updates and the scans, we will be grouping all the necessary commands into shell scripts to make their usage easier:

              scripts/clam_update-db.sh
              #!/usr/bin/env bash
              # -*- coding: utf-8 -*-
              
              echo "Update Virus database..."
              freshclam --config-file="${PLATFORM_APP_DIR}etc/freshclam.conf"
              scripts/clam_scan.sh
              #!/usr/bin/env bash
              # -*- coding: utf-8 -*-
              
              echo "Trigger scan by one-time call..."
              clamscan  \
                  --database="${PLATFORM_APP_DIR}/var/lib" \
                  --log="${PLATFORM_APP_DIR}/var/log/scan.log" \
                  --move="${PLATFORM_APP_DIR}/data/quarantine" \
                  --recursive \
                  "${PLATFORM_APP_DIR}/data/folder2scan"

              Now let’s add execution permissions to the script.

              Terminal
              chmod +x scripts/*.sh

              Then, automate these step by setting up some CRON jobs:

              .upsun/config.yaml
               1
               2
               3
               4
               5
               6
               7
               8
               9
              10
              11
              12
              13
              14
              15
              16
              17
              18
              19
              20
              21
              22
              23
              24
              25
              26
              27
              28
              29
              30
              31
              32
              33
              34
              35
              
              applications:
                clamav:
                  source:
                    root: "/"
              
                  stack:
                    - "clamav"
              
                  mounts:
                    'data':
                      source: storage
                      source_path: data
                    'var':
                      source: storage
                      source_path: var
              
                  # Hook to create sctructure folder for ClamAV
                  hooks:
                    deploy: |
                      set -eu
                      ./scripts/clam_install.sh        
              
                  # Trigger by CRON (case one-time)
                  crons:
                    update-db:
                      spec: '0 0 * * *'
                      commands:
                        start: ./scripts/clam_update-db.sh
                        stop: pkill freshclam
              
                    scan-files:
                      spec: 'H * * * *'
                      commands:
                        start: ./scripts/clam_scan.sh
                        stop: pkill clamscan

              Finally, commit everything:

              Terminal
              git add .
              git commit -m "Add automation (CRONs)"
              git push

              Extra feature

              You can use runtime-operation provided by Upsun to trigger a new scan:

              Declare runtime operation

              .upsun/config.yaml
               1
               2
               3
               4
               5
               6
               7
               8
               9
              10
              11
              12
              13
              14
              15
              16
              17
              18
              19
              20
              21
              22
              23
              24
              25
              26
              27
              28
              29
              30
              31
              32
              33
              34
              35
              36
              37
              38
              39
              40
              41
              42
              43
              
              applications:
                clamav:
                  source:
                    root: "/"
              
                  stack:
                    - "clamav"
              
                  mounts:
                    'data':
                      source: storage
                      source_path: datat
                    'var':
                      source: storage
                      source_path: var
              
                  # Hook to create sctructure folder for ClamAV
                  hooks:
                    deploy: |
                      set -eu
                      ./scripts/clam_install.sh        
              
                  # Trigger by CRON (case one-time)
                  crons:
                    update-db:
                      spec: '0 0 * * *'
                      commands:
                        start: ./scripts/clam_update-db.sh
                        stop: pkill freshclam
              
                    scan-files:
                      spec: 'H * * * *'
                      commands:
                        start: ./scripts/clam_scan.sh
                        stop: pkill clamscan
              
                  # Trig event for one-time/service
                  operations:
                    trigg_clamav:
                      role: viewer
                      commands:
                        start: |
                          ./scripts/clam_scan.sh              

              Call runtime operation

              Then, trigger this runtime operation from your development machine (or any other environment with access to the Upsun CLI).

              Terminal
              upsun operation:run trigg_clamav --project <PROJECT_ID> --environment <ENVIRONMENT_NAME>

              Conclusion

              By following this guide, you have learned how to set up ClamAV on Upsun, utilizing features like Upsun composable-image and deployment hooks to automate updates and scans. This approach not only strengthens your infrastructure’s security but also enhances user trust, especially in cloud environments like Upsun. With proper configuration and automation scripts, ClamAV can be a powerful tool for maintaining system integrity while remaining lightweight and easy to integrate.


              Project on our Github Upsun

              Last updated on