Introduction to Amazon EKS

Amazon EKS diagram of different EKS workers.

In June of 2018, Amazon announced the general availability of their Elastic Container Service for Kubernetes. Given that at Anchore we deliver our products as Docker container images, it came as no surprise to us that our users and customers would begin deploying our software on EKS. Since Kubernetes, Kubernetes on AWS, and Anchore on EKS adoption have all increased, I thought it best to give EKS a shot.

Getting Started

For the scope of learning purposes, I thought I’d test out creating an EKS cluster, and launching a simple application. If you aren’t completely familiar with Kubernetes I highly recommend checking out the tutorials section of the website just so some of the concepts and verbiage I use make a little more sense. I also recommend reading about kubectl which is the command line interface for running actions against Kubernetes clusters.

In addition to the above reading, I also recommend the following:

Creating a Cluster

There are a couple of ways to create an EKS cluster, with the console or with the AWS CLI.

Create Cluster Using AWS Console

To begin, navigate here and select create cluster.

There are several pieces of information you’ll need to provide AWS for it to create your cluster successfully.

  • Cluster name (should be a unique name for your cluster)
  • Role name
  • VPC and Subnets
  • Security groups

Role name

Here I will need to select the IAM role that will allow Amazon EKS and the Kubernetes control plane to manage AWS resources on my behalf. If you have not already, you should create an EKS service role in the IAM console.

VPC and subnets

Select a VPC and choose the subnets in the selected VPC where the worker nodes will run. If you have not created a VPC, you will need to create one in the VPC console and create subnets as well. AWS has a great tutorial on VPC and Subnet creation here.

Note: Subnets specified must be in at least two different availability zones.

Security groups

Here I choose security groups to apply to network interfaces that are created in my subnets to allow the EKS control plane to communicate with worker nodes.

Once all the necessary requirements have been fulfilled, I can create the cluster.

Create cluster using AWS CLI

I can also create a cluster via the AWS CLI by running the following:

aws eks --region region create-cluster --name devel --role-arn arn:aws:iam::111122223333:role/eks-service-role-AWSServiceRoleForAmazonEKS-EXAMPLEBKZRQR --resources-vpc-config subnetIds=subnet-a9189fe2,subnet-50432629,securityGroupIds=sg-f5c54184

I would simply update --role-arnsubnetIds, and securityGroupIds into the above command.

Once my cluster has been created the console looks like the following:

Amazon EKS clusters screen.

Next I can use the AWS CLI update-kubeconfig command to create or update my kubeconfig for my cluster.

aws eks --region us-east-2 update-kubeconfig --name anchore-demo

Then I test the configuration: kubectl get svc

Which outputs the following:

NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   172.20.0.1   <none>        443/TCP   7m

Launch Worker Nodes

I created and launched worker nodes via the AWS CloudFormation console. It is important to note that Amazon EKS worker nodes are just standard Amazon EC2 instances. To create the stack, I simply selected create stack and added this Amazon S3 template URL, then I just filled out the parameters on the following screens.

Next, I need to enable the worker nodes to join the cluster. I will do so by downloading and editing the AWS authenticator configuration map.

apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    - rolearn: <ARN of instance role (not instance profile)>
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes

Note – The ARN of instance role is the NodeInstanceRole value you can see in the output of the creation of your CloudFormation stack.

Next, apply the configuration: kubectl apply -f aws-auth-cm.yaml and view the nodes: kubectl get nodes

NAME                                       STATUS    ROLES     AGE       VERSION
ip-10-0-1-112.us-east-2.compute.internal   Ready     <none>    3m        v1.11.5
ip-10-0-1-36.us-east-2.compute.internal    Ready     <none>    3m        v1.11.5
ip-10-0-3-21.us-east-2.compute.internal    Ready     <none>    3m        v1.11.5

I can also view them in the EC2 console:

EC2 console clusters.

Working with Services

In Kubernetes, a LoadBalancer service is a service that points to external load balancers that are not in your Kubernetes clusters. In the case of AWS, and this blog, an external load balancer (ELB) will be created automatically when I create a LoadBalancer service.

In order to do this, I must first define my service like so:

# my-loadbalancer-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: mylbservice
spec:
  type: LoadBalancer
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

Then I simply create the service.

kubectl create -f my-loadbalancer-service.yaml

To verify, I can describe my service.

kubectl describe service mylbservicet

Which outputs the following:

