What is Clair

Clair is an application for parsing image contents and reporting vulnerabilities affecting the contents. This is done via static analysis and not at runtime.

Clair supports the extraction of contents and assignment of vulnerabilities from the following official base containers:

  • Ubuntu
  • Debian
  • RHEL
  • Suse
  • Oracle
  • Alpine
  • AWS Linux
  • VMWare Photon
  • Python

The above list defines Clair's current support matrix.

Architecture

Clair v4 utilizes the ClairCore library as its engine for examining contents and reporting vulnerabilities. At a high level you can consider Clair a service wrapper to the functionality provided in the ClairCore library.

diagram of clairV4 highlevel architecture

The above diagram expresses the separation of concerns between Clair and the ClairCore library. Most development involving new distributions, vulnerability sources, and layer indexing will occur in ClairCore.

How Clair Works

Clair's analysis is broken into three distinct parts.

Indexing

Indexing starts with submitting a Manifest to Clair. On receipt, Clair will fetch layers, scan their contents, and return an intermediate representation called an IndexReport.

Manifests are Clair's representation of a container image. Clair leverages the fact that OCI Manifests and Layers are content-addressed to reduce duplicated work.

Once a Manifest is indexed, the IndexReport is persisted for later retrieval.

Matching

Matching is taking an IndexReport and correlating vulnerabilities affecting the manifest the report represents.

Clair is continually ingesting new security data and a request to the matcher will always provide you with the most up to date vulnerability analysis of an IndexReport.

How we implement indexing and matching in detail is covered in ClairCore's documentation.

Notifications

Clair implements a notification service.

When new vulnerabilities are discovered, the notifier service will determine if these vulnerabilities affect any indexed Manifests. The notifier will then take action according to its configuration.

Getting Started

At this point you'll probably want to check out Getting Started With Clair.

How Tos

The following sections provide instructions on accomplish specific goals in Clair.

Getting Started With Clair

Releases

All of the source code needed to build clair is packaged as an archive and attached to the release. Releases are tracked at the github releases.

The release artifacts also include the clairctl command line tool.

Official Containers

Clair is officially packaged and released as a container at quay.io/projectquay/clair. The latest tag tracks the git development branch, and version tags are built from the corresponding release.

Running Clair

The easiest way to get Clair up and running for test purposes is to use our local dev environment

If you're the hands on type who wants to get into the details however, continue reading.

Modes

Clair can run in several modes. Indexer, matcher, notifier or combo mode. In combo mode, everything runs in a single OS process.

If you are just starting with Clair you will most likely want to start with combo mode and venture out to a distributed deployment once acquainted.

This how-to will demonstrate combo mode and introduce some further reading on a distributed deployment.

Postgres

Clair uses PostgreSQL for its data persistence. Migrations are supported so you should only need to point Clair to a fresh database and have it do the setup for you.

We will assume you have setup a postgres database and it's reachable with the following connection string: host=clair-db port=5432 user=clair dbname=clair sslmode=disable. Adjust for your environment accordingly.

Starting Clair In Combo Mode

At this point, you should either have built Clair from source or have pulled the container. In either case, we will assume that the clair-db hostname will resolve to your postgres database.

You may need to configure docker or podman networking if you are utilizing containers. This is out of scope for this how-to.

A basic config for combo mode can be found here. Make sure to edit this config with your database settings and set "migrations" to true for all mode stanzas. In this basic combo mode, all "connstring" fields should point to the same database and any *_addr fields are simply ignored. For more details see the config reference and deployment models

Clair has 3 requirements to start:

  • The mode flag or CLAIR_MODE environment variable specifying what mode this instance will run in.
  • The conf flag or CLAIR_CONF environment variable specifying where Clair can find its configuration.
  • A yaml document providing Clair's configuration.

If you are running a container, you can mount a Clair config and set the CLAIR_CONF environment variable to the corresponding path.

CLAIR_MODE=combo
CLAIR_CONF=/path/to/mounted/config.yaml

If you are running a Clair binary directly, its likely easiest to use the command line.

clair -conf "path/to/config.yaml" -mode "combo"

Submitting A Manifest

The simplest way to submit a manifest to your running Clair is utilizing clairctl. This is a CLI tool capable of grabbing image manifests from public repositories and submitting them for analysis. The command will be in the Clair container, but can also be installed locally by running the following command:

go install github.com/quay/clair/v4/cmd/clairctl@latest

You can submit a manifest to ClairV4 via the following command.

$ clairctl report --host ${net_address_of_clair} ${image_tag}

You will need to add the config flag if you are using a PSK authentication (as in the local dev environment setup, for example).

$ clairctl report --config local-dev/clair/config.yaml --host ${net_address_of_clair} ${image_tag}

By default, clairctl will look for Clair at localhost:6060 or the environment variable CLAIR_API, and for a configuration at config.yaml or the environment variable CLAIR_CONF.

If everything is configured correctly, you should see some output like the following informing you of vulnerabilities affecting the supplied image.

$ clairctl report ubuntu:focal
ubuntu:focal found bash        5.0-6ubuntu1.1         CVE-2019-18276
ubuntu:focal found libpcre3    2:8.39-12build1        CVE-2017-11164
ubuntu:focal found libpcre3    2:8.39-12build1        CVE-2019-20838
ubuntu:focal found libpcre3    2:8.39-12build1        CVE-2020-14155
ubuntu:focal found libsystemd0 245.4-4ubuntu3.2       CVE-2018-20839
ubuntu:focal found libsystemd0 245.4-4ubuntu3.2       CVE-2020-13776
ubuntu:focal found libtasn1-6  4.16.0-2               CVE-2018-1000654
ubuntu:focal found libudev1    245.4-4ubuntu3.2       CVE-2018-20839
ubuntu:focal found libudev1    245.4-4ubuntu3.2       CVE-2020-13776
ubuntu:focal found login       1:4.8.1-1ubuntu5.20.04 CVE-2013-4235
ubuntu:focal found login       1:4.8.1-1ubuntu5.20.04 CVE-2018-7169
ubuntu:focal found coreutils   8.30-3ubuntu2          CVE-2016-2781
ubuntu:focal found passwd      1:4.8.1-1ubuntu5.20.04 CVE-2013-4235
ubuntu:focal found passwd      1:4.8.1-1ubuntu5.20.04 CVE-2018-7169
ubuntu:focal found perl-base   5.30.0-9build1         CVE-2020-10543
ubuntu:focal found perl-base   5.30.0-9build1         CVE-2020-10878
ubuntu:focal found perl-base   5.30.0-9build1         CVE-2020-12723
ubuntu:focal found tar         1.30+dfsg-7            CVE-2019-9923
ubuntu:focal found dpkg        1.19.7ubuntu3          CVE-2017-8283
ubuntu:focal found gpgv        2.2.19-3ubuntu2        CVE-2019-13050
ubuntu:focal found libc-bin    2.31-0ubuntu9          CVE-2016-10228
ubuntu:focal found libc-bin    2.31-0ubuntu9          CVE-2020-6096
ubuntu:focal found libc6       2.31-0ubuntu9          CVE-2016-10228
ubuntu:focal found libc6       2.31-0ubuntu9          CVE-2020-6096
ubuntu:focal found libgcrypt20 1.8.5-5ubuntu1         CVE-2019-12904

To test locally-built images, you'll need to push them to a registry that is accessible by the Clair service and the clairctl command. A local registry can be used for this, but the specifics of configuration vary by registry and container runtime. Consult the relevant documentation for more information.

What's Next

Now that you see the basic usage of Clair, you can checkout our deployment models to learn different ways of deploying.

You may also be curious about how clairctl did that work. Check out our API definition to understand how an application interacts with Clair.

Deploying Clair

Clair v4 was designed with flexible deployment architectures in mind. An operator is free to choose a deployment model which scales to their use cases.

Configuration

Before jumping directly into the models, its important to note that Clair is designed to use a single configuration file across all node types. This design decision makes it very easy to deploy on systems like Kubernetes and OpenShift.

See Config Reference

Combined Deployment

In a combined deployment, all the Clair processes run in a single OS process. This is by far the easiest deployment model to configure as it involves the least moving parts.

A load balancer is still recommended if you plan on performing TLS termination. Typically this will be a OpenShift route or a Kubernetes ingress.

combo mode single db deployment diagran

In the above diagram, Clair is running in combo mode and talking to a single database. To configure this mode, you will provide all node types the same database and start Clair in combo mode.

...
indexer:
    connstring: "host=clairdb user=pqgotest dbname=pqgotest sslmode=verify-full"
matcher:
    connstring: "host=clairdb user=pqgotest dbname=pqgotest sslmode=verify-full"
    ...
notifier:
    connstring: "host=clairdb user=pqgotest dbname=pqgotest sslmode=verify-full"
    ...

In this mode, any configuration informing Clair how to talk to other nodes is ignored; it is not needed as all intra-process communication is done directly.

For added flexibility, it's also supported to split the databases while in combo mode.

combo mode multiple db deployment diagran

In the above diagram, Clair is running in combo mode but database load is split between multiple databases. Since Clair is conceptually a set of micro-services, its processes do not share database tables even when combined into the same OS process.

To configure this mode, you would provide each process its own "connstring" in the configuration.

...
indexer:
    connstring: "host=indexer-clairdb user=pqgotest dbname=pqgotest sslmode=verify-full"
matcher:
    connstring: "host=matcher-clairdb user=pqgotest dbname=pqgotest sslmode=verify-full"
    ...
notifier:
    connstring: "host=notifier-clairdb user=pqgotest dbname=pqgotest sslmode=verify-full"
    ...

Distributed Deployment

If your application needs to asymmetrically scale or you expect high load you may want to consider a distributed deployment.

In a distributed deployment, each Clair process runs in its own OS process. Typically this will be a Kubernetes or OpenShift Pod.

A load balancer must be setup in this deployment model. The load balancer will route traffic between Clair nodes along with routing API requests via path based routing to the correct services. In a Kubernetes or OpenShift deployment this is usually handled with the Service and Route abstractions. If deploying on bare metal, a load balancer will need to be configured appropriately.

distributed mode multiple db deployment diagran

In the above diagram, a load balancer is configured to route traffic coming from the client to the correct service. This routing is path based and requires a layer 7 load balancer. Traefik, Nginx, and HAProxy are all capable of this. As mentioned above, this functionality is native to OpenShift and Kubernetes.

In this configuration, you'd supply each process with database connection strings and addresses for their dependent services. Each OS process will need to have its "mode" CLI flag or environment variable set to the appropriate value. See Config Reference

...
indexer:
    connstring: "host=indexer-clairdb user=pqgotest dbname=pqgotest sslmode=verify-full"
matcher:
    connstring: "host=matcher-clairdb user=pqgotest dbname=pqgotest sslmode=verify-full"
    indexer_addr: "indexer-service"
    ...
notifier:
    connstring: "host=notifier-clairdb user=pqgotest dbname=pqgotest sslmode=verify-full"
    indexer_addr: "indexer-service"
    matcher_addr: "matcher-service"
    ...

Keep in mind a config file per process is not need. Processes only use the values necessary for their configured mode.

TLS Termination

It's recommended to offload TLS termination to the load balancing infrastructure. This design choice is due to the ubiquity of Kubernetes and OpenShift infrastructure already providing this facility.

