Skip to content
Logo Theodo

How to Implement an Antivirus API in 10 min

Clément Pasteau3 min read

I recently had to allow customers to upload files on a website, then send their content to an external API.
We had a few requirements for the files to be valid and one of them was to ensure they were checked for any virus before posting their content to the API.

Our infrastructure

Our stack was a React frontend and a Django Backend, hosted on AWS Elastic Beanstalk.
The backend was mainly designed as a proxy for all the requests that the frontend wanted to make with the external API, which means we would not be storing any of the files uploaded by the customer. We only needed to analyse the stream of the files.

ClamAV-1-1024x604

We also needed to be sure that the solution would work for our development environment alongside our validation and our production platforms.
The go-to solution was to use Docker Images.
Not only could we have a quick installation for our local environments but we could use the EBS Docker configuration to setup our instances easily.

In terms of AntiVirus, ClamAV revealed itself as the only one we could use easily and for free.
We then chose 2 Docker images:

ClamAV-2-1-1024x546

Configuration

Our docker-compose.yml file looked like this for our local environment:

version: '2'

services:
  clamav-server:
    image: mkodockx/docker-clamav
  clamav-rest:
    image: lokori/clamav-rest
    links:
      - clamav-server
    environment:
      CLAMD_HOST: clamav-server
  backend:
    build: .
    command: python /code/manage.py runserver
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    links:
      - clamav-rest

Note: these images did not need any open ports because they would be called directly from your backend instance. However, the REST image needed to have the ClamaAV server as a link and the backend needed to have access to the REST!

We could replicate the same configuration as a multi-container docker configuration within AWS EBS.
Here is our Dockerrun.aws.json:

{
  "AWSEBDockerrunVersion": 2,
  "containerDefinitions": [
    {
      "name": "clamav-server",
      "image": "mkodockx/docker-clamav",
      "essential": true,
      "memory": 1024
    },
    {
      "name": "clamav-rest",
      "image": "lokori/clamav-rest",
      "essential": true,
      "memory": 512,
      "links": [
        "clamav-server:clamav-server"
      ],
      "portMappings": [
        {
          "hostPort": 8080,
          "containerPort": 8080
        }
      ],
      "environment" : [
          { "name" : "CLAMD_HOST", "value" : "clamav-server" }
      ]
    }
  ]
}

Note: we went for a t2.small for the instance because the daemon and freshclam used a lot of memory when updating. (below 1GB caused us problems)

Make it rain!

Then we could use our instance with its private IP to post files on the port 8080!

curl

In python, we could analyse the file sent from the frontend:

files = {'file': file.name}
data = {'name': file.name}
response = requests.post('http://%s:8080/scan' % settings.CLAMAV_HOST, files=files, data=data)

if not 'Everything ok : true' in response.text:
    logger.info('File %s is dangerous, preventing upload' % file.name)
    raise UploadValidationException('Virus found in the file')

Note: the rest API is returning ‘Everything ok : true’ with what seems to be a new line at the end of the string.
CLAMAV_HOST was our instance private IP on our staging and production platform, it was ‘clamav-rest’ locally.

Conclusion

It took us a few days to investigate all the possible solutions and come up with this configuration.
This not only allows you to have a fast solution but also a reliable one thanks to ElasticBeanstalk.
I hope it will help anyone who needs to have a quick implementation of an antivirus :)

Liked this article?