Name:                     mylbservice
Namespace:                default
Labels:                   <none>
Annotations:              <none>
Selector:                 app=nginx
Type:                     LoadBalancer
IP:                       172.20.16.171
LoadBalancer Ingress:     a0564b91c4b7711e99cfb0a558a37aa8-1932902294.us-east-2.elb.amazonaws.com
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
NodePort:                 <unset>  32005/TCP
Endpoints:                10.0.1.100:80,10.0.1.199:80,10.0.3.19:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:
  Type    Reason                Age   From                Message
  ----    ------                ----  ----                -------
  Normal  EnsuringLoadBalancer  10m   service-controller  Ensuring load balancer
  Normal  EnsuredLoadBalancer   10m   service-controller  Ensured load balancer

I can see the created load balancer by navigating to the EC2 console and selecting Load Balancers

alt text

Or better yet, hit the LoadBalancer Ingress:

Confirmation of nginx installed and working.

Conclusion

Now I just quickly walked through a simple application deployment on EKS. As you’ve probably gathered, the most challenging part is all the setup. When you are ready to start deploying more complex containerized applications on EKS you now have all the steps needed to get a cluster set up quickly. At Anchore, I’m always excited to work with our users and customers leveraging Amazon EKS to run Anchore. To find out more about how Anchore can be deployed, I highly recommend checking out our helm chart and reading more about Helm on EKS.

There is certainly a learning curve to Amazon EKS that requires a bit of knowledge on several different Amazon services in order to manage Kubernetes clusters effectively. By far the longest piece of this was getting the cluster set up. Any AWS-heavy users should be thrilled about the ability to make running containerized workloads in Kubernetes easy and cost-effective on the most popular cloud provider. With AWS still reigning as the top public cloud provider, it is only fitting Amazon created a service to adhere to the tremendous amount of container and Kubernetes adoption over the past two years.

Operational Awareness and Performance Tuning For Anchore Part 2

If you haven’t read Part 1 please do so before reading this article as we rely heavily on concepts and vocabulary established in that article. In this article, we’ll dive more deeply into matching metrics gathered in our first part with opportunities to tune the performance of a given Anchore deployment.

Just to refresh, the steps for image analysis and evaluation in Anchore Engine is as follows:

1) The Image is downloaded.
2) The Image is unpacked.
3) The Image is analyzed locally.
4) The result of the analysis is uploaded to core services.
5) The analysis data that was uploaded to core services is then evaluated during a vulnerability scan or policy evaluation.

Steps 1-3 are the most intensive operational costs for Anchore. Package, file and image analysis is both CPU and Disk intensive as operations. Making sure we’re on a host that has good disk throughput and high single-thread CPU performance will help greatly here.

Overall deployment performance depends on a few things: how the services interact and can scale together, how performant the database service is in response to the rest of the services, and how each service is provisioned with their own resources.

How To Improve Step 1: Enable Layer Caching

It is very likely that many of your images share common layers, especially if a standard base image is being used to build services. Performance can be improved by using caching on each of those layers contained in your image manifest. Anchore has a setting that enables a layer-specific caching for analyzers in order to reduce operational cost over time. In your Prometheus analysis, look at anchore_analysis_time_seconds for insight into when layer caching would be beneficial.

To enable the cache you can define a temp directory the config.yaml for each analyzer, shown below. We should make sure that whatever you define should have the same throughput considerations as scaling the overall container throughput: Make sure you have fast SSD or local disk to each analyzer, as the cache layer is not shared between nodes, and is ephemeral.

If we have set the following mount for a tmp_dir:

tmp_dir: '/scratch'

Then in order to utilize /scratch within the container make sure config.yaml is updated to use /scratch as the temporary directory for image analysis. We suggest the temporary directory should be sized to at least 3 times the uncompressed image size to be analyzed. To enable the layer caching, let’s enable the “layer_cache_enable” parameter and the “layer_cache_max_gigabytes” parameter as follows:

analyzer:
    enabled: True
    require_auth: True
    cycle_timer_seconds: 1
    max_threads: 1
    analyzer_driver: 'nodocker'
    endpoint_hostname: '${ANCHORE_HOST_ID}'
    listen: '0.0.0.0'
    port: 8084
    layer_cache_enable: True
    layer_cache_max_gigabytes: 4

In this example, the cache is set to 4 gigabytes. The temporary volume should be sized to at least 3 times the uncompressed image size + 4 gigabytes. The minimum size for the cache is 1 gigabyte and the cache uses a least recently used (LRU) policy. The cache files will be stored in the anchore_layercache directory of the /tmp_dir volume.