If this is not possible for some reason, it is possible to have processes terminate TLS by using the $.tls configuration key. A load balancer is still required.

Disk Usage Considerations

By default, Clair will store container layers in /var/tmp while in use. This can be changed by setting the TMPDIR environment variable. There's currently no way to change this in the configuration file.

The disk space needed depends on the precise layers being indexed at any one time, but a good approximation is twice as large as the largest (uncompressed size) layer in the corpus.

More On Path Routing

If you are considering a distributed deployment you will need more details on path based routing.

Learn how to grab our OpenAPI spec here and either start up a local dev instance of the swagger editor or load the spec file into the online editor.

You will notice particular API paths are grouped by the services which implement them. This is your guide to configure your layer 7 load balancer correctly.

When the load balancer encounters a particular path prefix it must send those request to the correct set of Clair nodes.

For example, this is how we configure Traefik in our local development environment:

- "traefik.enable=true"
- "traefik.http.routers.notifier.entrypoints=clair"
- "traefik.http.routers.notifier.rule=PathPrefix(`/notifier`)"
- "traefik.http.routers.notifier.service=notifier"
- "traefik.http.services.notifier.loadbalancer.server.port=6000"

This configuration is saying "take any paths prefixes of /notifier/ and send them to the notifier services on port 6000".

Every load balancer will have their own way to perform path routing. Check the documentation for your infrastructure of choice.

API Definition

Clair provides its API definition via an OpenAPI specification. You can view our OpenAPI spec here

The OpenAPI spec can be used in a variety of ways.

  • Generating http clients for your application
  • Validating data returned from Clair
  • Importing into a rest client such as Postman
  • API documentation via Swagger Editor

See Testing Clair to learn how the local dev tooling starts a local swagger editor. This is handy for making changes to the spec in real time.

See API Reference for a markdown rendered API reference.

Testing Clair

We provide dev tooling in order to quickly get a fully configured Clair and Quay environment stood up locally. This environment can be used to test and develop Clair's Quay integration.

Requirements

Make

Make is used to stand up the local dev environment. Make is readily available in just about every package manager you can think of. It's very likely your workstation already has make on it.

Podman/Docker and Docker Compose

Currently our local dev tooling is supported by docker and docker-compose. Podman should work fine since v3.0.

Docker version 19.03.11 and docker-compose version 1.28.6 are confirmed working. Our assumption is most recent versions will not have an issue running the local dev tooling.

See Get Started with Podman.

Go Toolchain

Go 1.20 or higher is required.

See Install Golang.

Starting a cluster

git clone git@github.com:quay/clair.git
cd clair
docker-compose up -d
# or: make local-dev
# or: make local-dev-debug
# or: make local-dev-quay

After the local development environment successfully starts, the following infrastructure is available to you:

  • localhost:8080

    Dashboards and debugging services -- See the traefik configs in local-dev/traefik for where the various services are served.

  • localhost:6060

    Clair services.

  • Quay (if started)

    Quay will be started in a single node, local storage configuration. A random port will be forwarded from localhost, see podman port for the mapping.

  • PostgreSQL

    PostgreSQL will have a random port forwarded from localhost to the database server. See local-dev/clair/init.sql for credentials and permissions and podman port for the mapping.

Debugging

With the local-dev-debug make target the operator has access to some more useful tools:

ToolDescriptionURLCredentials
pgAdminPostgres administrationlocalhost:8080/pgadminclair@clair.com:clair
jaegerDistributed tracinglocalhost:8080/jaeger-
prometheusMetrics collection and queryinglocalhost:8080/prom-
pyroscopeContinuous profilinglocalhost:8080/pyroscope-
grafanaMetrics dashboardslocalhost:8080/grafanaadmin:admin

Pushing to the Local Quay

As mentioned above, Quay is forwarded to a random port on the host. You can connect to the server on that port and create a account. Creating an account named admin will ensure you are a super user. An email is required, but is not validated. You'll also need to create a namespace.

To push to Quay, you'll need to exec into the skopeo container:

docker-compose exec -it skopeo /usr/bin/skopeo copy --dest-creds '<user>:<pass>' --dest-tls-verify=false <src> docker://clair-quay:8080/<namespace>/<repo>:<tag>

Note that skopeo expects its image arguments in containers-transports(5) format.

Viewing Results

By default, Quay displays security scanner results on the Tags page of the given repository.

Making changes to configuration

You may want to play with either Clair or Quay's configuration. If so, the configuration files can be found inside the repository at local-dev/quay/config.yaml and local-dev/clair/config.yaml. Any changes to the configs will require a restart of the relevant service. The quay-specific clair config is autogenerated, see the Makefile.

Tearing it down

docker-compose down

will rip the entire environment down.

Troubleshooting

The most common issue encountered when standing up the dev environment is port conflicts. Make sure that you do not have any other processes listening on any of the ports outlined above.

The second issue you may face is your Docker resource settings being too constrained to support the local dev stack. This is typically seen on Docker4Mac since a VM is used with a specific set of resources configured. See Docker For Mac Manual for instructions on how to change these resources.

If docker-compose reports errors like Unsupported config option for services.activemq: 'profiles', the docker-compose version is too old and you'll need to upgrade. Consult the relevant documentation for your environment for instructions.

Lastly, you can view traefik's ui at localhost:8080/dashboard/.

Concepts

The following sections give a conceptual overview of how Clair works internally.

Indexing

The Indexer service is responsble for "indexing a manifest".

Indexing involves taking a manifest representing a container image and computing its constituent parts. The indexer is trying to discover what packages exist in the image, what distribution the image is derived from, and what package repositories are used within the image. Once this information is computed it is persisted in an IndexReport.

The IndexReport is an intermediate data structure describing the contents of a container image. This report can be fed to a Matcher node for vulnerability analysis.

Content Addressability

ClairV4 treats all manifests and layers as content addressable. In the context of ClairV4 this means once we index a specific manifest we will not index it again unless it's required, and likewise with individual layers. This allows a large reduction in work.

For example, consider how many images in a registry may use "ubuntu:artful" as a base layer. It could be a large majority of images if the developers prefer basing their images off ubuntu. Treating the layers and manifests as content addressable means we will only fetch and scan the base layer once.

There are of course conditions where ClairV4 should re-index a manifest.

When an internal component such as a package scanner is updated, Clair will know to perform the scan with the new package scanner. Clair has enough information to determine that a component has changed and the IndexReport may be different this time around.

A client can track ClairV4's index_state endpoint to understand when an internal component has changed and subsequently issue re-indexes. See our api guide to learn how to view our api specification.

Summary

In summary, you should understand that Indexing is the process Clair uses to understand the contents of layers.

For a more indepth look at indexing check out the ClairCore Documentation

Matching

A Matcher node is responsible for matching vulnerabilities to a provided IndexReport.

Matchers by default are also responsible for keeping the database of vulnerabilities up to date. Matchers will typically run a set of Updaters which periodically probe their data sources for new contents, storing new vulnerabilities in the database when discovered.

The matcher API is designed to be called often and will always provide the most up-to-date VulnerabilityReport when queried. This VulnerabilityReport summaries both a manifest's contents and any vulnerabilities affecting the contents.

See our api guide to learn how to view our api specification and work with the Matcher api.

Remote Matching

A remote matcher behaves similarly to a matcher, except that it uses api calls to fetch vulnerability data for a provided IndexReport. Remote matchers are useful when it is not possible to persist data from a given source into the database.

The crda remote matcher is responsible for fetching vulnerabilities from Red Hat Code Ready Dependency Analytics (CRDA). By default, this matcher serves 100 requests per minute. The rate-limiting can be lifted by requesting a dedicated API key, which is done via this form.

Summary

In summary you should understand that a Matcher node provides vulnerability reports given the output of an Indexing process. By default it will also run background Updaters keeping the vulnerability database up-to-date.

For a more indepth look at indexing check out the ClairCore Documentation

Internal

Internal endpoints are underneath /api/v1/internal and are meant for communication between Clair microservices. If Clair is operating in combo mode, these endpoints may not exist. Any sort of API ingress should disallow clients to talk to these endpoints.

We do not formally expose these APIs in our OpenAPI spec. Further information and usage is an effort left to the reader.

Updates Diffs

The update_diff/ endpoint exposes the api for diffing two update operations. This is used by the notifier to determine the added and removed vulnerabilities on security databsae update.

Update Operation

The update_operation endpoint exposes the api for viewing updaters' activity. This is used by the notifier to determine if new updates have occurred and triggers an update diff to see what has changed.

AffectedManifest

The affected_manifest endpoint exposes the api for retreiving affected manifests given a list of Vulnerabilities. This is used by the notifier to determine the manifests that need to have a notification generated.

Authentication

Previous versions of Clair used jwtproxy to gate authentication. For ease of building and deployment, v4 handles authentication itself.

Authentication is configured by specifying configuration objects underneath the auth key of the configuration. Multiple authentication configurations may be present, but they will be used preferentially in the order laid out below.

PSK

Clair implements JWT-based authentication using a pre-shared key.

Configuration

The auth stanza of the configuration file requires two parameters: iss, which is the issuer to validate on all incoming requests; and key, which is a base64 encoded symmetric key for validating the requests.

auth:
  psk:
    key: >-
      MDQ4ODBlNDAtNDc0ZC00MWUxLThhMzAtOTk0MzEwMGQwYTMxCg==
    iss: 'issuer'

Notifications

ClairV4 implements a notification system.

The notifier service will keep track of new security database updates and inform an interested client if new or removed vulnerabilities affect an indexed manifest.

The interested client can subscribe to notifications via several mechanisms:

  • Webhook delivery
  • AMQP delivery
  • STOMP delivery

Configuring the notifier is done via the yaml configuration.

See the "Notifier" object in our config reference

A Notification

When the notifier becomes aware of new vulnerabilities affecting a previously indexed manifest, it will use the configured method(s) to issue notifications about the new changes. Any given notification expresses the most severe vulnerability discovered because of the change. This avoids creating a flurry of notifications for the same security database update.

Once a client receives a notification, it should issue a new request against the matcher to receive an up-to-date vulnerability report.

The notification schema is the JSON marshaled form of the following types:

// Reason indicates the catalyst for a notification
type Reason string
const (
	Added   Reason = "added"
	Removed Reason = "removed"
	Changed Reason = "changed"
)
type Notification struct {
	ID            uuid.UUID        `json:"id"`
	Manifest      claircore.Digest `json:"manifest"`
	Reason        Reason           `json:"reason"`
	Vulnerability VulnSummary      `json:"vulnerability"`
}
type VulnSummary struct {
	Name           string                  `json:"name"`
	Description    string                  `json:"description"`
	Package        *claircore.Package      `json:"package,omitempty"`
	Distribution   *claircore.Distribution `json:"distribution,omitempty"`
	Repo           *claircore.Repository   `json:"repo,omitempty"`
	Severity       string                  `json:"severity"`
	FixedInVersion string                  `json:"fixed_in_version"`
	Links          string                  `json:"links"`
}

Webhook Delivery

See the "Notifier.Webhook" object in the config reference for complete configuration details.

When you configure notifier for webhook delivery you provide the service with the following pieces of information:

  • A target URL where the webhook will fire
  • The callback URL where the notifier may be reached including its API path
    • e.g. "http://clair-notifier/notifier/api/v1/notification"

