While working on my master's thesis, I examined amongst other things the open source software Clair. Clair is used for static analysis of vulnerabilities in Docker containers.
When I started looking into the software, I was surprised, that there were no good and simple tutorials on setting up Clair with a Docker registry. To close this gap, I will share my findings. The aim is to provide a quick first look at Clair and how it works.
To show the basic concepts of Clair, we will first set up our environment and then push Docker images into a Registry. After that, we will analyze these images by sending them to the Clair server with the cli-tool klar.

Requirements

When starting this tutorial, you should have docker and docker-compose installed. The setup will work only on a linux or MAC OS.

Structure of the Environment

You can either clone the full project here or follow along the tutorial. If you choose the latter you should create a folder with the following structure:

├── clair_config
│   └── config.yaml
└── docker-compose.yml

In the docker-compose.yml we will be creating all the containers that we need. The config.yaml has the custom config for Clair.

config.yaml

config.yaml is the configuration file for Clair. The only thing I changed from the standard configuration is the connection to the database. You can copy it.

# Copyright 2015 clair authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# The values specified here are the default values that Clair uses if no configuration file is specified or if the keys are not defined.
clair:
  database:
    # Database driver
    type: pgsql
    options:
      # PostgreSQL Connection string
      # https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
      source: host=localhost port=5432 user=postgres password="" sslmode=disable statement_timeout=60000

      # Number of elements kept in the cache
      # Values unlikely to change (e.g., namespaces) are cached to save prevent needless round trips to the database.
      cachesize: 16384

  api:
    # API server port
    port: 6060

    # Health server port
    # This is an unencrypted endpoint useful for load balancers to check to the healthiness of the clair server.
    healthport: 6061

    # Deadline before an API request will respond with a 503
    timeout: 900s

    # 32-bit URL-safe base64 key used to encrypt pagination tokens
    # If one is not provided, it will be generated.
    # Multiple clair instances in the same cluster need the same value.
    paginationkey:

    # Optional PKI configuration
    # If you want to generate client certificates and CAs easily, try the following projects:
    # https://github.com/coreos/etcd-ca
    # https://github.com/cloudflare/cfssl
    servername:
    cafile:
    keyfile:
    certfile:

  updater:
    # Frequency the database will be updated with vulnerabilities from the default data sources
    # The value 0 disables the updater entirely.
    interval: 2h

  notifier:
    # Number of attempts before the notification is marked as failed to be sent
    attempts: 3

    # Duration before a failed notification is retried
    renotifyinterval: 2h

    http:
      # Optional endpoint that will receive notifications via POST requests
      endpoint:

      # Optional PKI configuration
      # If you want to easily generate client certificates and CAs, try the following projects:
      # https://github.com/cloudflare/cfssl
      # https://github.com/coreos/etcd-ca
      servername:
      cafile:
      keyfile:
      certfile:

      # Optional HTTP Proxy: must be a valid URL (including the scheme).
      proxy:

docker-compose.yml

After that, we come to the interesting part, our containers!

version: '3'

services:
  postgres:
    container_name: clair_postgres
    image: postgres:latest
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: ""
    network_mode: host

  clair:
    container_name: clair_clair
    image: quay.io/coreos/clair:latest
    restart: unless-stopped
    depends_on:
      - postgres
    volumes:
      - /tmp:/tmp
      - ./clair_config:/config
    command: [-config, /config/config.yaml]
    network_mode: host 

  registry:
    image: registry:latest
    network_mode: host

We are using three containers in this tutorial. First, we have a PostgreSQL database, where the data about vulnerabilities and the containers we tested is stored.
Next one is Clair. The config is copied in per volume. Each container has the standard configuration.
Last but not least we are adding a docker-registry to the mix. Here we will store the images we want to test.
For this quick and first look we will use "network_mode: host". With this all containers are in the standard networks of your computer and we have no routing problems. Normally you would put them in their own network, but this brings other obstacles, which would make the tutorial too complicated.

Start the Environment

Hereafter we start our setup with the typical docker-compose command. We also set the -d tag, so the containers are started in the background.

$ docker-compose up -d

After starting Clair, the software starts downloading vulnerability information into the database. The download takes some time so it can happen that Clair hasn't downloaded the information for a particular vulnerability yet. If that is the case, it will find zero vulnerabilities while testing, which is why I recommend waiting... in order to get the real result.

Copy a Docker Image to the Registry

$ docker pull nginx:latest
$ docker tag nginx localhost:5000/nginx-test
$ docker push localhost:5000/nginx-test

You can see your pushed images and if the push was successful on http://localhost:5000/v2/_catalog.

Install klar

For sending images from our registry to Clair we need an additional program called klar. Klar is a CLI tool for interacting with a docker registry and clair.
First, download the binary from their GitHub page, then copy it to a folder in your path. Make sure that the downloaded binary can be executed. I renamed the download to klar and copied it to /usr/local/bin.

$ cp klar /usr/local/bin

Analyze the Image for Vulnerabilities

After installing klar, we can finally analyze our docker images for weaknesses!
You have to specify several parameters for the klar command. First the address of our Clair container. Then some stuff on how much information we want. After that, we set REGISTRY_INSECURE=TRUE, so we have no problems with our unsafe registry. At the end, we specify the image we want to test.

$ CLAIR_ADDR=http://localhost:6060 CLAIR_OUTPUT=Low CLAIR_THRESHOLD=10 REGISTRY_INSECURE=TRUE klar localhost:5000/nginx-test

The Output should look something like this:

clair timeout 1m0s
docker timeout: 1m0s
no whitelist file
Analysing 3 layers
Got results from Clair API v1
Found 88 vulnerabilities
Unknown: 6
Negligible: 34
Low: 16
Medium: 23
High: 9
....
# Here will be printed a list with the description of the found vulnerabilities:
....

As you can see nginx has some problems at the time of writing this blog post.

Summary

Now you can analyze as many images as you want. I hope you liked our first look at Clair and how to use it. In a later blog post, we'll discuss how to integrate Clair in a more real-life scenario.
You can find the full source code here.