How To Improve Steps 2-3: Improve Service I/O Throughput

This is pretty straight forward: better throughput performance for CPU and disk will improve the most I/O and CPU intensive tasks of Anchore’s analysis process. High single-thread CPU performance and fast disk read/write speeds for each Anchore analyzer service will speed up the steps where we pull, extract and do file analysis of any given container image. On premise, this may mean a beefier CPU spec and SSDs in your bare metal. In the cloud, you may be choosing to not run EBS to back your analyzer tmp directories and selecting for higher compute instances.

How To Improve Step 4: Scaling Anchore Engine Components

This tip is to address a very wide scope of performance, so there’s a wide scope of metrics to be watching, but in general scaling analyzer services and core services according to a consistent ratio is one way to ensure throughput overall can be maintained. In general, we suggest 1 core service for every 4 analyzers. Keeping this scale means that we can ensure throughput for core services grows with the number of analyzers.

How To Improve Step 5: Tune Max Connection Settings For Postgres

One of the most common questions about deploying Anchore in production is how to architect the Postgres instance used by Anchore Engine. While Anchore has installation methods that include a Postgres service container in our docker-compose YAML and helm chart, we do expect that production deployments will not use that Postgres container and instead will utilize a Postgres service, either on-premises or in the cloud (such as RDS, etc.) Using a cloud service like Using something like RDS is not only an easy way to control allocated resources to your DB instance but RDS specifically also automatically configures Postgres with pretty good settings for the chosen instance type out of the box.

For a more in-depth guide on tuning your Postgres deployments, you’ll want to consult with Postgres documentation or use a tool like pg_tune. For this guide’s purpose, we can check the performance stats in the DB with “select * from pg_stat_activity;” executed in your Postgres container.

When you are looking at Postgres performance stats from your pg_stat_activity table, you want to pay attention to connection statistics. Every Anchore service touches the database, and every service has a config YAML file where you can set client pool connections with a default set to 30. The setting on the anchore services side control how many client connections each service can make concurrently. In the Postgres configuration, max connections control how many clients total can connect at once. Anchore uses sql alchemy, which employs a connection pooling technique, so each service may allocate connection pool size number of client connections.

For example, in pg_stat_database we can see numbackends. We can from that number and the max_connections setting in pg_settings infer how close we are to forcing connection waits. This is because the percentage of max connections in use is numbackends as a percentage of max_connections. In a nutshell, with Anchore database client connections setting at 300, and deployment with 100 Anchore services, that could lead to 30000 client connections to the database. Without adjusting max_connections, this could lead to a serious bottleneck.

We typically recommend leaving the Anchore client max connection setting at its defaults and bumping up the max connections in Postgres configuration appropriately. With the client default at 30, the corresponding max connections setting for our deployment of 100 Anchore services should be at least 3000 (30 * 100). As long as your database has enough resources to handle incoming connections then the Anchore service pool won’t bottleneck.

I want to caution that this guide isn’t the comprehensive list of things that can be tuned to help performance. It is intended to address a wide audience and is based on the most common performance issues we’ve seen in the field.

Going Deeper with Anchore Policies, Using Whitelists

At Anchore we are consistently working with our users and customers to help them gain better insight into the contents of their container images, and more importantly, helping them create rules to enforce security, compliance, and best practices. The enforcement element is achieved through Anchore policy evaluations, and more specifically, the rules are defined within the policies component of a policy bundle.

Anchore policy bundles are the unit of policy definition and evaluation. Anchore users may have multiple policy bundles, but for policy evaluation, the user must specify a bundle to be evaluated or default to the bundle currently marked as active. One of the components of a policy bundle is whitelists. A whitelist is a set of exclusion rules for trigger matches found during policy evaluation. A whitelist defines a specific gate and trigger_id that should have its action recommendation statically set to go. When a policy rule result is whitelisted, it is still present in the output of the policy evaluation, but its action is set to go and it is indicated that there was a whitelist match. The overarching idea is to give developers, operations, and security team members an effective mechanism for ignoring vulnerability matches that are known to be false positives or ignoring vulnerabilities on specific packages (if they have been patched), or any other agreed-upon reason for creating a whitelist rule.

Whitelists in Anchore Enterprise

Within the Anchore Enterprise UI, navigating to the Whitelists tab will show the lists of whitelists that are currently present in the current policy bundle.

Anchore Enterprise whitelists tab for policy bundles.