When the notifier has determined an updated security database has changed the affected status of an indexed manifest, it will deliver the following JSON body to the configured target:

{
  "notification_id": {uuid_string},
  "callback": {url_to_notifications}
}

On receipt, the server can immediately browse to the URL provided in the callback field.

Pagination

The URL returned in the callback field brings the client to a paginated result.

The callback endpoint specification follows:

GET /notifier/api/v1/notification/{id}?[page_size=N][next=N]
{
  page: {
    size:    int,      // maximum number of notifications in the response
    next:   string, //  if present, the next id to fetch.
  }
  notifications: [ Notification… ] // array of notifications; max len == page.size
}

The GET callback request implements a simple bare-minimum paging mechanism.

The "page_size" url param controls how many notifications are returned in a single page. If not provided a default of 500 is used.

The "next" url param informs Clair the next set of paged notifications to return. If not provided the 0th page is assumed.

A page object accompanying the notification list specifies "next" and "size" fields.

The "next" field returned in the page must be provided as the subsequent request's "next" url parameter to retrieve the next set of notifications.

The "size" field will simply echo back the request page_size parameter.

When the final page is served to the client the returned "page" data structure will not contain a "next" member.

Therefore the following loop is valid for obtaining all notifications for a notification id in pages of a specified size.

{ page, notifications } = http.Get("http://clairv4/notifier/api/v1/notification/{id}?page_size=1000")

while (page.Next != None) {
    { page, notifications } = http.Get("http://clairv4/notifier/api/v1/notification/{id}?next={page.Next},page_size=1000")
}

Note: If the client specifies a custom page_size it must specify this page_size on every request for accurate responses.

Deleting Notifications

While not mandatory, the client may issue a delete of the notification via a DELETE method. See api to view the delete api.

Deleting a notification ID will clean up resources in the notifier quicker. Otherwise the notifier will wait a predetermined length of time before clearing delivered notifications from its database.

AMQP Delivery

See the "Notifier.AMQP" object in our config reference for complete configuration details.

The notifier also supports delivering to an AMQP broker. With AMQP delivery you can control whether a callback is delivered to the broker or whether notifications are directly delivered to the queue.

This allows the developer of the AMQP consumer to determine the logic of notification processing.

Note that AMQP delivery only supports AMQP 0.x protocol (e.g. RabbitMQ). If you need to publish notifications on AMQP 1.x message queue (e.g. ActiveMQ), you can use STOMP delivery.

Direct Delivery

If the notifier's configuration specifies direct: true for AMQP, notifications will be delivered directly to the configured exchange.

When direct is set, the rollup property may be set to instruct the notifier to send a max number of notifications in a single AMQP message. This allows a balance between size of the message and number of messages delivered to the queue.

Testing and Development

The notifier has a testing mode enabled when it sees the "NOTIFIER_TEST_MODE" environment variable set. It can be set to any value as we only check to see if it exists.

When this environment variable is set, the notifier will begin sending fake notifications to the configured delivery mechanism every "poll_interval" interval. This provides an easy way to implement and test new or existing deliverers.

The notifier will run in this mode until the environment variable is cleared and the service is restarted.

Updaters

Clair utilizes go packages we call "updaters" that encapsulate the logic of fetching and parsing different vulnerability databases. Updaters are usually pared with a matcher to interpret if and how any vulnerability is related to a package.

Operators may wish to update the vulnerability database less frequently or not import vulnerabilities from databases that they know will not be used.

Configuration

Updaters can be configured by updaters key at the top of the configuration. If updaters are being run automatically within the matcher processes, as is the default, the period for running updaters is configured under the matcher's configuration stanza.

Choosing Sets

Specific sets of updaters can be selected by the sets list. If not present, the defaults of all upstream updaters will be used.

updaters:
  sets:
    - rhel

Specific Updaters

Configuration for specific updaters can be passed by putting a key underneath the config member of the updaters object. The name of an updater may be constructed dynamically; users should examine logs to double-check names. The specific object that an updater expects should be covered in the updater's documentation.

For example, to have the "rhel" updater fetch a manifest from a different location:

updaters:
  config:
    rhel:
      url: https://example.com/mirror/oval/PULP_MANIFEST

Airgap

For additional flexibility, Clair supports running updaters in a different environment and importing the results. This is aimed at supporting installations that disallow the Clair cluster from talking to the Internet directly. An update procedure needs to arrange to call the relevant clairctl command in an environment with access to the Internet, move the resulting artifact across the airgap according to site policy, and then call the relevant clairctl command to import the updates.

For example:

# On a workstation, run:
clairctl export-updaters updates.json.gz
# Move the resulting file to a place reachable by the cluster:
scp updates.json.gz internal-webserver:/var/www/
# On a pod inside the cluster, import the file:
clairctl import-updaters http://web.svc/updates.json.gz

Note that a configuration file is needed to run these commands.

Configuration

Matcher processes should have the disable_updaters key set to disable automatic updaters running.

matcher:
  disable_updaters: true

Indexers

Configuration

Airgap

indexer:
  airgap: true

Specific Scanners

indexer:
  scanner:
    package:
      name:
        key: value
    repo:
      name:
        key: value
    dist:
      name:
        key: value

Operation

Contribuion

The following sections provides information on how to contribute to Clair.

Releases

Clair releases are cut roughly every three months and actively maintained for six.

This means that bugfixes should be landed on master (if applicable) and then marked for backporting to a minor version's release branch. The process for doing this is not yet formalized.

Process

Minor

When cutting a new minor release, two things need to be done: creating a tag and creating a release branch. This can be done like so:

git tag -as v4.x.0 HEAD
git push upstream HEAD:release-4.x tag v4.x.0

Then, a "release" needs to be created in the Github UI using the created tag.

Patch

A patch release is just like a minor release with the caveat that minor version tags should only appear on release branches and a new branch does not need to be created.

git checkout release-4.x
git tag -as v4.x.1 HEAD
git push upstream tag v4.x.1

Then, a "release" needs to be created in the Github UI using the created tag.

Creating Artifacts

Clair's artifact release process is automated and driven off the releases in Github.

Publishing a new release in the Github UI automatically triggers the creation of a complete source archive and a container. The archive is attached to the release, and the container is pushed to the quay.io/projectquay/clair repository.

This is all powered by a Github Action in .github/workflows/cut-release.yml.

Commit Style

The Clair project utilizes well structured commits to keep the history useful and help with release automation. We suggest signing off on your commits as well.

A typical commit will take on the following structure:

<scope>: <subject>

<body>
Fixes #1
Pull Request #2

Signed-Off By: <email>

The header of the commit is regexp checked before commit and your commit will be kicked back if it does not conform.

Scope

This is the section of code this commit influences.

You will often see scopes such as "notifier", "auth", "chore", "cicd".

We use this field to group commits by scope in our automated changelog generation.

It would be wise to take a look at our changelog before contributing to get an idea of the common scopes we use.

Subject

Subject is a short and concise summary of the change the commit is introducing. It should be a sentence fragment without starting capitalization and ending punctuation and limited to about 60 characters, to allow for the scope prefix and decoration in the git log.

Body

Body should be full of detail.

Explain what this commit is doing and why it is necessary.

You may include references to issues and pull requests as well. Our automated changelog process will discover references prefixed with "Fixes", "Closed" and "Pull Request"

Reference

The following sections provide reference information useful when exploring the documentation or working with Clair directly.


title: ClairV4 v1.1 language_tabs:

  • python: Python
  • go: Golang
  • javascript: Javascript language_clients:
  • python: ""
  • go: ""
  • javascript: "" toc_footers: [] includes: [] search: false highlight_theme: darkula headingLevel: 2

ClairV4 v1.1

Scroll down for code samples, example requests and responses. Select a language for code samples from the tabs above or the mobile navigation menu.

ClairV4 is a set of cooperating microservices which scan, index, and match your container's content with known vulnerabilities.

Email: Clair Team Web: Clair Team License: Apache License 2.0

Notifier

DeleteNotification

Code samples

import requests
headers = {
  'Accept': 'application/json'
}

r = requests.delete('/notifier/api/v1/notification/{notification_id}', headers = headers)

print(r.json())

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Accept": []string{"application/json"},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("DELETE", "/notifier/api/v1/notification/{notification_id}", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}


const headers = {
  'Accept':'application/json'
};

