DEV Community

Cover image for Set up a private ClamAV database mirror with a Tor-based local SOCKS5 proxy
Timofey Chuchkanov
Timofey Chuchkanov

Posted on • Edited on

Set up a private ClamAV database mirror with a Tor-based local SOCKS5 proxy

WARNING! NONE OF THE PERFORMED CONFIGURATIONS CONSIDER SPECIFIC PROJECTS' SECURITY REQUIREMENTS. USE THIS ARTICLE AS A SECONDARY

SOURCE OF KNOWLEDGE ONLY AND HARDEN YOUR SYSTEMS AS APPROPRIATE.

*upd 12/26/2024: Dev Community formatting has gone nuts, I will eventually rewrite all or some of my blog posts on my personal website. Stay tuned to get the link.

Contents

Preface

If you are an active computer user, you probably often get a lot of new files from the outside world. The environment is harsh, and it's quite easy to infect a device with malware. To reduce the chances of being infected, it's a good practice to scan everything with special software. For small files that don't contain any confidential info, services like VirusTotal can be utilized. For other data, a standalone or a Live CD-based antivirus application should be preferred.

One of the most popular Free and Open Source applications of this kind is ClamAV by Cisco Talos. The tool is the de facto standard antivirus for UNIX-like systems (both on servers and clients).

To be able to find threats, antivirus software needs a special database of virus signatures. These signatures are continuously created by large teams of security specialists.

Why set up a private mirror?

Well, there might be lots of reasons. The most significant one coming straight to mind is to reduce network traffic and to increase the database update speed for clients.

The decision to host a private virus signature database mirror for ClamAV at my Homelab emerged because I use the tool a lot, and it's not available in my region.

I encourage you to try out this small project even for your Homelab, because it is really easy!

Setting up a server

What tools are used

Since the aforementioned Homelab is based on the Proxmox VE hypervisor, everything is set up there, in a container.

In this scenario, freshclam is used for fetching database files to a mirror server. It's not possible to serve CDIFF files using this method. Consider cvdupdate if you need to reduce network bandwidth consumption even more during clients' database updates.

What Tool
Hypervisor Proxmox VE 7.3-4
Guest Fedora Linux 37 (Container Image)
Antivirus (mirror) ClamAV 0.103.7
Anonymous communication Tor 0.4.7.13

Creating an LXC container on Proxmox

  1. Select your storage pool for VM disks and container templates, go to CT Templates and click on the Templates button.

    Image description

  2. Find the latest Fedora version in the list and download it.

    Image description

  3. Click on the Create CT button at the top of the Proxmox dashboard and follow the wizard. It's ok to designate 1 CPU core to the container, but it needs sufficient RAM to update databases. Set the limit to something around 4096 MiB.

  4. Don't forget to set an option to start the container at boot.

    Image description

  5. Start up the container and connect to its console.

A local Tor proxy (SOCKS5) with obfs4 bridges

In countries where Tor is censored, bridges can be used.

Some operating systems' repositories don't provide pre-built packages for obfs4, but the tool can be easily compiled from source code. If you are interested in a quick guide on how to compile obfs4, let me know!

Install Tor

Thankfully, Fedora has everything we need in its repositories <3.

# dnf install tor obfs4
Enter fullscreen mode Exit fullscreen mode

Configure Tor

  1. Locate the obfs4proxy binary

    $ which obfs4proxy
    

    Example
    [deathroll@ClamAV-DB ~]$ which obfs4proxy
    /usr/bin/obfs4proxy
    [deathroll@ClamAV-DB ~]$
    

  2. Open the /etc/tor/torrc config file with an editor of choice. Mine is vim.

    # vim /etc/tor/torrc
    
  3. Next, add the following two lines:

    ClientTransportPlugin obfs4 exec /usr/bin/obfs4proxy
    UseBridges 1
    

    👆 Replace the path after exec with what you got in step 1.

  4. Now, get some bridges!
    Go to https://bridges.torproject.org/options/, select obfs4, and click on Get Bridges.

    Image description

    In order to get a list of bridges, complete a captcha. When the list is displayed, just copy it as-is.

    Image description

  5. Paste what you've just copied to the torrc file and prefix each line with Bridge. Here's how it will look in the file (the output was modified due to privacy concerns):

    [deathroll@ClamAV-DB ~]$ grep -i ^bridge /etc/tor/torrc 
    Bridge obfs4 <some.ip.address.here:port> <ALONGSTRINGOFSOMEJIBBERISH> cert=<ONEMORELONGSTRINGOFSOMEJIBBERISH> iat-mode=<number>
    Bridge obfs4 <some.ip.address.here:port> <ALONGSTRINGOFSOMEJIBBERISH> cert=<ONEMORELONGSTRINGOFSOMEJIBBERISH> iat-mode=<number>
    [deathroll@ClamAV-DB ~]$
    
  6. After saving and closing the config file, perform a test connection by executing tor as toranon.

    $ sudo -u toranon tor
    
  7. In case of success, kill the process with Ctrl + C, and enable and start the service.

    # systemctl enable --now tor
    
  8. Check the status with the following command:

    # systemctl status tor
    