Selecting the edit button on the far right under the action column will bring up the whitelist editor where users have the ability to create new whitelist entries or modify existing ones.

Anchore whitelist editor for list items.

The example whitelist above is represented as JSON below:

{
  "comment": "Default global whitelist",
  "id": "37fd763e-1765-11e8-add4-3b16c029ac5c",
  "items": ,
  "name": "Global Whitelist",
  "version": "1_0"
}

Components of a Whitelist in Anchore

 

  • Gate: The gate to whitelist matches from (ensures trigger_ids are not matched in the wrong context).
  • Trigger Id: The specific trigger result to match and whitelist. This id is gate/trigger specific as each trigger may have its own trigger_id format. Most commonly, the CVE trigger ids produced by the vulnerability package gate-trigger. The trigger_id may include wildcards for partial matches as shown with the second item.
  • Id: An identifier for the whitelist rule. It only needs to be unique within the whitelist object itself.

It is important to note that if a whitelist item matches a policy trigger output, the action for that particular output is set to go and the policy evaluation result will inform the user that the trigger output was matched for a whitelist item.

Uploading a Whitelist in Anchore Enterprise

Through the UI, Anchore users have the option to upload a whitelist by selecting the Upload Whitelist button which brings up the following:

Uploading a whitelist to Anchore platform.

Viewing Whitelisted Entries

Anchore users can view the whitelisted entries in the Policy Evaluation table as shown below:

View whitelist entries in the Anchore policy evaluation tab.

Additionally, users can optionally Add / Remove a particular whitelist item as shown below:

Add or remove whitelist items from Anchore.

Conclusion

When working with the security risks associated with both the operating system and non-operating system packages, ignoring issues is sometimes a necessary action. At Anchore, the goal is to provide teams a solid means of managing vulnerabilities and packages that may need to be suppressed. Due to the fact that working with whitelists and policies carries a certain level of risk, Anchore Enterprise provides role-based access control to make policy editing only available to users who have been assigned the appropriate level of permissions. In the example below, the current user only has ‘read-only’ access and cannot make any changes to the whitelist.

Anchore policy bundles on platform.

When working with whitelisted items, it is important to remember that this does not mean there are no longer security issues, only that these particular items now have a go output associated with them. Remember to use carefully and in moderation. Lastly, as with any CVE remediation and policy rule creation, consult across your development, security, and operations teams to collectively come up with acceptable actions that best suit your organization’s security and compliance requirements.

Further information on Anchore Enterprise can be found on our website.

Operational Awareness & Performance Tuning For Anchore

This series will focus on topics taken directly from customer interactions, community discussion and practical, real-world use of Anchore Engine deployments. The goal will hopefully be to provide lessons learned from real-world deployments of Anchore.

Part 1: Concepts and Metrics

In the first set of posts in this series, I will walk through how to evaluate and tune your Anchore deployment for better image analysis performance. To do so, we’ll discuss the actions Anchore Engine takes to pull, analyze and evaluate images and how that is affected by configuration and deployment architecture. We’ll also point out how you can get metrics on each of these functions to determine what you can do to improve the performance of your deployment.

Firstly, I want to take a moment and thank the Anchore Community Slack and also the Anchore Engineering team for helping me delve very deeply into this. They’ve been fantastic, and if you haven’t done so yet make sure you join our slack community to keep up to date with the project and product, as well as exchange ideas with the rest of the community.

One thing to understand about Anchore’s approach is that the acts of image analysis (downloading and analyzing the image contents) and of image scanning (for vulnerabilities) are separate steps. Image analysis only needs to happen once for any given image digest. The image digest is a unique ID for a given image content set, and Anchore is capable of watching an image tag in an upstream repository and detect when a new version of the content of that image (the digest) has been associated with a tag.

Vulnerability scans and policy evaluations are performed against any image (digest) that has been analyzed. When updates happen to either a vulnerability feed or a policy bundle, Anchore can re-scan an image to produce the latest vulnerability report or policy evaluation report for any given digest without the need to re-analyze the image (digest).

Put simply, our discovery of the contents of an image digest is separate from our evaluation of the vulnerabilities or policy compliance of the same said image digest. Image analysis (ie: the downloading, unpacking and discovery of contents of an image digest) is a far more expensive operation from an I/O perspective than image scanning (ie: the scanning said image digest analysis data for vulnerabilities or policy evaluation.)

Let’s review what actually happens to a container image (digest) as Anchore Engine consumes and analyzes it:

