Docker Image Scanner for Vulnerabilities With Clair

I’m gonna tell you how you can add a step in your CI pipeline to check if the Docker image you’re build contains vulnerabilities or not.

Pre-requisites

I assume you’ve Docker installed on your system. We’re gonna use Jenkins but is optional.

Clair

Clair is an open source project for the static analysis of vulnerabilities in appc and docker containers. It’s been developed by CoreOS.

Vulnerabilities Database

Clair relay on Postgres to keep the database. We’ve two different options here. We can create a new Postgres database which will be initializes when Clair starts or we can use a database already provisioned. 

If you want to try the first option keep in mind it takes a while to feed the database (10~15 minutes depends on network).

We’re using the second option here. Go to a terminal and run this command:

$ docker network create ci
$ docker volume create --name clair-postgres
$ docker run --detach \
   --name clair-postgres \
   --publish 5432:5432 \
   --net ci \
   --volume clair-postgres:/var/lib/postgresql/data \
   arminc/clair-db:latest

We just have created a new network to Clair can resolve the database by its name, a volume and a Docker container. After a couple seconds we can check the database is up and ready.

$ docker logs --tail 1 clair-postgres 
2019-05-15 13:36:00.068 UTC [1] LOG:  database system is ready to accept connections

Clair Service

Now it’s time to run the service. But first, a little configuration is needed. We must set the database for Clair in the config file. Run the following command:

$ curl --silent https://raw.githubusercontent.com/nordri/config-files/master/clair/config-clair.yaml | sed "s/POSTGRES_NAME/clair-postgres/" > config.yaml

We’re changing the string POSTGRES_NAME by clair-postgres which is the name we’ve gave to the Postgres container. Now, we can launch the Clair container by running:

$ docker run --detach \
  --name clair \
  --net ci \
  --publish 6060:6060 \
  --publish 6061:6061 \
  --volume ${PWD}/config.yaml:/config/config.yaml \
  quay.io/coreos/clair:latest -config /config/config.yaml

And that is.

Launch an scanner

It’s time to check everything is working properly. To run the Clair scanner I’ve just built a container with the latest release. You can found it here and then build the image using this Dockerfile.

FROM debian:jessie

COPY clair-scanner_linux_amd64 /clair-scanner
RUN chmod +x /clair-scanner

I already did so you can use this image: nordri/clair-scanner.

Let’s check some images now. One of Clair limitations is it cannot scan remote images. All images must be locals. To scan an image just launch

$ docker run -ti \
  --rm \
  --name clair-scanner \
  --net ci \
  -v /var/run/docker.sock:/var/run/docker.sock \
  nordri/clair-scanner:latest /bin/bash

Now we’re inside the container and we’re able to launch the scanner:

# export IP=$(ip r | tail -n1 | awk '{ print $9 }')
# /clair-scanner --ip ${IP} --clair=http://clair:6060 debian:jessie

If we launch that as it, we’ll see an endless list of vulnerabilities, which is more noise than something useful. So, we can filter by severity using this flag:

-t, --threshold="Unknown"             CVE severity threshold. Valid values; 'Defcon1', 'Critical', 'High', 'Medium', 'Low', 'Negligible', 'Unknown'

We can choose only check for critical or higher and then the list will be much more gentle because all the vulnerabilities will be treat as approved, and, which is much more interested, the command will exit with 0. Then we can add this to a CI pipeline.

Jenkins Integration

If you’ve Jenkins running in your infrastructure, you’ll be probably interested in check the images you’re delivering to your customers.

This simple Jenkinsfile can solve the problem

node {

   docker.image('docker').inside('-v /var/run/docker.sock:/var/run/docker.sock') {
      
       stage ('Checkout') {
         checkout scm
       }

       stage ('Build Docker image') {
           // Build docker image
           // docker build... DOCKER_IMAGE
       }
   }

   docker.image('nordri/clair-scanner').inside('--net ci') {

       stage ('Security scanner') {
           sh '''
             IP=$(ip r | tail -n1 | awk '{ print $9 }')
             /clair-scanner --ip ${IP} --clair=http://clair:6060 --threshold="Critical" DOCKER_IMAGE
           '''
       }
   }
}

As we can see, we’re using a container to build the image and another one to scan that image for vulnerabilities, we set the threshold to Critical so only really big problem will made the pipeline to fail.

References

This entry was posted in Docker, Jenkins, Seguridad and tagged , , , . Bookmark the permalink.