Tutorial: Create and scan an image for vulnerabilities and configuration flaws

Use the open source Trivy tool to scan a container configuration file and a container image

By

Matt Colman

Now let's take a look at container image vulnerability scanning in practice. In this tutorial, you'll be using Trivy, an open source container security tool that demonstrates the ability to scan both a container configuration file (Dockerfile) and a container image. In a project scenario, Trivy could be provided to developers on their workstations to allow scanning and feedback during coding, as well as being integrated into an automated CI/CD pipeline for formal build validation. Other open source and commercial container image scanning tools are also available.

This tutorial shows you how to create an image and scan it to highlight any vulnerabilities and configuration flaws. Using this information, you can reduce and remove vulnerabilities and flaws where possible.

Prerequisites

To complete this tutorial, you need to meet the following requirements:

  • Must be running Linux or Mac
  • Need Podman, Docker, or another container builder installed
  • Need "sed" and "awk" installed

Caveat: Podman/Docker on Mac utilizes a virtual machine running Linux. This is taken care of by the Podman machine init command, or through the use of Docker Desktop, as described in the installation instructions which can be accessed via the links above.

Estimated time

It should take you approximately 20 minutes to complete this tutorial.

Steps

  1. Install Trivy as appropriate per these instructions: https://aquasecurity.github.io/trivy/v0.25.0/getting-started/installation/

  2. Create a directory for the lab:

    • Change to your home:
       cd ~
      
    • Create a directory within your home directory, to work in:
      mkdir vuln-lab
      
    • Change to this directory:
      cd vuln-lab
      
  3. Use the following command to create a Dockerfile:

     cat << 'EOF' > Dockerfile
     FROM debian:10.0
     RUN apt-get -y install bash
     ADD ./myfile.tar /tmp
     EXPOSE 22
     EOF
    
  4. Create a directory to use for an archive in a later step:

     mkdir archive
    
  5. Use the following commands to create a demonstration tar file:

     echo this is some text > ./archive/file.txt
     tar cvf myfile.tar archive
    
  6. Use Podman, Docker, or another container builder to create the container image:

    • Podman:

       podman build -t mytestimage:0.1 ./ -f Dockerfile
      
    • Docker:

       docker build -t mytestimage:0.1 ./ -f Dockerfile
      
  7. Ensure that the Docker or Podman socket is available:

    • For Docker:

       service docker start
      
    • For Podman, run the commands produced by this code:

       if [ "$(podman info | grep remoteSocket -A2 | sed -n '2p' | grep -c "exists")" -eq "0" ]; then printf "\nPlease run the following commands:\n\nmkdir -p $(podman info | grep remoteSocket -A1 | grep "path\:" | awk -F": " '{print $2}' | sed -e 's;\/podman.sock;;g')\npodman system service -t 3600 unix://$(podman info | grep remoteSocket -A1 | grep "path\:" | awk -F": " '{print $2}') & \nexport XDG_RUNTIME_DIR=$(podman info | grep remoteSocket -A1 | grep "path\:" | awk -F": " '{print $2}' | sed -e 's;podman\/podman.sock;;g')\n"; else printf "\nPlease run the following commands:\n\nexport XDG_RUNTIME_DIR=$(podman info | grep remoteSocket -A2 | sed -n '3p' | grep "path\:" | awk -F": " '{print $2}' | sed -e 's;podman\/podman.sock;;g')\n"; fi
      
  8. Scan with trivy and have it create an output file:

     trivy image -f json -o mytestimage_results_"`date +"%H%M%S"`".json localhost/mytestimage:0.1
    
  9. Upload the results to: https://dbsystel.github.io/trivy-vulnerability-explorer/#/.

  10. Note there are a large number of vulnerabilities (~177) in the image, with ~47 fixable.

  11. Now let's change the Dockerfile to use the slim version of Debian 10.0 as the base:

     cat << 'EOF' > Dockerfile
     FROM debian:10.0-slim
     RUN apt-get -y install bash
     ADD ./myfile.tar /tmp
     EXPOSE 22
     EOF
    
  12. Build the image:

    • Podman:

      podman build -t mytestimage:0.2 ./ -f Dockerfile
      
    • Docker:

      docker build -t mytestimage:0.2 ./ -f Dockerfile
      
  13. Scan the image:

     trivy image -f json -o mytestimage_results_"`date +"%H%M%S"`".json localhost/mytestimage:0.2
    
  14. Upload the results to: https://dbsystel.github.io/trivy-vulnerability-explorer/#/.

  15. Note the large number of vulnerabilities (~174) in the image, with ~47 fixable.

  16. Let's change the Dockerfile to use the slim version of newer image; debian 11.2 as the base:

     cat << 'EOF' > Dockerfile
     FROM debian:11.2-slim
     RUN apt-get -y install bash
     ADD ./myfile.tar /tmp
     EXPOSE 22
     EOF
    
  17. Build the image:

    • Podman:

      podman build -t mytestimage:0.3 ./ -f Dockerfile
      
    • Docker:

      docker build -t mytestimage:0.3 ./ -f Dockerfile
      
  18. Scan the image:

     trivy image -f json -o mytestimage_results_"`date +"%H%M%S"`".json localhost/mytestimage:0.3
    
  19. Upload the results to: https://dbsystel.github.io/trivy-vulnerability-explorer/#/.

  20. Note the large number of vulnerabilities (~79) in the image, with ~10 fixable, consisting of (approximately) 6 critical, 4 high, 5 medium, and 63 low.

  21. Does your image need all these packages? If not, can you convert to a minimal (smaller) image? Let's change to Alpine:

     cat << 'EOF' > Dockerfile
     FROM alpine:3.14.4
     RUN apk add bash --no-cache
     ADD ./myfile.tar /tmp
     EXPOSE 22
     EOF
    
  22. Build the image:

    • Podman:

      podman build -t mytestimage:0.4 ./ -f Dockerfile
      
    • Docker:

      docker build -t mytestimage:0.4 ./ -f Dockerfile
      
  23. Scan the image:

     trivy image -f json -o mytestimage_results_"`date +"%H%M%S"`".json localhost/mytestimage:0.4
    
  24. Upload the results to: https://dbsystel.github.io/trivy-vulnerability-explorer/#/.

  25. Note the number of vulnerabilities now: ~2 in the image, with ~2 fixable, consisting of (approximately) 2 high.

  26. Now let's try fixing the vulnerable package. (Note: The number of vulnerabilities and the affected packages might change after this lab is published, so please add any additional packages to be fixed to the Dockerfile config below, specifying the fixed version):

     cat << 'EOF' > Dockerfile
     FROM alpine:3.14.4
     RUN apk add bash "libretls=3.3.3p1-r3" "zlib=1.2.12-r0" --no-cache
     ADD ./myfile.tar /tmp
     EXPOSE 22
     EOF
    
  27. Build the image:

    • Podman:

      podman build -t mytestimage:0.5 ./ -f Dockerfile
      
    • Docker:

      docker build -t mytestimage:0.5 ./ -f Dockerfile
      
  28. Scan the image:

     trivy image -f json -o mytestimage_results_"`date +"%H%M%S"`".json localhost/mytestimage:0.5
    
  29. Upload the results to: https://dbsystel.github.io/trivy-vulnerability-explorer/#/.

  30. You should now see that there are no vulnerabilities (assuming no unfixable vulnerabilities have been identified since this lab was published). Also note that newer images are being created all the time, and it is likely that when alpine:3.14.5 is released it will resolve these vulnerabilities so you won't need to install the packages yourself.

  31. Let's now check if there are any configuration concerns, by scanning the Dockerfile:

     trivy config ./Dockerfile
    
  32. As it turns out, there are two configuration flaws to fix:

    • You should run the container with a non-root user.
    • You shouldn't expose port 22 unless needed (let's assume it's not).
  33. Amend the Dockerfile (note that “ARG” arguments are included to parameterize some values that are used in multiple places):

     cat << 'EOF' > Dockerfile
     FROM alpine:3.14.4
     ARG USER=mytest
     ARG UID=1000
     ARG GID=1000
     RUN apk add bash "libretls=3.3.3p1-r3" "zlib=1.2.12-r0" --no-cache && \
         addgroup -S -g ${GID} ${USER} && \
         adduser -S -D -u ${UID} -G ${USER} ${USER} 
     ADD ./myfile.tar /tmp
     USER ${UID}
     EOF
    
  34. Let's now check if there are any configuration concerns by scanning the Dockerfile:

     trivy config ./Dockerfile
    
  35. You should see that the security failures have been fixed:

     Dockerfile (dockerfile)
     =======================
     Tests: 23 (SUCCESSES: 23, FAILURES: 0, EXCEPTIONS: 0)
     Failures: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
    