1) The Image is downloaded.
2) The Image is unpacked.
3) The Image is analyzed locally.
4) The result of the analysis is uploaded to core services.
5) The analysis data that was uploaded to core services is then evaluated during a vulnerability scan or policy evaluation.

The first four steps are what we call image analysis. That last step is image evaluation. Each of those actions has specific performance implications in your deployment.

Most importantly is to know what parts of your deployment require changes to improve performance and to do that we need information. Let’s start by enabling metrics on our Anchore Engine deployment.

To enable the metrics option in Anchore Engine, look to set the following in your Anchore Engine configuration file config.yaml:

metrics:
  enabled: True

Once that is enabled and the services brought up a /metrics route will be exposed on all individual Anchore services that are listening on a network interface and require authentication. You can then configure Prometheus to scrape data from each Anchore service. These are those services:

1) apiext: Running on port 8228 this is the External API service.
2) catalog: Running on port 8082 this is the internal catalog service.
3) simplequeue: Running on port 8083 this is the internal queuing service
4) analyzer: Running on port 8084 this is the service that analyzes the containers pulled into Anchore.
5) policy_engine: Running on port 8087 this is the internal service that provides the policy engine for evaluation and action on the analyzed containers.

Only the external API service is typically enabled for external access. All other services are used only by the Anchore Engine. Prometheus should have network access to each service to be scraped and the Prometheus service should be configured with credentials to access the engine. Here’s an example:

global:
  scrape_interval: 15s
  scrape_timeout: 10s
  evaluation_interval: 15s
alerting:
  alertmanagers:
  - static_configs:
    - targets: []
    scheme: http
    timeout: 10s
scrape_configs:
- job_name: anchore-api
  scrape_interval: 15s
  scrape_timeout: 10s
  metrics_path: /metrics
  scheme: http
  static_configs:
  - targets:
    - anchore-engine:8228
  basic_auth:
    username: admin
    password: foobar

- job_name: anchore-catalog
  scrape_interval: 15s
  scrape_timeout: 10s
  metrics_path: /metrics
  scheme: http
  static_configs:
  - targets:
    - anchore-engine:8082
  basic_auth:
    username: admin
    password: foobar

- job_name: anchore-simplequeue
  scrape_interval: 15s
  scrape_timeout: 10s
  metrics_path: /metrics
  scheme: http
  static_configs:
  - targets:
    - anchore-engine:8083
  basic_auth:
    username: admin
    password: foobar

- job_name: anchore-analyzer
  scrape_interval: 15s
  scrape_timeout: 10s
  metrics_path: /metrics
  scheme: http
  static_configs:
  - targets:
    - anchore-engine:8084
  basic_auth:
    username: admin
    password: foobar

- job_name: anchore-policy-engine
  scrape_interval: 15s
  scrape_timeout: 10s
  metrics_path: /metrics
  scheme: http
  static_configs:
  - targets:
    - anchore-engine:8087
  basic_auth:
    username: admin
    password: foobar

This config file would go into the anchore-prometheus.yaml file created as part of the docker-compose or helm deployment.

The last bit of information you’ll want is metrics on the performance of your postgres service. For Anchore specifically, we want to know mostly about connection statistics and I/O timing. This can be discovered with the execution of something like “select * from pg_stat_activity;” within your DB container. If you need help exploring your postgres instance inside of Docker, here’s a good post to use as reference: https://markheath.net/post/exploring-postgresql-with-docker

Knowing how long it takes your Anchore deployment to scan your images, how the other services are receiving and sending data, and having metrics on the postgres database performance is key to knowing where you can help tune your system.

If you would like to see the metrics from Prometheus you need only hit the API endpoint for the service you want metrics on using an authenticated call. For example, using a docker-compose exec command, it would look like this:

docker-compose exec anchore-engine curl http://admin:foobar@localhost:8087/metrics

That’s a call to get metrics on the policy engine. Refer to the config YAML above to hit the port needed for the service you would require metrics from.

In Part 2 of this series we will go in-depth to break down the functional steps described above to match them with the gathered metrics, and then evaluate how to tune our configuration and deployment accordingly.

Inline scanning with Anchore Engine

Note: Anchore Engine’s feed service will be deprecated in April 2022 (per this announcement) in favor of improved open source tools, Syft and Grype. For full container vulnerability scanning and policy & compliance solutions that address the increasing security demands of the software supply chain, check out Anchore Enterprise.

