Clamd antivirus TCP client

Example how to use clamd TCP to rest client in order to secure your uploads with a scalable manner, written in golang and distribute as container

Some time ago we at Peopleperhour published a Clamd container and now for the sake of learning some GO code and as a POC, I am writing about how to create a clamd client to expose a subset of the clams server TCP commands.

The client code can be found in this git repo of clam-rest-api/overview and the distributable container can be found in dockerhub.

The distributable container is only ==2MB== and when decompressed it will take up to ==5M== of disk space. It is stateless and easily scale, just add more instances of this client.

Goal of this client

I followed a simple guide on how to install and configure GO into my system, I am not going to get into more details cause it is not in this scope.

I thought first to try and understand what is that I want to accomplish with this client. > The goal is to publish some subset of the Clamd TCP commands so that you do not have to deal with the TCP commands :D.

you can use the lower level TCP commands if you like. I found instructions about them here

==This project is intended for proof of concept and not for production use==

Designing the functionality

What i wanted was for this client to function as a very simple service with the only external dependency been the antivirus server, so no RDBMS to keep track of scanned files, no sophisticated queuing mechanism, no persistence of the internal submitted jobs, in a few words this is a proof of concept

The implementation of the actual Clamd TCP client i found a very nice existing golang package of go-clamd and just used it cause it seems much better than I could have done :D

Also for the Clamd server, I am using the one I have created for Peopleperhour, you can use whatever other servers you like. To note here the actual implementation of the production Peopleperhour TCP client is very different and not in GO.

I think that its best to present the functionality as sequence diagram how to use it

Example usage of container

The easier way to start the client is by issuing the following command

$ docker run --rm -it \
   -e CLAM_SERVER=XXX.XXX.XXX.XXX \
   alexsapran/clam-rest-api

You only have to specify the CLAM_SERVER to point to the Clamd TCP server and the CLAM_PORT with default to 3310.

Endpoints

The default web controller is listening on port 6000

Root

This is only to check that the web part is up. This should not be used as a health check.

Path : /
Method: GET
Response status: 200
Response body:
    Clam TCP to rest client

Healthcheck

This is the health check that a load balancer should check. It validates that the server can indeed execute a remote TCP ping to the Clamd server.

Healthy example:

Path : /health
Method: GET

Response headers:
    HTTP/1.1 200 OK
    Content-Type: application/json
    Date: Fri, 10 Mar 2017 10:54:30 GMT
    Content-Length: 2
Response body:
    ok

Unhealthy example:

Path : /health
Method: GET

Response headers:
    HTTP/1.1 503 Service Unavailable
    Content-Type: text/plain; charset=utf-8
    X-Content-Type-Options: nosniff
    Date: Fri, 10 Mar 2017 10:55:05 GMT
    Content-Length: 20
Response body:
    Service Unavailable

Version

Get information about the remote clam server version

Path : /version
Method: GET

Response headers:
    HTTP/1.1 200 OK
    Content-Type: application/json
    Date: Fri, 10 Mar 2017 10:53:42 GMT
    Content-Length: 102

Response body:
  {
    "command": "version",
    "message": "ClamAV 0.99.2/22558/Fri Nov 18 11:16:33 2016",
    "file": "",
    "metadata": ""
  } 

Stats

Get statistics of the clam server

Path : /stats
Method: GET

Response headers:
    HTTP/1.1 200 OK
    Date: Fri, 10 Mar 2017 10:56:51 GMT
    Content-Length: 267
    Content-Type: text/plain; charset=utf-8

Response body:
  {
    "Pools": "1",
    "State": "STATE: VALID PRIMARY",
    "Threads": "THREADS: live 1  idle 0 max 15 idle-timeout 120",
    "Memstats": "MEMSTATS: heap 5.098M mmap 0.129M used 3.765M free 1.336M releasable 0.055M pools 1 pools_used 417.095M pools_total 417.112M",
    "Queue": "QUEUE: 0 items"
  }

Scan

Scan a remote file from a URI. This operation has the logic of submitting a file to be scanned and when complete (in a separate go routine) then the application will send back to a notify url the results.

Path : /scan?file=...&pingback=...
Method: GET

Response headers:
    HTTP/1.1 200 OK
    Content-Type: application/json
    Date: Fri, 10 Mar 2017 11:00:30 GMT
    Content-Length: 171

Response body:
  {
    "command":"scan",
    "message":"Queued",
    "file":"....",
    "metadata":""
  }

When the file is scanned you will get the results in your pingback url that defined in the submit of the file.

The results will send as a POST. Example :

  {
    "command":"scan",
    "message":"Eicar-Test-Signature",
    "file":".....160329143458_eicar.com.zip",
    "metadata":""
  }

Alexandros Sapranidis

Software engineer, keen on wearing many hat, current Senior Software Engineer @Elastic cloud

Athens, Greece http://sapranidis.gr