ClamAV with automatic database updates

Install ClamAV

# dnf install clamav clamd clamav-update
Enter fullscreen mode Exit fullscreen mode

Configure freshclam

  1. Open the /etc/freshclam.conf file and add the following:

    HTTPProxyServer socks5://127.0.0.1
    HTTPProxyPort 9050
    
  2. Enable database compression (optional).

    CompressLocalDatabase yes
    
  3. Also, you can enable logging (optional). For more, read freshclam.conf(5).

    1. Add this to the config:

      UpdateLogFile /var/log/freshclam.log
      LogTime yes
      LogSyslog yes
      
    2. Create the log file.

      # touch /var/log/freshclam.log
      
    3. And change its owner to clamupdate.

      # chown clamupdate: /var/log/freshclam.log
      
  4. After that, locate the clamav-freshclam service file.

    $ find /etc/systemd/ -name '*clam*'
    

    Example
    [root@ClamAV-DB deathroll]# find /etc/systemd/ -name '*clam*'
    /etc/systemd/system/multi-user.target.wants/clamav-freshclam.service
    [root@ClamAV-DB deathroll]#
    

  5. Open the file you've just found and append tor.service to the lines that begin with Wants and After so they look like this:

    Wants=network-online.target tor.service
    After=network-online.target tor.service
    
  6. Reload the systemd manager configuration.

    # systemctl daemon-reload
    
  7. Enable and start the clamav-freshclam service.

    # systemctl enable --now clamav-freshclam
    
  8. Check the status with the following command:

    # systemctl status clamav-freshclam
    

Web server with HTTPS

In this example, we use NGINX, but you're free to experiment.

Prepare a directory for serving

  1. Create a new directory. For me it is /srv/cvd.

    # mkdir -p /srv/cvd
    
  2. Find where the database is located.

    # find / -name clamav -xtype d |& grep -iv permission
    

    👆 -xtype d makes find search for directories only. |& pipes both stdout and stderr to grep. Here we are just filtering the output to remove read errors—don't worry, they are insignificant.

    In my case, the dir is /var/lib/clamav.

    Example
    [deathroll@ClamAV-DB nginx]$ sudo find / -name clamav -xtype d |& grep -iv permission
    /usr/share/licenses/clamav
    /var/lib/clamav
    [deathroll@ClamAV-DB nginx]$
    

    Without grep filtering
    [deathroll@ClamAV-DB nginx]$ sudo find / -name clamav -xtype d
    find: ‘/proc/tty/driver’: Permission denied
    /usr/share/licenses/clamav
    /var/lib/clamav
    find: ‘/dev/.lxc/sys/kernel’: Permission denied
    find: ‘/dev/.lxc/sys/power’: Permission denied
    find: ‘/dev/.lxc/sys/class’: Permission denied
    find: ‘/dev/.lxc/sys/devices’: Permission denied
    find: ‘/dev/.lxc/sys/dev’: Permission denied
    ---OUTPUT TRIMMED---
    

  3. Edit /etc/fstab to set a read-only bind mount from the database dir to the newly created one.

    /var/lib/clamav/    /srv/cvd    none    bind,ro    0   0
    
  4. Reload the systemd manager configuration.

    # systemctl daemon-reload
    
  5. Try to mount /srv/cvd.

    # mount /srv/cvd
    
  6. Check the mountpoint.

    $ findmnt /srv/cvd
    

    Example
    [deathroll@ClamAV-DB nginx]$ findmnt /srv/cvd
    TARGET   SOURCE                                                 FSTYPE OPTIONS
    /srv/cvd NAS/Virtualization/subvol-1000-disk-0[/var/lib/clamav] zfs    ro,noatime,xattr,posixacl
    [deathroll@ClamAV-DB nginx]$
    