With Anchore Engine, users can scan container images to generate reports against several aspects of the container image – vulnerability scans, content reports (files, OS packages, language packages, etc), fully customized policy evaluations (Dockerfile checks, OSS license checks, software package checks, security checks, and many more). With these capabilities, users have integrated an anchore-engine image scan into CI/CD build processes for both reporting and/or control decision purposes, as anchore policy evaluations include a ‘pass/fail’ result alongside a full report upon policy execution.

Up until now, the general setup required to achieve such integration has included the requirement to stand up an anchore-engine service, with its API exposed to your CI/CD build process and make thin anchore API client calls from the build process to the centralized anchore-engine deployment. Generally, the flow starts with an API call to ‘add’ an image to anchore-engine via an API call to the engine, at which point the engine will pull the referenced image from a docker v2 registry, and then perform report generation queries and/or policy evaluation calls. This method is still fully supported, and in many cases is a good architecture for integrating anchore into your CI/CD platform. However, there are other use cases where the same result is desired (image scans, policy evaluations, content reports, etc), but for a variety of reasons, it is impractical for the user to operate a centralized, managed and stable anchore-engine deployment that is available to CI/CD build processes.

To accommodate these cases, we are introducing a new way to interact with anchore to get image scans, evaluations, and content reports without requiring a central anchore-engine deployment to be available. We call this new approach ‘inline scan’, to indicate that a single, one-time scan can be performed ‘inline’ against a local container image at any time, without the need for any persistent data or service state between scans. Using this approach (which ultimately uses exactly the same analysis/vulnerability/policy evaluation and reporting functions of anchore-engine), users can achieve an integration with anchore that moves the analysis/scanning work to a local container process that can be run during the container image build pipeline, after an image has been built but before it is pushed to any registry.

With this new functionality, we hope to provide another approach for users to get deep analysis, scanning and policy evaluation capabilities of anchore in situations where operating a central anchore-engine service is impractical.

Using the inline_scan Script

To make using our inline-scan container as easy as possible, we have provided a simple wrapper script called inline_scan. The only requirement to run the inline_scan script is the ability to execute Docker commands & bash. We host a versioned copy of this script that can be downloaded directly with curl and executed in a bash pipeline, providing you image inspection, reporting and policy enforcement with one command.

To run the script on your workstation, use the following command syntax.

curl -s https://ci-tools.anchore.io/inline_scan-v0.6.0 | bash -s -- [options] IMAGE_NAME(s)

Inline_scan Options

-b  [optional] Path to local Anchore policy bundle.
-d  [optional] Path to local Dockerfile.
-v  [optional] Path to directory to be mounted as docker volume. All image archives in directory will be scanned.
-f  [optional] Exit script upon failed Anchore policy evaluation.
-p  [optional] Pull remote docker images.
-r  [optional] Generate analysis reports in your current working directory.
-t  [optional] Specify timeout for image scanning in seconds (defaults to 300s).

Examples

Pull multiple images from DockerHub, scan them all and generate individual reports in ./anchore-reports.

curl -s https://ci-tools.anchore.io/inline_scan-v0.6.0 | bash -s -- -p -r alpine:latest ubuntu:latest centos:latest

Perform a local docker build, then pass the Dockerfile to anchore inline scan. Use a custom policy bundle to ensure Dockerfile compliance, failing the script if anchore policy evaluation does not pass.

docker build -t example-image:latest -f Dockerfile .
curl -s https://ci-tools.anchore.io/inline_scan-v0.6.0 | bash -s -- -f -d Dockerfile -b .anchore-policy.json example-image:latest

Save multiple docker image archives to a directory, then mount the entire directory for analysis using a timeout of 500s.

cd example1/
docker build -t example1:latest .
cd ../example2
docker build -t example2:latest .
cd ..
mkdir images/
docker save example1:latest -o images/example1+latest.tar
docker save example2:latest -o images/example2+latest.tar
curl -s https://ci-tools.anchore.io/inline_scan-v0.6.0 | bash -s -- -v ./images -t 500

Using Anchore Inline Scan in Your Build Pipeline

This same functionality can be utilized on any CI/CD platform that allows the execution of Docker commands. The remainder of this post will be going over implementations of the anchore inline scan on a variety of popular CI/CD platforms.

All of the following examples can be found in this repository.

CircleCI Implementation

CircleCI version 2.0+ allows native docker command execution with the setup_remote_docker job step. By using this functionality combined with an official docker:stable image, we can build, scan, and push our images within the same job. We will also create reports and save them as artifacts within CircleCI. These reports are all created in JSON format, allowing easy aggregation from CircleCI into your preferred reporting tool.

