How to scan a container for vulnerabilities and publish results as a part of Azure DevOps CI/CD pipeline

Posted by Nikos Tsirmirakis on

In my previous post, I have described how to run static code analysis on infrastructure as code (IaC) terraform scripts with the use of tfsec scanner. In this post, we will look into container security. We will build a sample container, scan it for security vulnerabilities with Trivy from AquaSec, publish scan results to Azure DevOps and publish the container to Docker Hub.

Pipeline

Build pipeline is defined in a YAML script (azure-pipelines-dm12.yml) and contains the following steps. All scripts are available in the DBAinTheCloud GitHub repository.

  1. Build container
  2. Scan container with Trivy
  3. Publish scan results to Azure DevOps
  4. Scan container with Trivy and fail pipeline if there are any critical vulnerabilities
  5. Publish container to Docker Hub

First, we will build a sample container based on the Ubuntu image. We will use the following dockerfile (dockerfile) to install Terraform and Azure az.

FROM ubuntu:latest

RUN apt-get update && DEBIAN_FRONTEND="noninteractive" TZ="Europe/London" apt-get install -y gnupg software-properties-common curl ca-certificates apt-transport-https lsb-release

RUN curl -fsSL https://apt.releases.hashicorp.com/gpg | apt-key add -
RUN apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"

RUN curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
RUN AZ_REPO=$(lsb_release -cs) && echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $AZ_REPO main" | tee /etc/apt/sources.list.d/azure-cli.list

RUN apt-get update && apt-get install terraform azure-cli

We will use a docker step to build it.

- task: Docker@2
  displayName: Build an image
  inputs:
    command: "build"
    Dockerfile: "$(Build.SourcesDirectory)/12-container-trivy-sacnning/dockerfile"
    tags: "$(tag)"
    arguments: "-t $(imageName):$(tag)"

After the image is built we will scan it locally with Trivy. In this example, we will run Trivy from the docker image provided by AquaSec to generate a report in JUnit format. We will use a template file with follwing switches, --format template --template "@contrib/junit.tpl" to generate output in correct format. Please note that we are explicitly ignoring any errors so the pipeline is not failing even if any errors are detected and publish the results in the following step.

- task: CmdLine@2
  displayName: Scan for reports
  inputs:
    script: |
      docker run --rm \
      -v /var/run/docker.sock:/var/run/docker.sock \
      -v $HOME/Library/Caches:/root/.cache/ \
      -v "$(pwd):/src" \
      aquasec/trivy \
      --exit-code 0 \
      --severity LOW,MEDIUM,HIGH \
      --format template --template "@contrib/junit.tpl" \
      -o src/junit-report-low-med-high.xml \
      --ignore-unfixed \
      '$(imageName):$(tag)'
        
      docker run --rm \
      -v /var/run/docker.sock:/var/run/docker.sock \
      -v $HOME/Library/Caches:/root/.cache/ \
      -v "$(pwd):/src" \
      aquasec/trivy \
      --exit-code 0 \
      --severity CRITICAL \
      --format template --template "@contrib/junit.tpl" \
      -o src/junit-report-crit.xml \
      --ignore-unfixed \
      '$(imageName):$(tag)'

After scan results are ready we will publish it to Azure DevOps.

- task: PublishTestResults@2
  displayName: Publish Test Results
  inputs:
    testResultsFormat: "JUnit" # Options: JUnit, NUnit, VSTest, xUnit, cTest
    testResultsFiles: "**/junit-report-*.xml"

Then we will scan the image again, this time to fail the pipeline if there are any critical vulnerabilities (you can amend this step to fail on high vulnerabilities as well if required).

- task: CmdLine@2
  displayName: Scan for errors
  inputs:
    script: |
      docker run --rm \
      -v /var/run/docker.sock:/var/run/docker.sock \
      -v $HOME/Library/Caches:/root/.cache/ \
      -v "$(pwd):/src" \
      aquasec/trivy \
      --exit-code 1 \
      --severity CRITICAL \
      --ignore-unfixed \
      '$(imageName):$(tag)'

Finally, if everything is fine and there are no critical vulnerabilities we will publish the image to Docker Hub.

- task: Docker@2
  inputs:
    containerRegistry: "DockerHub-WinOpsDBA-nikosts"
    command: "login"

- task: Docker@2
  inputs:
    containerRegistry: 'DockerHub-WinOpsDBA-nikosts'
    repository: '$(imageName)'
    command: 'push'
    tags: '$(tag)'

After the pipeline run, Trivy test reviews can be reviewed in the Azure DevOps test section.

Please note that to run a docker step to publish an image to Docker Hub you will have to configure a service connection for it. Please have a look at the connection details we have used in the sample pipeline below.

Congratulations!

We have successfully built the container, scanned it, published results to Azure DevOps and published image to Docker Hub.

Coming next …

In the next post, we will scan the web application for vulnerabilities with the use of ZAP scanner and publish results to Azure DevOps.