Install NGINX

# dnf install nginx
Enter fullscreen mode Exit fullscreen mode

Copy certificates

In order to be able to use HTTPS, it's required to have valid certificates. Refer to this article to learn how to create a trusted self-signed certificate.

  1. Create a directory for certificates in a preferred location.

    # mkdir /etc/ssl/www
    
  2. Copy a chain of trust file and a key file to the newly created directory.

Configure NGINX

In this example, it's assumed that a DNS A record exists for the container's static IP address. The record name is cvd.deathroll.internal. It's also possible to implement HTTPS without DNS, though not recommended.

The configuration is split up into multiple files for ease of administration, but this is not required.

  1. Create a file with shared SSL/TLS configuration for server blocks. Choose a filename suitable for you.

    # vim /etc/nginx/ssl_params.conf
    
    1. Add at least two things: locations for the chain of trust and key files.

      ssl_certificate     "/etc/ssl/www/fullchain.pem";
      ssl_certificate_key "/etc/ssl/www/deathroll-internal-key.pem";
      
    2. Change any other options at your discretion. For example:

      ssl_prefer_server_ciphers on;
      ssl_protocols TLSv1.2 TLSv1.3;
      
  2. Change the default server block in the /etc/nginx/nginx.conf file to the following:

    server {
        listen      80;
        server_name _; # A catch-all server name for handling requests to any host.
    
        location / {
            return 301 https://$host$request_uri; # Redirect to HTTPS.
        }
    }
    
  3. Create a file for a <domain> HTTPS server block in /etc/nginx/conf.d.

    vim /etc/nginx/conf.d/cvd.deathroll.internal.conf
    

    And populate it with contents similar to these:

    server {
        listen      443 ssl;
        server_name cvd.deathroll.internal;
    
        root        /srv/cvd; # This is the bind mount directory you have created earlier.
    
        include     /etc/nginx/ssl_params.conf; # Include shared TLS/SSL parameters.
    
        location / {
            autoindex on; # Enable directory listing.
        }
    }
    
  4. Create a file with a server block for denying requests to any host. For example, /etc/nginx/conf.d/deny-conn-by-ip.conf. Add something like this:

    server {
        listen      443 default_server; # Make this server default for port 443 to be able to catch unwanted requests.
        server_name _;
    
        include     /etc/nginx/ssl_params.conf;
    
        location / {
            return 403;
        }
    }
    

    👆 From what I could infer from inspecting packets, observing NGINX behavior, and reading its documentation, when NGINX receives a request, it checks the Host HTTP header to decide what virtual host should process the request.

    Image description

    The catch-all (_) server for port 443 has a default_server parameter, so HTTPS requests to any Host will be processed by this virtual host. If this parameter is omitted, any other virtual server for this port may become the default server, and hence requests won't be denied.

  5. Reload NGINX config.

    # systemctl reload nginx
    
  6. Check NGINX status.

    # systemctl status nginx
    

Enabling firewall

In this example, firewalld is used.

Install firewalld

# dnf install firewalld
Enter fullscreen mode Exit fullscreen mode

Configure firewalld

  1. Enable and start the service

    # systemctl enable --now firewalld
    
  2. Add ports. If you use SSH, add it as well.

    # firewall-cmd --permanent --add-port={80,443}/tcp
    
  3. Reload firewalld

    # firewall-cmd --complete-reload
    

Configuring clients

Disable database compression (optional) and add a private mirror in the freshclam.conf file, so these options look something like this:

deathroll@fdesk:~ % egrep -i '^(private|compress)' /usr/local/etc/freshclam.conf
CompressLocalDatabase no
PrivateMirror https://cvd.deathroll.internal
deathroll@fdesk:~ % 
Enter fullscreen mode Exit fullscreen mode

The result

After the setup is complete, what we have is a Fedora 37 container with a local Tor-based SOCKS5 proxy that is used for updating the ClamAV database. On top of this, we have an NGINX web server that acts as a private mirror that allows clients to list and download database files for ClamAV. This web server redirects all requests to HTTPS and denies requests to any host different from the server's FQDN.

Deny a request to an IP address
Image description

Directory listing
Image description

Update the ClamAV database on a client
Image description

Top comments (0)