This workflow requires the DOCKER_USER & DOCKER_PASS environment variables to be set in a context called dockerhubin your CircleCI account settings at settings -> context -> create

Config.yml

version: 2.1
jobs:
  build_scan_image:
    docker:
    - image: docker:stable
    environment:
      IMAGE_NAME: btodhunter/anchore-ci-demo
      IMAGE_TAG: circleci
    steps:
    - checkout
    - setup_remote_docker
    - run:
        name: Build image
        command: docker build -t "${IMAGE_NAME}:ci" .
    - run:
        name: Scan image
        command: |
          apk add curl bash
          curl -s https://ci-tools.anchore.io/inline_scan-v0.6.0 | bash -s -- -r "${IMAGE_NAME}:ci"
    - run:
        name: Push to DockerHub
        command: |
          echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin
          docker tag "${IMAGE_NAME}:ci" "${IMAGE_NAME}:${IMAGE_TAG}"
          docker push "${IMAGE_NAME}:${IMAGE_TAG}"
    - store_artifacts:
        path: anchore-reports/
  
workflows:
  scan_image:
    jobs:
    - build_scan_image:
        context: dockerhub

GitLab Implementation

GitLab allows docker command execution through a docker:dind service container. This job pushes the image to the GitLab registry, using built-in environment variables for specifying the image name and registry login credentials. To prevent premature timeouts, the timeout has been increased to 500s with the -t option. Reports are generated using the -r option, which are then passed as artifacts to be stored in GitLab. Even if you’re not using an aggregation tool for artifacts, the json format allows reports to be parsed and displayed within GitLab pipeline using simple command line tools like jq.

.gitlab-ci.yml

variables:
  IMAGE_NAME: ${CI_REGISTRY_IMAGE}/build:${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHA}

stages:
- build