fetch('/notifier/api/v1/notification/{notification_id}',
{
  method: 'DELETE',

  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

DELETE /notifier/api/v1/notification/{notification_id}

Issues a delete of the provided notification id and all associated notifications. After this delete clients will no longer be able to retrieve notifications.

Parameters

NameInTypeRequiredDescription
notification_idpathstringfalseA notification ID returned by a callback

Example responses

400 Response

{
  "code": "string",
  "message": "string"
}

Responses

StatusMeaningDescriptionSchema
200OKOKNone
400Bad RequestBad RequestError
405Method Not AllowedMethod Not AllowedError
500Internal Server ErrorInternal Server ErrorError

Retrieve a paginated result of notifications for the provided id.

Code samples

import requests
headers = {
  'Accept': 'application/json'
}

r = requests.get('/notifier/api/v1/notification/{notification_id}', headers = headers)

print(r.json())

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Accept": []string{"application/json"},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("GET", "/notifier/api/v1/notification/{notification_id}", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}


const headers = {
  'Accept':'application/json'
};

fetch('/notifier/api/v1/notification/{notification_id}',
{
  method: 'GET',

  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

GET /notifier/api/v1/notification/{notification_id}

By performing a GET with a notification_id as a path parameter, the client will retrieve a paginated response of notification objects.

Parameters

NameInTypeRequiredDescription
notification_idpathstringfalseA notification ID returned by a callback
page_sizequeryintfalseThe maximum number of notifications to deliver in a single page.
nextquerystringfalseThe next page to fetch via id. Typically this number is provided on initial response in the page.next field. The first GET request may omit this field.

Example responses

200 Response

{
  "page": {
    "size": 100,
    "next": "1b4d0db2-e757-4150-bbbb-543658144205"
  },
  "notifications": [
    {
      "id": "5e4b387e-88d3-4364-86fd-063447a6fad2",
      "manifest": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a",
      "reason": "added",
      "vulnerability": {
        "name": "CVE-2009-5155",
        "fixed_in_version": "v0.0.1",
        "links": "http://link-to-advisory",
        "description": "In the GNU C Library (aka glibc or libc6) before 2.28, parse_reg_exp in posix/regcomp.c misparses alternatives, which allows attackers to cause a denial of service (assertion failure and application exit) or trigger an incorrect result by attempting a regular-expression match.\"",
        "normalized_severity": "Unknown",
        "package": {
          "id": "10",
          "name": "libapt-pkg5.0",
          "version": "1.6.11",
          "kind": "binary",
          "normalized_version": "",
          "arch": "x86",
          "module": "",
          "cpe": "",
          "source": {
            "id": "9",
            "name": "apt",
            "version": "1.6.11",
            "kind": "source",
            "source": null
          }
        },
        "distribution": {
          "id": "1",
          "did": "ubuntu",
          "name": "Ubuntu",
          "version": "18.04.3 LTS (Bionic Beaver)",
          "version_code_name": "bionic",
          "version_id": "18.04",
          "arch": "",
          "cpe": "",
          "pretty_name": "Ubuntu 18.04.3 LTS"
        },
        "repository": {
          "id": "string",
          "name": "string",
          "key": "string",
          "uri": "string",
          "cpe": "string"
        }
      }
    }
  ]
}

Responses

StatusMeaningDescriptionSchema
200OKA paginated list of notificationsPagedNotifications
400Bad RequestBad RequestError
405Method Not AllowedMethod Not AllowedError
500Internal Server ErrorInternal Server ErrorError

Indexer

Index the contents of a Manifest

Code samples

import requests
headers = {
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

r = requests.post('/indexer/api/v1/index_report', headers = headers)

print(r.json())

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Content-Type": []string{"application/json"},
        "Accept": []string{"application/json"},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("POST", "/indexer/api/v1/index_report", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const inputBody = '{
  "hash": "sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3",
  "layers": [
    {
      "hash": "sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3",
      "uri": "https://storage.example.com/blob/2f077db56abccc19f16f140f629ae98e904b4b7d563957a7fc319bd11b82ba36",
      "headers": {
        "property1": [
          "string"
        ],
        "property2": [
          "string"
        ]
      }
    }
  ]
}';
const headers = {
  'Content-Type':'application/json',
  'Accept':'application/json'
};

fetch('/indexer/api/v1/index_report',
{
  method: 'POST',
  body: inputBody,
  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

POST /indexer/api/v1/index_report

By submitting a Manifest object to this endpoint Clair will fetch the layers, scan each layer's contents, and provide an index of discovered packages, repository and distribution information.

Body parameter

{
  "hash": "sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3",
  "layers": [
    {
      "hash": "sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3",
      "uri": "https://storage.example.com/blob/2f077db56abccc19f16f140f629ae98e904b4b7d563957a7fc319bd11b82ba36",
      "headers": {
        "property1": [
          "string"
        ],
        "property2": [
          "string"
        ]
      }
    }
  ]
}

Parameters

NameInTypeRequiredDescription
bodybodyManifesttruenone

Example responses

201 Response

{
  "manifest_hash": "sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3",
  "state": "IndexFinished",
  "packages": {
    "10": {
      "id": "10",
      "name": "libapt-pkg5.0",
      "version": "1.6.11",
      "kind": "binary",
      "normalized_version": "",
      "arch": "x86",
      "module": "",
      "cpe": "",
      "source": {
        "id": "9",
        "name": "apt",
        "version": "1.6.11",
        "kind": "source",
        "source": null
      }
    }
  },
  "distributions": {
    "1": {
      "id": "1",
      "did": "ubuntu",
      "name": "Ubuntu",
      "version": "18.04.3 LTS (Bionic Beaver)",
      "version_code_name": "bionic",
      "version_id": "18.04",
      "arch": "",
      "cpe": "",
      "pretty_name": "Ubuntu 18.04.3 LTS"
    }
  },
  "environments": {
    "10": [
      {
        "package_db": "var/lib/dpkg/status",
        "introduced_in": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a",
        "distribution_id": "1"
      }
    ]
  },
  "success": true,
  "err": ""
}

Responses

StatusMeaningDescriptionSchema
201CreatedIndexReport CreatedIndexReport
400Bad RequestBad RequestError
405Method Not AllowedMethod Not AllowedError
500Internal Server ErrorInternal Server ErrorError

Delete the IndexReport and associated information for the given Manifest hashes, if they exist.

Code samples

import requests
headers = {
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

r = requests.delete('/indexer/api/v1/index_report', headers = headers)

print(r.json())

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Content-Type": []string{"application/json"},
        "Accept": []string{"application/json"},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("DELETE", "/indexer/api/v1/index_report", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const inputBody = '[
  "sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3"
]';
const headers = {
  'Content-Type':'application/json',
  'Accept':'application/json'
};

fetch('/indexer/api/v1/index_report',
{
  method: 'DELETE',
  body: inputBody,
  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

DELETE /indexer/api/v1/index_report

Given a Manifest's content addressable hash, any data related to it will be removed if it exists.

Body parameter

[
  "sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3"
]

Parameters

NameInTypeRequiredDescription
bodybodyBulkDeletetruenone

Example responses

200 Response

[
  "sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3"
]

Responses

StatusMeaningDescriptionSchema
200OKOKBulkDelete
400Bad RequestBad RequestError
500Internal Server ErrorInternal Server ErrorError

Delete the IndexReport and associated information for the given Manifest hash, if exists.

Code samples

import requests
headers = {
  'Accept': 'application/json'
}

r = requests.delete('/indexer/api/v1/index_report/{manifest_hash}', headers = headers)

print(r.json())

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Accept": []string{"application/json"},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("DELETE", "/indexer/api/v1/index_report/{manifest_hash}", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}


const headers = {
  'Accept':'application/json'
};

fetch('/indexer/api/v1/index_report/{manifest_hash}',
{
  method: 'DELETE',

  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

DELETE /indexer/api/v1/index_report/{manifest_hash}

Given a Manifest's content addressable hash, any data related to it will be removed it it exists.

Parameters

NameInTypeRequiredDescription
manifest_hashpathDigesttrueA digest of a manifest that has been indexed previous to this request.

Example responses

400 Response

{
  "code": "string",
  "message": "string"
}

Responses

StatusMeaningDescriptionSchema
204No ContentOKNone
400Bad RequestBad RequestError
500Internal Server ErrorInternal Server ErrorError

Retrieve an IndexReport for the given Manifest hash if exists.

Code samples

import requests
headers = {
  'Accept': 'application/json'
}

r = requests.get('/indexer/api/v1/index_report/{manifest_hash}', headers = headers)

print(r.json())

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Accept": []string{"application/json"},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("GET", "/indexer/api/v1/index_report/{manifest_hash}", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}


const headers = {
  'Accept':'application/json'
};

fetch('/indexer/api/v1/index_report/{manifest_hash}',
{
  method: 'GET',

  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

GET /indexer/api/v1/index_report/{manifest_hash}

Given a Manifest's content addressable hash an IndexReport will be retrieved if exists.

Parameters

NameInTypeRequiredDescription
manifest_hashpathDigesttrueA digest of a manifest that has been indexed previous to this request.

Example responses

200 Response

{
  "manifest_hash": "sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3",
  "state": "IndexFinished",
  "packages": {
    "10": {
      "id": "10",
      "name": "libapt-pkg5.0",
      "version": "1.6.11",
      "kind": "binary",
      "normalized_version": "",
      "arch": "x86",
      "module": "",
      "cpe": "",
      "source": {
        "id": "9",
        "name": "apt",
        "version": "1.6.11",
        "kind": "source",
        "source": null
      }
    }
  },
  "distributions": {
    "1": {
      "id": "1",
      "did": "ubuntu",
      "name": "Ubuntu",
      "version": "18.04.3 LTS (Bionic Beaver)",
      "version_code_name": "bionic",
      "version_id": "18.04",
      "arch": "",
      "cpe": "",
      "pretty_name": "Ubuntu 18.04.3 LTS"
    }
  },
  "environments": {
    "10": [
      {
        "package_db": "var/lib/dpkg/status",
        "introduced_in": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a",
        "distribution_id": "1"
      }
    ]
  },
  "success": true,
  "err": ""
}

Responses

StatusMeaningDescriptionSchema
200OKIndexReport retrievedIndexReport
400Bad RequestBad RequestError
404Not FoundNot FoundError
405Method Not AllowedMethod Not AllowedError
500Internal Server ErrorInternal Server ErrorError

Report the indexer's internal configuration and state.

Code samples

import requests
headers = {
  'Accept': 'application/json'
}

r = requests.get('/indexer/api/v1/index_state', headers = headers)

print(r.json())

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Accept": []string{"application/json"},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("GET", "/indexer/api/v1/index_state", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}


const headers = {
  'Accept':'application/json'
};

fetch('/indexer/api/v1/index_state',
{
  method: 'GET',

  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

GET /indexer/api/v1/index_state

The index state endpoint returns a json structure indicating the indexer's internal configuration state. A client may be interested in this as a signal that manifests may need to be re-indexed.

Example responses

200 Response

{
  "state": "aae368a064d7c5a433d0bf2c4f5554cc"
}

Responses

StatusMeaningDescriptionSchema
200OKIndexer StateState
304Not ModifiedIndexer State UnchangedNone

Response Headers

StatusHeaderTypeFormatDescription
200EtagstringEntity Tag

Matcher

Retrieve a VulnerabilityReport for a given manifest's content addressable hash.

Code samples

import requests
headers = {
  'Accept': 'application/json'
}

r = requests.get('/matcher/api/v1/vulnerability_report/{manifest_hash}', headers = headers)

print(r.json())

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Accept": []string{"application/json"},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("GET", "/matcher/api/v1/vulnerability_report/{manifest_hash}", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}


const headers = {
  'Accept':'application/json'
};

fetch('/matcher/api/v1/vulnerability_report/{manifest_hash}',
{
  method: 'GET',

  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

GET /matcher/api/v1/vulnerability_report/{manifest_hash}

Given a Manifest's content addressable hash a VulnerabilityReport will be created. The Manifest must have been Indexed first via the Index endpoint.

Parameters

NameInTypeRequiredDescription
manifest_hashpathDigesttrueA digest of a manifest that has been indexed previous to this request.

Example responses

201 Response

{
  "manifest_hash": "sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3",
  "packages": {
    "10": {
      "id": "10",
      "name": "libapt-pkg5.0",
      "version": "1.6.11",
      "kind": "binary",
      "normalized_version": "",
      "arch": "x86",
      "module": "",
      "cpe": "",
      "source": {
        "id": "9",
        "name": "apt",
        "version": "1.6.11",
        "kind": "source",
        "source": null
      }
    }
  },
  "distributions": {
    "1": {
      "id": "1",
      "did": "ubuntu",
      "name": "Ubuntu",
      "version": "18.04.3 LTS (Bionic Beaver)",
      "version_code_name": "bionic",
      "version_id": "18.04",
      "arch": "",
      "cpe": "",
      "pretty_name": "Ubuntu 18.04.3 LTS"
    }
  },
  "environments": {
    "10": [
      {
        "package_db": "var/lib/dpkg/status",
        "introduced_in": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a",
        "distribution_id": "1"
      }
    ]
  },
  "vulnerabilities": {
    "356835": {
      "id": "356835",
      "updater": "",
      "name": "CVE-2009-5155",
      "description": "In the GNU C Library (aka glibc or libc6) before 2.28, parse_reg_exp in posix/regcomp.c misparses alternatives, which allows attackers to cause a denial of service (assertion failure and application exit) or trigger an incorrect result by attempting a regular-expression match.\"",
      "links": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-5155 http://people.canonical.com/~ubuntu-security/cve/2009/CVE-2009-5155.html https://sourceware.org/bugzilla/show_bug.cgi?id=11053 https://debbugs.gnu.org/cgi/bugreport.cgi?bug=22793 https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32806 https://debbugs.gnu.org/cgi/bugreport.cgi?bug=34238 https://sourceware.org/bugzilla/show_bug.cgi?id=18986\"",
      "severity": "Low",
      "normalized_severity": "Low",
      "package": {
        "id": "0",
        "name": "glibc",
        "version": "",
        "kind": "",
        "source": null,
        "package_db": "",
        "repository_hint": ""
      },
      "dist": {
        "id": "0",
        "did": "ubuntu",
        "name": "Ubuntu",
        "version": "18.04.3 LTS (Bionic Beaver)",
        "version_code_name": "bionic",
        "version_id": "18.04",
        "arch": "",
        "cpe": "",
        "pretty_name": ""
      },
      "repo": {
        "id": "0",
        "name": "Ubuntu 18.04.3 LTS",
        "key": "",
        "uri": ""
      },
      "issued": "2019-10-12T07:20:50.52Z",
      "fixed_in_version": "2.28-0ubuntu1"
    }
  },
  "package_vulnerabilities": {
    "10": [
      "356835"
    ]
  }
}

Responses

StatusMeaningDescriptionSchema
201CreatedVulnerabilityReport CreatedVulnerabilityReport
400Bad RequestBad RequestError
404Not FoundNot FoundError
405Method Not AllowedMethod Not AllowedError
500Internal Server ErrorInternal Server ErrorError

Schemas

Page

{
  "size": 1,
  "next": "1b4d0db2-e757-4150-bbbb-543658144205"
}

Page

Properties

NameTypeRequiredRestrictionsDescription
sizeintfalsenoneThe maximum number of elements in a page
nextstringfalsenoneThe next id to submit to the api to continue paging

PagedNotifications

{
  "page": {
    "size": 100,
    "next": "1b4d0db2-e757-4150-bbbb-543658144205"
  },
  "notifications": [
    {
      "id": "5e4b387e-88d3-4364-86fd-063447a6fad2",
      "manifest": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a",
      "reason": "added",
      "vulnerability": {
        "name": "CVE-2009-5155",
        "fixed_in_version": "v0.0.1",
        "links": "http://link-to-advisory",
        "description": "In the GNU C Library (aka glibc or libc6) before 2.28, parse_reg_exp in posix/regcomp.c misparses alternatives, which allows attackers to cause a denial of service (assertion failure and application exit) or trigger an incorrect result by attempting a regular-expression match.\"",
        "normalized_severity": "Unknown",
        "package": {
          "id": "10",
          "name": "libapt-pkg5.0",
          "version": "1.6.11",
          "kind": "binary",
          "normalized_version": "",
          "arch": "x86",
          "module": "",
          "cpe": "",
          "source": {
            "id": "9",
            "name": "apt",
            "version": "1.6.11",
            "kind": "source",
            "source": null
          }
        },
        "distribution": {
          "id": "1",
          "did": "ubuntu",
          "name": "Ubuntu",
          "version": "18.04.3 LTS (Bionic Beaver)",
          "version_code_name": "bionic",
          "version_id": "18.04",
          "arch": "",
          "cpe": "",
          "pretty_name": "Ubuntu 18.04.3 LTS"
        },
        "repository": {
          "id": "string",
          "name": "string",
          "key": "string",
          "uri": "string",
          "cpe": "string"
        }
      }
    }
  ]
}

PagedNotifications

Properties

NameTypeRequiredRestrictionsDescription
pageobjectfalsenoneA page object informing the client the next page to retrieve. If page.next becomes "-1" the client should stop paging.
notifications[Notification]falsenoneA list of notifications within this page

Callback

{
  "notification_id": "269886f3-0146-4f08-9bf7-cb1138d48643",
  "callback": "http://clair-notifier/notifier/api/v1/notification/269886f3-0146-4f08-9bf7-cb1138d48643"
}

Callback

Properties

NameTypeRequiredRestrictionsDescription
notification_idstringfalsenonethe unique identifier for this set of notifications
callbackstringfalsenonethe url where notifications can be retrieved

VulnSummary

{
  "name": "CVE-2009-5155",
  "fixed_in_version": "v0.0.1",
  "links": "http://link-to-advisory",
  "description": "In the GNU C Library (aka glibc or libc6) before 2.28, parse_reg_exp in posix/regcomp.c misparses alternatives, which allows attackers to cause a denial of service (assertion failure and application exit) or trigger an incorrect result by attempting a regular-expression match.\"",
  "normalized_severity": "Unknown",
  "package": {
    "id": "10",
    "name": "libapt-pkg5.0",
    "version": "1.6.11",
    "kind": "binary",
    "normalized_version": "",
    "arch": "x86",
    "module": "",
    "cpe": "",
    "source": {
      "id": "9",
      "name": "apt",
      "version": "1.6.11",
      "kind": "source",
      "source": null
    }
  },
  "distribution": {
    "id": "1",
    "did": "ubuntu",
    "name": "Ubuntu",
    "version": "18.04.3 LTS (Bionic Beaver)",
    "version_code_name": "bionic",
    "version_id": "18.04",
    "arch": "",
    "cpe": "",
    "pretty_name": "Ubuntu 18.04.3 LTS"
  },
  "repository": {
    "id": "string",
    "name": "string",
    "key": "string",
    "uri": "string",
    "cpe": "string"
  }
}

VulnSummary

Properties

NameTypeRequiredRestrictionsDescription
namestringfalsenonethe vulnerability name
fixed_in_versionstringfalsenoneThe version which the vulnerability is fixed in. Empty if not fixed.
linksstringfalsenonelinks to external information about vulnerability
descriptionstringfalsenonethe vulnerability name
normalized_severitystringfalsenoneA well defined set of severity strings guaranteed to be present.
packagePackagefalsenoneA package discovered by indexing a Manifest
distributionDistributionfalsenoneAn indexed distribution discovered in a layer. See https://www.freedesktop.org/software/systemd/man/os-release.html for explanations and example of fields.
repositoryRepositoryfalsenoneA package repository

Enumerated Values

PropertyValue
normalized_severityUnknown
normalized_severityNegligible
normalized_severityLow
normalized_severityMedium
normalized_severityHigh
normalized_severityCritical

Notification

{
  "id": "5e4b387e-88d3-4364-86fd-063447a6fad2",
  "manifest": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a",
  "reason": "added",
  "vulnerability": {
    "name": "CVE-2009-5155",
    "fixed_in_version": "v0.0.1",
    "links": "http://link-to-advisory",
    "description": "In the GNU C Library (aka glibc or libc6) before 2.28, parse_reg_exp in posix/regcomp.c misparses alternatives, which allows attackers to cause a denial of service (assertion failure and application exit) or trigger an incorrect result by attempting a regular-expression match.\"",
    "normalized_severity": "Unknown",
    "package": {
      "id": "10",
      "name": "libapt-pkg5.0",
      "version": "1.6.11",
      "kind": "binary",
      "normalized_version": "",
      "arch": "x86",
      "module": "",
      "cpe": "",
      "source": {
        "id": "9",
        "name": "apt",
        "version": "1.6.11",
        "kind": "source",
        "source": null
      }
    },
    "distribution": {
      "id": "1",
      "did": "ubuntu",
      "name": "Ubuntu",
      "version": "18.04.3 LTS (Bionic Beaver)",
      "version_code_name": "bionic",
      "version_id": "18.04",
      "arch": "",
      "cpe": "",
      "pretty_name": "Ubuntu 18.04.3 LTS"
    },
    "repository": {
      "id": "string",
      "name": "string",
      "key": "string",
      "uri": "string",
      "cpe": "string"
    }
  }
}

Notification

Properties

NameTypeRequiredRestrictionsDescription
idstringfalsenonea unique identifier for this notification
manifeststringfalsenoneThe hash of the manifest affected by the provided vulnerability.
reasonstringfalsenonethe reason for the notifcation, [added
vulnerabilityVulnSummaryfalsenoneA summary of a vulnerability

Environment

{
  "package_db": "var/lib/dpkg/status",
  "introduced_in": "sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3",
  "distribution_id": "1"
}

Environment

Properties

NameTypeRequiredRestrictionsDescription
package_dbstringtruenoneThe filesystem path or unique identifier of a package database.
introduced_inDigesttruenoneA digest string with prefixed algorithm. The format is described here: https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests
Digests are used throughout the API to identify Layers and Manifests.
distribution_idstringtruenoneThe distribution ID found in an associated IndexReport or VulnerabilityReport.

IndexReport

{
  "manifest_hash": "sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3",
  "state": "IndexFinished",
  "packages": {
    "10": {
      "id": "10",
      "name": "libapt-pkg5.0",
      "version": "1.6.11",
      "kind": "binary",
      "normalized_version": "",
      "arch": "x86",
      "module": "",
      "cpe": "",
      "source": {
        "id": "9",
        "name": "apt",
        "version": "1.6.11",
        "kind": "source",
        "source": null
      }
    }
  },
  "distributions": {
    "1": {
      "id": "1",
      "did": "ubuntu",
      "name": "Ubuntu",
      "version": "18.04.3 LTS (Bionic Beaver)",
      "version_code_name": "bionic",
      "version_id": "18.04",
      "arch": "",
      "cpe": "",
      "pretty_name": "Ubuntu 18.04.3 LTS"
    }
  },
  "environments": {
    "10": [
      {
        "package_db": "var/lib/dpkg/status",
        "introduced_in": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a",
        "distribution_id": "1"
      }
    ]
  },
  "success": true,
  "err": ""
}

IndexReport

Properties

NameTypeRequiredRestrictionsDescription
manifest_hashDigesttruenoneA digest string with prefixed algorithm. The format is described here: https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests
Digests are used throughout the API to identify Layers and Manifests.
statestringtruenoneThe current state of the index operation
packagesobjecttruenoneA map of Package objects indexed by Package.id
» additionalPropertiesPackagefalsenoneA package discovered by indexing a Manifest
distributionsobjecttruenoneA map of Distribution objects keyed by their Distribution.id discovered in the manifest.
» additionalPropertiesDistributionfalsenoneAn indexed distribution discovered in a layer. See https://www.freedesktop.org/software/systemd/man/os-release.html for explanations and example of fields.
environmentsobjecttruenoneA map of lists containing Environment objects keyed by the associated Package.id.
» additionalProperties[Environment]falsenone[The environment a particular package was discovered in.]
successbooleantruenoneA bool indicating succcessful index
errstringtruenoneAn error message on event of unsuccessful index

VulnerabilityReport

{
  "manifest_hash": "sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3",
  "packages": {
    "10": {
      "id": "10",
      "name": "libapt-pkg5.0",
      "version": "1.6.11",
      "kind": "binary",
      "normalized_version": "",
      "arch": "x86",
      "module": "",
      "cpe": "",
      "source": {
        "id": "9",
        "name": "apt",
        "version": "1.6.11",
        "kind": "source",
        "source": null
      }
    }
  },
  "distributions": {
    "1": {
      "id": "1",
      "did": "ubuntu",
      "name": "Ubuntu",
      "version": "18.04.3 LTS (Bionic Beaver)",
      "version_code_name": "bionic",
      "version_id": "18.04",
      "arch": "",
      "cpe": "",
      "pretty_name": "Ubuntu 18.04.3 LTS"
    }
  },
  "environments": {
    "10": [
      {
        "package_db": "var/lib/dpkg/status",
        "introduced_in": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a",
        "distribution_id": "1"
      }
    ]
  },
  "vulnerabilities": {
    "356835": {
      "id": "356835",
      "updater": "",
      "name": "CVE-2009-5155",
      "description": "In the GNU C Library (aka glibc or libc6) before 2.28, parse_reg_exp in posix/regcomp.c misparses alternatives, which allows attackers to cause a denial of service (assertion failure and application exit) or trigger an incorrect result by attempting a regular-expression match.\"",
      "links": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-5155 http://people.canonical.com/~ubuntu-security/cve/2009/CVE-2009-5155.html https://sourceware.org/bugzilla/show_bug.cgi?id=11053 https://debbugs.gnu.org/cgi/bugreport.cgi?bug=22793 https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32806 https://debbugs.gnu.org/cgi/bugreport.cgi?bug=34238 https://sourceware.org/bugzilla/show_bug.cgi?id=18986\"",
      "severity": "Low",
      "normalized_severity": "Low",
      "package": {
        "id": "0",
        "name": "glibc",
        "version": "",
        "kind": "",
        "source": null,
        "package_db": "",
        "repository_hint": ""
      },
      "dist": {
        "id": "0",
        "did": "ubuntu",
        "name": "Ubuntu",
        "version": "18.04.3 LTS (Bionic Beaver)",
        "version_code_name": "bionic",
        "version_id": "18.04",
        "arch": "",
        "cpe": "",
        "pretty_name": ""
      },
      "repo": {
        "id": "0",
        "name": "Ubuntu 18.04.3 LTS",
        "key": "",
        "uri": ""
      },
      "issued": "2019-10-12T07:20:50.52Z",
      "fixed_in_version": "2.28-0ubuntu1"
    }
  },
  "package_vulnerabilities": {
    "10": [
      "356835"
    ]
  }
}

VulnerabilityReport

Properties

NameTypeRequiredRestrictionsDescription
manifest_hashDigesttruenoneA digest string with prefixed algorithm. The format is described here: https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests
Digests are used throughout the API to identify Layers and Manifests.
packagesobjecttruenoneA map of Package objects indexed by Package.id
» additionalPropertiesPackagefalsenoneA package discovered by indexing a Manifest
distributionsobjecttruenoneA map of Distribution objects indexed by Distribution.id.
» additionalPropertiesDistributionfalsenoneAn indexed distribution discovered in a layer. See https://www.freedesktop.org/software/systemd/man/os-release.html for explanations and example of fields.
environmentsobjecttruenoneA mapping of Environment lists indexed by Package.id
» additionalProperties[Environment]falsenone[The environment a particular package was discovered in.]
vulnerabilitiesobjecttruenoneA map of Vulnerabilities indexed by Vulnerability.id
» additionalPropertiesVulnerabilityfalsenoneA unique vulnerability indexed by Clair
package_vulnerabilitiesobjecttruenoneA mapping of Vulnerability.id lists indexed by Package.id.
» additionalProperties[string]falsenonenone

Vulnerability

{
  "id": "356835",
  "updater": "",
  "name": "CVE-2009-5155",
  "description": "In the GNU C Library (aka glibc or libc6) before 2.28, parse_reg_exp in posix/regcomp.c misparses alternatives, which allows attackers to cause a denial of service (assertion failure and application exit) or trigger an incorrect result by attempting a regular-expression match.\"",
  "links": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-5155 http://people.canonical.com/~ubuntu-security/cve/2009/CVE-2009-5155.html https://sourceware.org/bugzilla/show_bug.cgi?id=11053 https://debbugs.gnu.org/cgi/bugreport.cgi?bug=22793 https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32806 https://debbugs.gnu.org/cgi/bugreport.cgi?bug=34238 https://sourceware.org/bugzilla/show_bug.cgi?id=18986\"",
  "severity": "Low",
  "normalized_severity": "Low",
  "package": {
    "id": "0",
    "name": "glibc",
    "version": "",
    "kind": "",
    "source": null,
    "package_db": "",
    "repository_hint": ""
  },
  "dist": {
    "id": "0",
    "did": "ubuntu",
    "name": "Ubuntu",
    "version": "18.04.3 LTS (Bionic Beaver)",
    "version_code_name": "bionic",
    "version_id": "18.04",
    "arch": "",
    "cpe": "",
    "pretty_name": ""
  },
  "repo": {
    "id": "0",
    "name": "Ubuntu 18.04.3 LTS",
    "key": "",
    "uri": ""
  },
  "issued": "2019-10-12T07:20:50.52Z",
  "fixed_in_version": "2.28-0ubuntu1",
  "x-widdershins-oldRef": "#/components/examples/Vulnerability/value"
}

Vulnerability

Properties

NameTypeRequiredRestrictionsDescription
idstringtruenoneA unique ID representing this vulnerability.
updaterstringtruenoneA unique ID representing this vulnerability.
namestringtruenoneName of this specific vulnerability.
descriptionstringtruenoneA description of this specific vulnerability.
linksstringtruenoneA space separate list of links to any external information.
severitystringtruenoneA severity keyword taken verbatim from the vulnerability source.
normalized_severitystringtruenoneA well defined set of severity strings guaranteed to be present.
packagePackagefalsenoneA package discovered by indexing a Manifest
distributionDistributionfalsenoneAn indexed distribution discovered in a layer. See https://www.freedesktop.org/software/systemd/man/os-release.html for explanations and example of fields.
repositoryRepositoryfalsenoneA package repository
issuedstringfalsenoneThe timestamp in which the vulnerability was issued
rangestringfalsenoneThe range of package versions affected by this vulnerability.
fixed_in_versionstringtruenoneA unique ID representing this vulnerability.

Enumerated Values

PropertyValue
normalized_severityUnknown
normalized_severityNegligible
normalized_severityLow
normalized_severityMedium
normalized_severityHigh
normalized_severityCritical

Distribution

{
  "id": "1",
  "did": "ubuntu",
  "name": "Ubuntu",
  "version": "18.04.3 LTS (Bionic Beaver)",
  "version_code_name": "bionic",
  "version_id": "18.04",
  "arch": "",
  "cpe": "",
  "pretty_name": "Ubuntu 18.04.3 LTS",
  "x-widdershins-oldRef": "#/components/examples/Distribution/value"
}

Distribution

Properties

NameTypeRequiredRestrictionsDescription
idstringtruenoneA unique ID representing this distribution
didstringtruenonenone
namestringtruenonenone
versionstringtruenonenone
version_code_namestringtruenonenone
version_idstringtruenonenone
archstringtruenonenone
cpestringtruenonenone
pretty_namestringtruenonenone

SourcePackage

{
  "id": "10",
  "name": "libapt-pkg5.0",
  "version": "1.6.11",
  "kind": "binary",
  "normalized_version": "",
  "arch": "x86",
  "module": "",
  "cpe": "",
  "source": {
    "id": "9",
    "name": "apt",
    "version": "1.6.11",
    "kind": "source",
    "source": null
  },
  "x-widdershins-oldRef": "#/components/examples/Package/value"
}

SourcePackage

Properties

NameTypeRequiredRestrictionsDescription
idstringtruenoneA unique ID representing this package
namestringtruenoneName of the Package
versionstringtruenoneVersion of the Package
kindstringfalsenoneKind of package. Source
sourcestringfalsenonenone
normalized_versionVersionfalsenoneVersion is a normalized claircore version, composed of a "kind" and an array of integers such that two versions of the same kind have the correct ordering when the integers are compared pair-wise.
archstringfalsenonenone
modulestringfalsenonenone
cpestringfalsenoneA CPE identifying the package

Package

{
  "id": "10",
  "name": "libapt-pkg5.0",
  "version": "1.6.11",
  "kind": "binary",
  "normalized_version": "",
  "arch": "x86",
  "module": "",
  "cpe": "",
  "source": {
    "id": "9",
    "name": "apt",
    "version": "1.6.11",
    "kind": "source",
    "source": null
  },
  "x-widdershins-oldRef": "#/components/examples/Package/value"
}

Package

Properties

NameTypeRequiredRestrictionsDescription
idstringtruenoneA unique ID representing this package
namestringtruenoneName of the Package
versionstringtruenoneVersion of the Package
kindstringfalsenoneKind of package. Source
sourceSourcePackagefalsenoneA source package affiliated with a Package
normalized_versionVersionfalsenoneVersion is a normalized claircore version, composed of a "kind" and an array of integers such that two versions of the same kind have the correct ordering when the integers are compared pair-wise.
archstringfalsenoneThe package's target system architecture
modulestringfalsenoneA module further defining a namespace for a package
cpestringfalsenoneA CPE identifying the package

Repository

{
  "id": "string",
  "name": "string",
  "key": "string",
  "uri": "string",
  "cpe": "string"
}

Repository

Properties

NameTypeRequiredRestrictionsDescription
idstringfalsenonenone
namestringfalsenonenone
keystringfalsenonenone
uristringfalsenonenone
cpestringfalsenonenone

Version

"pep440:0.0.0.0.0.0.0.0.0"

Version

Properties

NameTypeRequiredRestrictionsDescription
VersionstringfalsenoneVersion is a normalized claircore version, composed of a "kind" and an array of integers such that two versions of the same kind have the correct ordering when the integers are compared pair-wise.

Manifest

{
  "hash": "sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3",
  "layers": [
    {
      "hash": "sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3",
      "uri": "https://storage.example.com/blob/2f077db56abccc19f16f140f629ae98e904b4b7d563957a7fc319bd11b82ba36",
      "headers": {
        "property1": [
          "string"
        ],
        "property2": [
          "string"
        ]
      }
    }
  ]
}

Manifest

Properties

NameTypeRequiredRestrictionsDescription
hashDigesttruenoneA digest string with prefixed algorithm. The format is described here: https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests
Digests are used throughout the API to identify Layers and Manifests.
layers[Layer]truenone[A Layer within a Manifest and where Clair may retrieve it.]

Layer

{
  "hash": "sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3",
  "uri": "https://storage.example.com/blob/2f077db56abccc19f16f140f629ae98e904b4b7d563957a7fc319bd11b82ba36",
  "headers": {
    "property1": [
      "string"
    ],
    "property2": [
      "string"
    ]
  }
}

Layer

Properties

NameTypeRequiredRestrictionsDescription
hashDigesttruenoneA digest string with prefixed algorithm. The format is described here: https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests
Digests are used throughout the API to identify Layers and Manifests.
uristringtruenoneA URI describing where the layer may be found. Implementations MUST support http(s) schemes and MAY support additional schemes.
headersobjecttruenonemap of arrays of header values keyed by header value. e.g. map[string][]string
» additionalProperties[string]falsenonenone

BulkDelete

[
  "sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3"
]

BulkDelete

Properties

NameTypeRequiredRestrictionsDescription
BulkDelete[Digest]falsenoneAn array of Digests to be deleted.

Error

{
  "code": "string",
  "message": "string"
}

Error

Properties

NameTypeRequiredRestrictionsDescription
codestringfalsenonea code for this particular error
messagestringfalsenonea message with further detail

State

{
  "state": "aae368a064d7c5a433d0bf2c4f5554cc"
}

State

Properties

NameTypeRequiredRestrictionsDescription
statestringtruenonean opaque identifier

Digest

"sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3"

Digest

Properties

NameTypeRequiredRestrictionsDescription
DigeststringfalsenoneA digest string with prefixed algorithm. The format is described here: https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests
Digests are used throughout the API to identify Layers and Manifests.

Clairctl

clairctl is a command line tool for working with Clair. This CLI is capable of generating manifests from most public registries (dockerhub, quay.io, Red Hat Container Catalog) and submitting them for analysis to a running Clair.

Note that if the Clair instance has authentication configured, the value provided to the issuer flag must be on the list accepted by the server.

NAME:
   clairctl - interact with a clair API

USAGE:
   clairctl [global options] command [command options] [arguments...]

VERSION:
   0.1.0

DESCRIPTION:
   A command-line tool for clair v4.

COMMANDS:
   manifest         print a clair manifest for the named container
   report           request vulnerability reports for the named containers
   export-updaters  run updaters and export results
   import-updaters  import updates
   help, h          Shows a list of commands or help for one command

GLOBAL OPTIONS:
   -D                           print debugging logs (default: false)
   --config value, -c value     clair configuration file (default: "config.yaml") [$CLAIR_CONF]
   --issuer value, --iss value  jwt "issuer" to use when making authenticated requests (default: "clairctl")
   --help, -h                   show help (default: false)
   --version, -v                print the version (default: false)
NAME:
   clairctl manifest - print a clair manifest for the named container

USAGE:
   clairctl manifest [arguments...]

DESCRIPTION:
   print a clair manifest for the named container
NAME:
   clairctl report - request vulnerability reports for the named containers

USAGE:
   clairctl report [command options] container...

DESCRIPTION:
   Request and print a Clair vulnerability report for the named container(s).

OPTIONS:
   --host value           URL for the clairv4 v1 API. (default: "http://localhost:6060/") [$CLAIR_API]
   --out value, -o value  output format: text, json, xml (default: text)
NAME:
   clairctl export-updaters - run updaters and export results

USAGE:
   clairctl export-updaters [command options] [out]

DESCRIPTION:
   Run configured exporters and export to a file.

   A configuration file is needed to run this command, see 'clairctl help'
   for how to specify one.

OPTIONS:
   --strict  Return non-zero exit when updaters report errors. (default: false)
NAME:
   clairctl import-updaters - import updates

USAGE:
   clairctl import-updaters input...

DESCRIPTION:
   Import updates from files or HTTP URIs.

   A configuration file is needed to run this command, see 'clairctl help'
   for how to specify one.

Config

CLI Flags And Environment Variables

Clair is configured by a structured yaml or JSON1 file and an optional directory of "merge" and "patch" documents1. Each Clair node needs to specify what mode it will run in and a path to a configuration file via CLI flags or environment variables.

For example:

$ clair -conf ./path/to/config.yaml -mode indexer
$ clair -conf ./path/to/config.yaml -mode matcher
-mode 
    (also specified by CLAIR_MODE env variable)
    One of the following strings
    Sets which mode the clair instances will run in
    
    "indexer": runs just the indexer node
    "matcher": runs just the matcher node
    "notifier": runs just the notifier node
    "combo": will run all services on the same node.
-conf
    (also specified by CLAIR_CONF env variable)
    A file system path to Clair's config file

The above example starts two Clair nodes using the same configuration. One will only run the indexing facilities while the other will only run the matching facilities.

Environment variables respected by the Go standard library can be specified if needed. Some notable examples:

  • HTTP_PROXY
  • HTTPS_PROXY
  • SSL_CERT_DIR

If running in "combo" mode you must supply the indexer, matcher, and notifier configuration blocks in the configuration.

Configuration dropins

Starting in Clair version 4.7.0, dropin configuration files are supported.

Given a root configurtaion file of /etc/clair/config.json, all files matching the globs /etc/clair/config.json.d/*.json and /etc/clair/config.json.d/*.json-patch would be loaded in lexical order after the root configuration file. Similarly, given /etc/clair/config.yaml, all files matching the globs /etc/clair/config.yaml.d/*.yaml and /etc/clair/config.yaml.d/*.yaml-patch would be loaded. Only the extensions yaml and json are supported, and indicate yaml and JSON formatting, respectively.

The dropin files must have the same extension and format as the root file. Dropins with the bare suffix are treated as merge documents. Dropins with the -patch suffix are treated as patch documents and must contain a valid RFC 6902 structure. Yaml documents must be resolvable to the JSON subset.

Take care with the merge behavior around lists; a patch operation may be more suitable. The clairctl check-config command can be used to ensure a merged configuration is what is intended. In addition, placing test operations in a patch file that's evaluated last (such as zz-validate.json-patch) can be used to have Clair refuse to start if some configuration values are not what is intended.

The application defaults are applied after the configuration is loaded and as such, not reflected in the clairctl check-config command. The output of that command is also not currently suitable to be used to "compile" a config to a single file.

Deprecations and Changes

Starting in version 4.7.0, unknown keys are disallowed. Configurations that looked valid previously and loaded fine may now cause Clair to refuse to start.

In version 4.8.0, using Jaeger for trace submission was deprecated. Configurations that use Jaeger will print a warning. In future versions, using the Jaeger format may cause an error.

Configuration Reference

Please see the go module documentation for additional documentation on defaults and use.

http_listen_addr: ""
introspection_addr: ""
log_level: ""
tls: {}
indexer:
    connstring: ""
    scanlock_retry: 0
    layer_scan_concurrency: 0
    migrations: false
    scanner: {}
    airgap: false
matcher:
    connstring: ""
    indexer_addr: ""
    migrations: false
    period: ""
    disable_updaters: false
    update_retention: 2
matchers:
    names: nil
    config: nil
updaters:
    sets: nil
    config: nil
notifier:
    connstring: ""
    migrations: false
    indexer_addr: ""
    matcher_addr: ""
    poll_interval: ""
    delivery_interval: ""
    disable_summary: false
    webhook: null
    amqp: null
    stomp: null
auth: 
  psk: nil
trace:
    name: ""
    probability: null
    jaeger:
        agent:
            endpoint: ""
        collector:
            endpoint: ""
            username: null
            password: null
        service_name: ""
        tags: nil
        buffer_max: 0
    otlp:
	  http: {}
      grpc: {}
metrics:
    name: ""
    prometheus:
        endpoint: null

Note: the above just lists every key for completeness. Copy-pasting the above as a starting point for configuration will result in some options not having their defaults set normally.

$.http_listen_addr

A string in <host>:<port> format where <host> can be an empty string.

This configures where the HTTP API is exposed. See /openapi/v1 for the API spec.

$.introspection_addr

A string in <host>:<port> format where <host> can be an empty string.

This configures where Clair's metrics and health endpoints are exposed.

$.log_level

Set the logging level.

One of the following strings:

  • debug-color
  • debug
  • info
  • warn
  • error
  • fatal
  • panic

$.tls

TLS is a map containing the config for serving the HTTP API over TLS (and HTTP/2).

$.tls.cert

The TLS certificate to be used. Must be a full-chain certificate, as in nginx.

$.tls.key

A key file for the TLS certificate. Encryption is not supported on the key.

$.indexer

Indexer provides Clair Indexer node configuration.

$.indexer.airgap

Disables HTTP access to the Internet for indexers and fetchers. Private IPv4 and IPv6 addresses are allowed. Database connections are unaffected.

$.indexer.connstring

A Postgres connection string.

Accepts a format as a url (e.g., postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full) or a libpq connection string (e.g., user=pqgotest dbname=pqgotest sslmode=verify-full).

$.indexer.index_report_request_concurrency

Integer.

Rate limits the number of index report creation requests.

Setting this to 0 will attempt to auto-size this value. Setting a negative value means "unlimited." The auto-sizing is a multiple of the number of available cores.

The API will return a 429 status code if concurrency is exceeded.

$.indexer.scanlock_retry

A positive integer representing seconds.

Concurrent Indexers lock on manifest scans to avoid clobbering. This value tunes how often a waiting Indexer will poll for the lock.

$.indexer.layer_scan_concurrency

Positive integer limiting the number of concurrent layer scans.

Indexers will index a Manifest's layers concurrently. This value tunes the number of layers an Indexer will scan in parallel.

$.indexer.migrations

A boolean value.

Whether Indexer nodes handle migrations to their database.

$.indexer.scanner

Indexer configurations.

Scanner allows for passing configuration options to layer scanners. The scanner will have this configuration passed to it on construction if designed to do so.

$.indexer.scanner.dist

A map with the name of a particular scanner and arbitrary yaml as a value.

$.indexer.scanner.package

A map with the name of a particular scanner and arbitrary yaml as a value.

$.indexer.scanner.repo

A map with the name of a particular scanner and arbitrary yaml as a value.

$.matcher

Matcher provides Clair matcher node configuration.

$.matcher.cache_age

Duration string.

Controls how long clients should be hinted to cache responses for.

$.matcher.connstring

A Postgres connection string.

Accepts a format as a url (e.g., postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full) or a libpq connection string (e.g., user=pqgotest dbname=pqgotest sslmode=verify-full).

$.matcher.max_conn_pool

A positive integer limiting the database connection pool size.

Clair allows for a custom connection pool size. This number will directly set how many active database connections are allowed concurrently.

This parameter will be ignored in a future version. Users should configure this through the connection string.

$.matcher.indexer_addr

A string in <host>:<port> format where <host> can be an empty string.

A Matcher contacts an Indexer to create a VulnerabilityReport. The location of this Indexer is required.

$.matcher.migrations

A boolean value.

Whether Matcher nodes handle migrations to their databases.

$.matcher.period

A time.ParseDuration parseable string.

Determines how often updates for new security advisories will take place.

Defaults to 6 hours.

$.matcher.disable_updaters

A boolean value.

Whether to run background updates or not.

$.matcher.update_retention

An integer value limiting the number of update operations kept in the database.

Sets the number of update operations to retain between garbage collection cycles. This should be set to a safe MAX value based on database size constraints.

Defaults to 10.

If a value less than 0 is provided, GC is disabled. 2 is the minimum value to ensure updates can be compared for notifications.

$.matchers

Matchers provides configuration for the in-tree Matchers and RemoteMatchers.

$.matchers.names

A list of string values informing the matcher factory about enabled matchers.

If the value is nil the default list of Matchers will run:

  • alpine-matcher
  • aws-matcher
  • debian-matcher
  • gobin
  • java-maven
  • oracle
  • photon
  • python
  • rhel
  • rhel-container-matcher
  • suse
  • ubuntu-matcher

If an empty list is provided zero matchers will run.

$.matchers.config

Provides configuration to specific matcher.

A map keyed by the name of the matcher containing a sub-object which will be provided to the matchers factory constructor.

A hypothetical example:

config:
  python:
    ignore_vulns:
      - CVE-XYZ
      - CVE-ABC

$.updaters

Updaters provides configuration for the Matcher's update manager.

$.updaters.sets

A list of string values informing the update manager which Updaters to run.

If the value is nil (or null in yaml) the default set of Updaters will run:

  • alpine
  • aws
  • debian
  • oracle
  • osv
  • photon
  • rhcc
  • rhel
  • suse
  • ubuntu

If an empty list is provided zero updaters will run.

$.updaters.config

Provides configuration to specific updater sets.

A map keyed by the name of the updater set name containing a sub-object which will be provided to the updater set's constructor.

A hypothetical example:

config:
  ubuntu:
    security_tracker_url: http://security.url
    ignore_distributions: 
      - cosmic

$.notifier

Notifier provides Clair notifier node configuration.

$.notifier.connstring

A Postgres connection string.

Accepts a format as a url (e.g., postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full) or a libpq connection string (e.g., user=pqgotest dbname=pqgotest sslmode=verify-full).

$.notifier.migrations

A boolean value.

Whether Notifier nodes handle migrations to their database.

$.notifier.indexer_addr

A string in <host>:<port> format where <host> can be an empty string.

A Notifier contacts an Indexer to create obtain manifests affected by vulnerabilities. The location of this Indexer is required.

$.notifier.matcher_addr

A string in <host>:<port> format where <host> can be an empty string.

A Notifier contacts a Matcher to list update operations and acquire diffs. The location of this Indexer is required.

$.notifier.poll_interval

A time.ParseDuration parsable string.

The frequency at which the notifier will query at Matcher for Update Operations.

$.notifier.delivery_interval

A time.ParseDuration parsable string.

The frequency at which the notifier attempt delivery of created or previously failed notifications.

$.notifier.disable_summary

A boolean.

Controls whether notifications should be summarized to one per manifest or not.

$.notifier.webhook

Configures the notifier for webhook delivery.

$.notifier.webhook.target

URL where the webhook will be delivered.

$.notifier.webhook.callback

The callback url where notifications can be retrieved. The notification ID will be appended to this url.

This will typically be where the clair notifier is hosted.

$.notifier.webhook.headers

A map associating a header name to a list of values.

$.notifier.amqp

Configures the notifier for AMQP delivery.

Note: Clair does not declare any AMQP components on its own. All attempts to use an exchange or queue are passive only and will fail The broker administrators should setup exchanges and queues ahead of time.

$.notifier.amqp.direct

A boolean value.

If true the Notifier will deliver individual notifications (not a callback) to the configured AMQP broker.

$.notifier.amqp.rollup

Integer 0 or greater.

If direct is true this value will inform notifier how many notifications to send in a single direct delivery. For example, if direct is set to true and rollup is set to 5, the notifier will deliver no more then 5 notifications in a single json payload to the broker. Setting the value to 0 will effectively set it to 1.

$.notifier.amqp.exchange

The AMQP Exchange to connect to.

$.notifier.amqp.exchange.name

string value

The name of the exchange to connect to.

$.notifier.amqp.exchange.type

string value

The type of the exchange. Typically:

  • direct
  • fanout
  • topic
  • headers

$.notifier.amqp.exchange.durability

bool value

Whether the configured queue is durable or not.

$.notifier.amqp.exchange.auto_delete

bool value

Whether the configured queue uses an auto_delete policy.

$.notifier.amqp.routing_key

string value

The name of the routing key each notification will be sent with.

$.notifier.amqp.callback

a URL string

If direct is false, this URL is provided in the notification callback sent to the broker. This URL should point to Clair's notification API endpoint.

$.notifier.amqp.uris

list of URL strings

A list of one or more AMQP brokers to connect to, in priority order.

$.notifier.amqp.tls

Configures TLS connection to AMQP broker.

$.notifier.amqp.tls.root_ca

string value

The filesystem path where a root CA can be read.

$.notifier.amqp.tls.cert

string value

The filesystem path where a tls certificate can be read. Note that clair also respects SSL_CERT_DIR, as documented for the Go crypto/x509 package.

$.notifier.amqp.tls.key

string value

The filesystem path where a TLS private key can be read.

$.notifier.stomp

Configures the notifier for STOMP delivery.

$.notifier.stomp.direct

A boolean value.

If true, the Notifier will deliver individual notifications (not a callback) to the configured STOMP broker.

$.notifier.stomp.rollup

Integer 0 or greater.

If direct is true, this value will limit the number of notifications sent in a single direct delivery. For example, if direct is set to true and rollup is set to 5, the notifier will deliver no more then 5 notifications in a single json payload to the broker. Setting the value to 0 will effectively set it to 1.

$.notifier.stomp.callback

a URL string

If direct is false, this URL is provided in the notification callback sent to the broker. This URL should point to Clair's notification API endpoint.

$.notifier.stomp.destination

a string value

The STOMP destination to deliver notifications to.

$.notifier.stomp.uris

list of URL strings

A list of one or more STOMP brokers to connect to in priority order.

$.notifier.stomp.tls

Configures TLS connection to STOMP broker.

$.notifier.stomp.tls.root_ca

string value

The filesystem path where a root CA can be read. Note that clair also respects SSL_CERT_DIR, as documented for the Go crypto/x509 package.

$.notifier.stomp.tls.cert

string value

The filesystem path where a tls certificate can be read.

$.notifier.stomp.tls.key

string value

The filesystem path where a tls private key can be read.

$.notifier.stomp.user

Configures login details for the STOMP broker.

$.notifier.stomp.user.login

string value

The STOMP login to connect with.

$.notifier.stomp.user.passcode

string value

The STOMP passcode to connect with.

$.auth

Defines ClairV4's external and intra-service JWT based authentication.

If multiple auth mechanisms are defined, Clair will pick one. Currently, there are not multiple mechanisms.

$.auth.psk

Defines preshared key authentication.

$.auth.psk.key

a string value

A shared base64 encoded key distributed between all parties signing and verifying JWTs.

$.auth.psk.iss

a list of string value

A list of JWT issuers to verify. An empty list will accept any issuer in a JWT claim.

$.trace

Defines distributed tracing configuration based on OpenTelemetry.

$.trace.name

Which submission format to use, one of:

  • jaeger
  • otlp
  • sentry

$.trace.probability

a float value

The probability a trace will occur.

$.trace.jaeger

Defines values for Jaeger tracing.

NOTE: Jaeger has deprecated using the jaeger protocol and encouraging users to migrate to OTLP, which Jaeger can ingest natively.

$.trace.jaeger.agent

Defines values for configuring delivery to a Jaeger agent.

$.trace.jaeger.agent.endpoint

a string value

An address in <host>:<post> syntax where traces can be submitted.

$.trace.jaeger.collector

Defines values for configuring delivery to a Jaeger collector.

$.trace.jaeger.collector.endpoint

a string value

An address in <host>:<post> syntax where traces can be submitted.

$.trace.jaeger.collector.username

a string value

$.trace.jaeger.collector.password

a string value

$.trace.jaeger.service_name

a string value

$.trace.jaeger.tags

a mapping of a string to a string

$.trace.jaeger.buffer_max

an integer value

$.trace.otlp

Configuration for OTLP traces.

Only one of the http or grpc keys should be provided.

$.trace.otlp.http

Configuration for OTLP traces submitted by HTTP.

$.trace.otlp.http.url_path

Request path to use for submissions. Defaults to /v1/traces.

$.trace.otlp.http.compression

Compression for payloads. One of:

  • gzip
  • none
$.trace.otlp.http.endpoint

Host:port for submission. Defaults to localhost:4318.

$.trace.otlp.http.headers

Key-value pairs of additional headers for submissions.

$.trace.otlp.http.insecure

Use HTTP instead of HTTPS.

$.trace.otlp.http.timeout

Maximum of of time for a trace submission.

$.trace.otlp.http.client_tls.cert

Client certificate for connection.

$.trace.otlp.http.client_tls.key

Key for the certificate specified in cert.

$.trace.otlp.grpc

Configuration for OTLP traces submitted by gRPC.

$.trace.otlp.grpc.reconnect

Sets the minimum time between connection attempts.

$.trace.otlp.grpc.service_config

A string containing a JSON-format gRPC service config.

$.trace.otlp.grpc.compression

Compression for payloads. One of:

  • gzip
  • none
$.trace.otlp.grpc.endpoint

Host:port for submission. Defaults to localhost:4317.

$.trace.otlp.grpc.headers

Key-value pairs of additional headers for submissions.

$.trace.otlp.grpc.insecure

Do not verify the server certificate.

$.trace.otlp.grpc.timeout

Maximum of of time for a trace submission.

$.trace.otlp.grpc.client_tls.cert

Client certificate for connection.

$.trace.otlp.grpc.client_tls.key

Key for the certificate specified in cert.

$.trace.sentry

Configuration for submitting traces to Sentry.

This is done via OpenTelemetry instrumentation, so may not provide identical results compared to other tracing backends or native Sentry instrumentation.

There's no integration for error submission. This means that alternative implementations of the Sentry API that do not support submitting traces (such as GlitchTip) are not usable.

$.trace.sentry.dsn

Sentry DSN to use. See also sentry-go.ClientOptions.

$.trace.sentry.environment

Sentry environment to use. See also sentry-go.ClientOptions.

$.metrics

Defines distributed tracing configuration based on OpenTelemetry.

$.metrics.name

a string value

$.metrics.prometheus

Configuration for a prometheus metrics exporter.

$.metrics.prometheus.endpoint

a string value

Defines the path where metrics will be served.

$.metrics.otlp

Configuration for OTLP metrics.

Only one of the http or grpc keys should be provided.

$.metrics.otlp.http

Configuration for OTLP metrics submitted by HTTP.

$.metrics.otlp.http.url_path

Request path to use for submissions. Defaults to /v1/metrics.

$.metrics.otlp.http.compression

Compression for payloads. One of:

  • gzip
  • none
$.metrics.otlp.http.endpoint

Host:port for submission. Defaults to localhost:4318.

$.metrics.otlp.http.headers

Key-value pairs of additional headers for submissions.

$.metrics.otlp.http.insecure

Use HTTP instead of HTTPS.

$.metrics.otlp.http.timeout

Maximum of of time for a metrics submission.

$.metrics.otlp.http.client_tls.cert

Client certificate for connection.

$.metrics.otlp.http.client_tls.key

Key for the certificate specified in cert.

$.metrics.otlp.grpc

Configuration for OTLP metrics submitted by gRPC.

$.metrics.otlp.grpc.reconnect

Sets the minimum time between connection attempts.

$.metrics.otlp.grpc.service_config

A string containing a JSON-format gRPC service config.

$.metrics.otlp.grpc.compression

Compression for payloads. One of:

  • gzip
  • none
$.metrics.otlp.grpc.endpoint

Host:port for submission. Defaults to localhost:4318.

$.metrics.otlp.grpc.headers

Key-value pairs of additional headers for submissions.

$.metrics.otlp.grpc.insecure

Use HTTP instead of HTTPS.

$.metrics.otlp.grpc.timeout

Maximum of of time for a metrics submission.

$.metrics.otlp.grpc.client_tls.cert

Client certificate for connection.

$.metrics.otlp.grpc.client_tls.key

Key for the certificate specified in cert.


1

Support added in version 4.7.0.

Indexer

When Clair is running in Indexer mode, it is responsible for receiving Manifests and generating IndexReports. An IndexReport is an intermediate representation of a manifest's content and is used to discover vulnerabilities.

Matcher

When Clair is running in Matcher mode it is responsible for receiving IndexReports and generating VulnerabilityReports. A VulnerabilityReport describes the contents of a manifest and any vulnerabilities affecting it.

Notifier

When Clair is running in Notifier mode, it is responsible for generating notifications when new vulnerabilities affecting a previously indexed manifest enters the system. The notifier will send notifications via the configured mechanisms.

Clair exports metrics on the introspection port in the Prometheus format.

The exact metrics exposed are not considered API (and so are subject to change between releases) but should be well described. An up-to-date grafana dashboard example is in contrib/openshift/grafana.