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.
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:
- One to run the Clamd daemon and the freshclam tool (to update the virus database) as a recurring job. (https://github.com/mko-x/docker-clamav)
- One to connect to the network socket of the daemon and expose a rest API we could easily use. (https://github.com/solita/clamav-rest)
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!
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 :)