container_build:
  stage: build
  image: docker:stable
  services:
  - docker:stable-dind

  variables:
    DOCKER_DRIVER: overlay2

  script:
  - echo "$CI_JOB_TOKEN" | docker login -u gitlab-ci-token --password-stdin "${CI_REGISTRY}"
  - docker build -t "$IMAGE_NAME" .
  - apk add bash curl 
  - curl -s https://ci-tools.anchore.io/inline_scan-v0.6.0 | bash -s -- -r -t 500 "$IMAGE_NAME"
  - docker push "$IMAGE_NAME"
  - |
      echo "Parsing anchore reports."
      for f in anchore-reports/*; do
        if [[ "$f" =~ "content-os" ]]; then
          printf "n%sn" "The following OS packages are installed on ${IMAGE_NAME}:"
          jq '[.content | sort_by(.package) | .[] | {package: .package, version: .version}]' $f || true
        fi
        if [[ "$f" =~ "vuln" ]]; then
          printf "n%sn" "The following vulnerabilities were found on ${IMAGE_NAME}:"
          jq '[.vulnerabilities | group_by(.package) | .[] | {package: .[0].package, vuln: [.[].vuln]}]' $f || true
        fi
      done

  artifacts:
    name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}
    paths:
    - anchore-reports/*

CodeShip Implementation

Docker command execution is enabled by default in CodeShip, which allows the inline_scan script to run on the docker:stable image without any additional configuration. By specifying the -f option on the inline_scan script, this job ensures that an image that fails it’s anchore policy evaluation will not be pushed to the registry. To ensure adherence to the organization’s security compliance policy, a custom policy bundle can be utilized for this scan by passing the -b <POLICY_BUNDLE_FILE> option to the inline_scan script.

This job requires creating an encrypted environment variable file for loading the DOCKER_USER & DOCKER_PASS variables into your job. See – Encrypting CodeShip Environment Variables.

codeship-services.yml

anchore:
  add_docker: true
  image: docker:stable-git
  environment:
    IMAGE_NAME: btodhunter/anchore-ci-demo
    IMAGE_TAG: codeship
  encrypted_env_file: env.encrypted

codeship-steps.yml

- name: build-scan
  service: anchore
  command: sh -c 'apk add bash curl &&
    mkdir -p /build && 
    cd /build &&
    git clone https://github.com/Btodhunter/ci-demos.git . &&
    docker build -t "${IMAGE_NAME}:ci" . &&
    curl -s https://ci-tools.anchore.io/inline_scan-v0.6.0 | bash -s -- -f -b .anchore_policy.json "${IMAGE_NAME}:ci" &&
    echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin &&
    docker tag "${IMAGE_NAME}:ci" "${IMAGE_NAME}:${IMAGE_TAG}" &&
    docker push "${IMAGE_NAME}:${IMAGE_TAG}"'

Jenkins Pipeline Implementation

Jenkins configured with the Docker, BlueOcean, and Pipeline plugins support docker command execution using the shdirective. By using the -d <PATH_TO_DOCKERFILE> option with the inline_scan script, you can pass your Dockerfile to anchore-engine for policy evaluation. With the -b <PATH_TO_POLICY_BUNDLE> option, a custom policy bundle can be passed to the inline scan to ensure your Dockerfile conforms to best practices.

To allow pushing to a private registry, the dockerhub-creds credentials must be created in the Jenkins server settings at – Jenkins -> Credentials -> System -> Global credentials -> Add Credentials

This example was tested against the Jenkins installation detailed here, using the declarative pipeline syntax – Jenkins Pipeline Docs

Jenkinsfile

pipeline{
    agent {
        docker {
            image 'docker:stable'
        }
    }
    environment {
        IMAGE_NAME = 'btodhunter/anchore-ci-demo'
        IMAGE_TAG = 'jenkins'
    }
    stages {
        stage('Build Image') {
            steps {
                sh 'docker build -t ${IMAGE_NAME}:ci .'
            }
        }
        stage('Scan') {
            steps {        
                sh 'apk add bash curl'
                sh 'curl -s https://ci-tools.anchore.io/inline_scan-v0.6.0 | bash -s -- -d Dockerfile -b .anchore_policy.json ${IMAGE_NAME}:ci'
            }
        }
        stage('Push Image') {
            steps {
                withDockerRegistry([credentialsId: "dockerhub-creds", url: ""]){
                    sh 'docker tag ${IMAGE_NAME}:ci ${IMAGE_NAME}:${IMAGE_TAG}'
                    sh 'docker push ${IMAGE_NAME}:${IMAGE_TAG}'
                }
            }
        }
    }
}

TravisCI Implementation

TravisCI allows docker command execution by default, which makes integrating Anchore Engine as simple as adding the inline_scan script to your existing image build pipeline. This analysis should be performed before pushing the image to your registry of choice.

The DOCKER_USER & DOCKER_PASS environment variables must be setup in the TravisCI console at repository -> settings -> environment variables

.travis.yml

language: node_js

services:
  - docker

env:
  - IMAGE_NAME="btodhunter/anchore-ci-demo" IMAGE_TAG="travisci"

script:
  - docker build -t "${IMAGE_NAME}:ci" .
  - curl -s https://ci-tools.anchore.io/inline_scan-v0.6.0 | bash -s -- "${IMAGE_NAME}:ci"
  - echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin
  - docker tag "${IMAGE_NAME}:ci" "${IMAGE_NAME}:${IMAGE_TAG}"
  - docker push "${IMAGE_NAME}:${IMAGE_TAG}"

AWS CodeBuild Implementation

AWS CodeBuild supports docker command execution by default. The Anchore inline_scan script can be inserted right into your pipeline before the image is pushed to its registry.

The DOCKER_USERDOCKER_PASSIMAGE_NAME, & IMAGE_TAG environment variables must be set in the CodeBuild console at Build Projects -> <PROJECT_NAME> -> Edit Environment -> Additional Config -> Environment Variables

buildspec.yml

version: 0.2

phases:
  build:
    commands:
      - docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .

  post_build:
    commands:
      - curl -s https://ci-tools.anchore.io/inline_scan-v0.6.0 | bash -s -- ${IMAGE_NAME}:${IMAGE_TAG}
      - echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin
      - docker push ${IMAGE_NAME}:${IMAGE_TAG}

Summary

As you can see from the above examples, the new inline scan makes it easier than ever to implement Anchore Engine image analysis in your Docker build pipeline! You can scan local images before pushing them into a registry, allowing you to inject scans directly into your current workflows. The inline_scan script makes it simple to ensure your Dockerfile meets best practices, perform fine-grained custom policy evaluations, and even pull an image directly from a remote registry for scanning. Anchore inline scan is a zero-friction solution for ensuring that only secure images make it through your build pipeline and get into production. Add it to your pipeline today!

Anchore Engine is an open source project, all issues and contribution details can be found on Github. We look forward to receiving feedback and contributions from our users!

Links

This post has been updated to reflect the newest version of the Anchore Inline Scanner.