Note: To adhere to the best practice of “shift-left” (AKA “fail fast”), you should further optimize this process by scanning candidate base images first, before utilizing them in a Dockerfile and creating your own image to scan.

It's also a good idea to look at the images you created to see how selecting a minimal base image can impact image size:

  • Podman:

     podman images "mytestimage"
    
  • Docker:

     docker images "mytestimage"
    
  • Result:

     REPOSITORY             TAG         IMAGE ID      CREATED         SIZE
     localhost/mytestimage  0.5         df9b52a58052  7 minutes ago   8.29 MB
     localhost/mytestimage  0.4         1aa1bf71b769  10 minutes ago  8.11 MB
     localhost/mytestimage  0.3         eb1b2a234b87  11 minutes ago  83.9 MB
     localhost/mytestimage  0.2         7bf756f9e33f  13 minutes ago  72.5 MB
     localhost/mytestimage  0.1         940908ddbf06  16 minutes ago  119 MB
    

Notice that you have reduced it from 119MB to 8.29MB, which means less to transfer around a network!

Tidy up

When you have completed all the steps above, you can tidy up as follows:

  1. Remove the lab directory and contents that you created:

     cd ../ && rm -rf vuln-lab
    
  2. Remove the images you created from Podman/Docker:

    • Podman:

       podman image rm $(podman images "mytestimage" -q)
      
    • Docker:

       docker image rm $(docker images "mytestimage" -q)
      

Conclusion

The steps in this tutorial have showed you how to ensure that you have followed an educated process for selecting the base image that you use to create a container image. This involved ensuring the use of up-to-date images to reduce vulnerability count, as well as using slim/minimal images to reduce and remove unecessary components in the base image which further improves security posture and reduces vulnerabilities. In addition, you checked the configuration for any security weaknesses to ensure that your resultant container image is aligned with best security practices. By putting these checks in place and taking remedial action, you can reduce the risks imposed on the system to which these are deployed.