How to Check Docker Images for Vulnerabilities
source link: https://mydeveloperplanet.com/2023/01/18/how-to-check-docker-images-for-vulnerabilities/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Regularily checking for vulnerabilities in your pipeline is very important. One of the steps to execute is to perform a vulnerability scan of your Docker images. In this blog, you will learn how to perform the vulnerability scan, how to fix the vulnerabilities and how to add it to your Jenkins pipeline. Enjoy!
1. Introduction
In a previous blog from a few years ago, it was described how you could scan your Docker images for vulnerabilities. A follow-up blog showed how to add the scan to a Jenkins pipeline. However, Anchore Engine, which was used in the previous blogs, is not supported anymore. An alternative solution is available with grype which is also provided by Anchore. In this blog, you will take a closer look at grype, how it works, how you can fix the issues and how you can add it to your Jenkins pipeline.
But first of all, why check for vulnerabilities? You have to stay up-to-date with the latest security fixes nowadays. Many security vulnerabilities are publicly available and therefore can be exploited quite easily. It is therefore a must have to fix security vulnerabilities as fast as possible in order to minimize your attack surface. But how to keep up with this? Your are mainly focussed at the business and do not want to have a full-time job at fixing security vulnerabilities. That is why it is important to scan your application and your Docker images automatically. Grype can help with scanning your Docker images. Grype will check Operating System vulnerabilities but also language specific packages, like Java jar files, for vulnerabilities and will report them. This way, you have a great tool which will automate the security checks for you. Do note that grype is not limited to scanning Docker images. It can also scan files and directories and can therefore be used for scanning your sources.
In this blog, you will create a vulnerable Docker image containing a Spring Boot application. You will install and use grype in order to scan the image and fix the vulnerabilities. At the end, you will learn how to add the scan to your Jenkins pipeline.
The sources used in this blog can be found at GitHub.
2. Prerequisites
Prerequisites needed for this blog are:
- Basic Linux knowledge;
- Basic Docker knowlegde;
- Basic Java and Spring Boot knowledge.
3. Vulnerable Application
Navigate to Spring Initializr and choose for a Maven build, Java 17, Spring Boot 2.7.6 and the Spring Web dependency. This will not be a very vulnerable application because Spring already ensures that you use the latest Spring Boot version. Therefore, change the Spring Boot version into 2.7.0. The Spring Boot application can be built with the following command, which will create the jar file for you:
$ mvn clean verify |
You are going to scan a Docker image, therefore a Dockerfile needs to be created. You will use a very basic Dockerfile which just contains the minimum instructions needed to create the image. If you want to create production ready Docker images, do read the posts Docker Best Practices and Spring Boot Docker Best Practices.
FROM eclipse-temurin:17.0.1_12-jre-alpine WORKDIR /opt/app ARG JAR_FILE COPY target/${JAR_FILE} app.jar ENTRYPOINT ["java", "-jar", "app.jar"] |
At the time of writing, the latest eclipse-temurin base image for Java 17 is version 17.0.5_8. Again, use an older one in order to make it vulnerable.
For building the Docker image, a fork of the dockerfile-maven-plugin
of Spotify will be used. The following snippet is therefore added to the pom
file.
< plugin > < groupId >com.xenoamess.docker</ groupId > < artifactId >dockerfile-maven-plugin</ artifactId > < version >1.4.25</ version > < configuration > < repository >mydeveloperplanet/mygrypeplanet</ repository > < tag >${project.version}</ tag > < buildArgs > < JAR_FILE >${project.build.finalName}.jar</ JAR_FILE > </ buildArgs > </ configuration > </ plugin > |
The advantage of using this plugin is that you can easily reuse the configuration. Creating the Docker image can be done by a single Maven command.
Building the Docker image can be done by invoking the following command:
$ mvn dockerfile:build |
You are now all set up to get started with grype.
4. Installation
Installation of grype can be done by executing the following script:
$ curl -sSfL https: //raw .githubusercontent.com /anchore/grype/main/install .sh | sh -s -- -b /usr/local/bin |
Verify the installation by executing the following command:
$ grype version Application: grype Version: 0.54.0 Syft Version: v0.63.0 BuildDate: 2022-12-13T15:02:51Z GitCommit: 93499eec7e3ce2704755e9f51457181b06b519c5 GitDescription: v0.54.0 Platform: linux /amd64 GoVersion: go1.18.8 Compiler: gc Supported DB Schema: 5 |
5. Scan Image
Scanning the Docker image is done by calling grype
followed by docker:
, indicating that you want to scan an image from the Docker daemon, the image and the tag:
$ grype docker:mydeveloperplanet /mygrypeplanet :0.0.1-SNAPSHOT Application: grype Version: 0.54.0 Syft Version: v0.63.0 Vulnerability DB [updated] Loaded image Parsed image Cataloged packages [50 packages] Scanned image [42 vulnerabilities] NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY busybox 1.34.1-r3 1.34.1-r5 apk CVE-2022-28391 High jackson-databind 2.13.3 java-archive CVE-2022-42003 High jackson-databind 2.13.3 java-archive CVE-2022-42004 High jackson-databind 2.13.3 2.13.4 java-archive GHSA-rgv9-q543-rqg4 High jackson-databind 2.13.3 2.13.4.1 java-archive GHSA-jjjh-jjxp-wpff High java 17.0.1+12 binary CVE-2022-21248 Low java 17.0.1+12 binary CVE-2022-21277 Medium java 17.0.1+12 binary CVE-2022-21282 Medium java 17.0.1+12 binary CVE-2022-21283 Medium java 17.0.1+12 binary CVE-2022-21291 Medium java 17.0.1+12 binary CVE-2022-21293 Medium java 17.0.1+12 binary CVE-2022-21294 Medium java 17.0.1+12 binary CVE-2022-21296 Medium java 17.0.1+12 binary CVE-2022-21299 Medium java 17.0.1+12 binary CVE-2022-21305 Medium java 17.0.1+12 binary CVE-2022-21340 Medium java 17.0.1+12 binary CVE-2022-21341 Medium java 17.0.1+12 binary CVE-2022-21360 Medium java 17.0.1+12 binary CVE-2022-21365 Medium java 17.0.1+12 binary CVE-2022-21366 Medium libcrypto1.1 1.1.1l-r7 apk CVE-2021-4160 Medium libcrypto1.1 1.1.1l-r7 1.1.1n-r0 apk CVE-2022-0778 High libcrypto1.1 1.1.1l-r7 1.1.1q-r0 apk CVE-2022-2097 Medium libretls 3.3.4-r2 3.3.4-r3 apk CVE-2022-0778 High libssl1.1 1.1.1l-r7 apk CVE-2021-4160 Medium libssl1.1 1.1.1l-r7 1.1.1n-r0 apk CVE-2022-0778 High libssl1.1 1.1.1l-r7 1.1.1q-r0 apk CVE-2022-2097 Medium snakeyaml 1.30 java-archive GHSA-mjmj-j48q-9wg2 High snakeyaml 1.30 1.31 java-archive GHSA-3mc7-4q67-w48m High snakeyaml 1.30 1.31 java-archive GHSA-98wm-3w3q-mw94 Medium snakeyaml 1.30 1.31 java-archive GHSA-c4r9-r8fh-9vj2 Medium snakeyaml 1.30 1.31 java-archive GHSA-hhhw-99gj-p3c3 Medium snakeyaml 1.30 1.32 java-archive GHSA-9w3m-gqgf-c4p9 Medium snakeyaml 1.30 1.32 java-archive GHSA-w37g-rhq8-7m4j Medium spring-core 5.3.20 java-archive CVE-2016-1000027 Critical ssl_client 1.34.1-r3 1.34.1-r5 apk CVE-2022-28391 High zlib 1.2.11-r3 1.2.12-r0 apk CVE-2018-25032 High zlib 1.2.11-r3 1.2.12-r2 apk CVE-2022-37434 Critical |
What does this output tell you?
- NAME: The name of the vulnerable package;
- INSTALLED: Which version is installed;
- FIXED-IN: In which version the vulnerability is fixed;
- TYPE: The type of the dependency, e.g. binary for the JDK, etc;
- VULNERABILITY; The identifier of the vulnerability. With this identifier, you are able to get more information about the vulnerability in the CVE database;
- SEVERITY: Speaks for itself and can be one of negligible, low, medium, high, critical.
As you take a closer look at the output, you will notice that not every vulnerability has a confirmed fix. So what to do in that case? Grype provides an option in order to show only the vulnerabilities with a confirmed fix. Adding the --only-fixed
flag will do the trick.
$ grype docker:mydeveloperplanet /mygrypeplanet :0.0.1-SNAPSHOT --only-fixed Vulnerability DB [no update available] Loaded image Parsed image Cataloged packages [50 packages] Scanned image [42 vulnerabilities] NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY busybox 1.34.1-r3 1.34.1-r5 apk CVE-2022-28391 High jackson-databind 2.13.3 2.13.4 java-archive GHSA-rgv9-q543-rqg4 High jackson-databind 2.13.3 2.13.4.1 java-archive GHSA-jjjh-jjxp-wpff High libcrypto1.1 1.1.1l-r7 1.1.1n-r0 apk CVE-2022-0778 High libcrypto1.1 1.1.1l-r7 1.1.1q-r0 apk CVE-2022-2097 Medium libretls 3.3.4-r2 3.3.4-r3 apk CVE-2022-0778 High libssl1.1 1.1.1l-r7 1.1.1n-r0 apk CVE-2022-0778 High libssl1.1 1.1.1l-r7 1.1.1q-r0 apk CVE-2022-2097 Medium snakeyaml 1.30 1.31 java-archive GHSA-3mc7-4q67-w48m High snakeyaml 1.30 1.31 java-archive GHSA-98wm-3w3q-mw94 Medium snakeyaml 1.30 1.31 java-archive GHSA-c4r9-r8fh-9vj2 Medium snakeyaml 1.30 1.31 java-archive GHSA-hhhw-99gj-p3c3 Medium snakeyaml 1.30 1.32 java-archive GHSA-9w3m-gqgf-c4p9 Medium snakeyaml 1.30 1.32 java-archive GHSA-w37g-rhq8-7m4j Medium ssl_client 1.34.1-r3 1.34.1-r5 apk CVE-2022-28391 High zlib 1.2.11-r3 1.2.12-r0 apk CVE-2018-25032 High zlib 1.2.11-r3 1.2.12-r2 apk CVE-2022-37434 Critical |
Note that the vulnerabilities for the Java JDK have disappeared, although there exists a more recent update for the Java 17 JDK. However, this might not be a big issue, because the other (non java-archive) vulnerabilities show you that the base image is outdated.
6. Fix Vulnerabilities
Fixing the vulnerabilities is quite easy in this case. First of all, you need to update the Docker base image. Change the first line in the Docker image:
FROM eclipse-temurin:17.0.1_12-jre-alpine |
into:
FROM eclipse-temurin:17.0.5_8-jre-alpine |
Build the image and run the scan again:
$ mvn dockerfile:build ... $ grype docker:mydeveloperplanet /mygrypeplanet :0.0.1-SNAPSHOT --only-fixed Vulnerability DB [no update available] Loaded image Parsed image Cataloged packages [62 packages] Scanned image [14 vulnerabilities] NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY jackson-databind 2.13.3 2.13.4 java-archive GHSA-rgv9-q543-rqg4 High jackson-databind 2.13.3 2.13.4.1 java-archive GHSA-jjjh-jjxp-wpff High snakeyaml 1.30 1.31 java-archive GHSA-3mc7-4q67-w48m High snakeyaml 1.30 1.31 java-archive GHSA-98wm-3w3q-mw94 Medium snakeyaml 1.30 1.31 java-archive GHSA-c4r9-r8fh-9vj2 Medium snakeyaml 1.30 1.31 java-archive GHSA-hhhw-99gj-p3c3 Medium snakeyaml 1.30 1.32 java-archive GHSA-9w3m-gqgf-c4p9 Medium snakeyaml 1.30 1.32 java-archive GHSA-w37g-rhq8-7m4j Medium |
As you can see in the output, only the java-archive vulnerabilities are still present. The other vulnerabilities have been solved.
Next, fix the Spring Boot dependency vulnerability. Change the version of Spring Boot from 2.7.0 into 2.7.6 in the pom.
< parent > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-parent</ artifactId > < version >2.7.6</ version > < relativePath /> <!-- lookup parent from repository --> </ parent > |
Build the jar file, build the Docker image and run the scan again:
$ mvn clean verify ... $ mvn dockerfile:build ... $ grype docker:mydeveloperplanet /mygrypeplanet :0.0.1-SNAPSHOT --only-fixed Vulnerability DB [no update available] Loaded image Parsed image Cataloged packages [62 packages] Scanned image [10 vulnerabilities] NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY snakeyaml 1.30 1.31 java-archive GHSA-3mc7-4q67-w48m High snakeyaml 1.30 1.31 java-archive GHSA-98wm-3w3q-mw94 Medium snakeyaml 1.30 1.31 java-archive GHSA-c4r9-r8fh-9vj2 Medium snakeyaml 1.30 1.31 java-archive GHSA-hhhw-99gj-p3c3 Medium snakeyaml 1.30 1.32 java-archive GHSA-9w3m-gqgf-c4p9 Medium snakeyaml 1.30 1.32 java-archive GHSA-w37g-rhq8-7m4j Medium |
So, you got rid of the jackson-databind
vulnerability, but not of the snakeyaml
vulnerability. So, in which dependency is snakeyaml 1.30 being used? You can find out by means of the dependency:tree
Maven command. For brevity purposes, only a part of the output is shown here:
$ mvnd dependency:tree ... com.mydeveloperplanet:mygrypeplanet:jar:0.0.1-SNAPSHOT [INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.7.6:compile [INFO] | +- org.springframework.boot:spring-boot-starter:jar:2.7.6:compile [INFO] | | +- org.springframework.boot:spring-boot:jar:2.7.6:compile [INFO] | | +- org.springframework.boot:spring-boot-autoconfigure:jar:2.7.6:compile [INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:2.7.6:compile [INFO] | | | +- ch.qos.logback:logback-classic:jar:1.2.11:compile [INFO] | | | | \- ch.qos.logback:logback-core:jar:1.2.11:compile [INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.17.2:compile [INFO] | | | | \- org.apache.logging.log4j:log4j-api:jar:2.17.2:compile [INFO] | | | \- org.slf4j:jul-to-slf4j:jar:1.7.36:compile [INFO] | | +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile [INFO] | | \- org.yaml:snakeyaml:jar:1.30:compile ... |
The output shows us that the dependency is part of the spring-boot-starter-web
dependency. So, how to solve this? Strictly speaking, Spring has to solve it. But if you do not want to wait for a solution, you can solve it by yourself.
Solution 1: you do not need the dependency. This is the easiest fix and low risk. Just exclude the dependency from the spring-boot-starter-web
dependency in the pom.
< dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-web</ artifactId > < exclusions > < exclusion > < groupId >org.yaml</ groupId > < artifactId >snakeyaml</ artifactId > </ exclusion > </ exclusions > </ dependency > |
Build the jar file, build the Docker image and run the scan again:
$ mvn clean verify ... $ mvn dockerfile:build ... $ grype docker:mydeveloperplanet /mygrypeplanet :0.0.1-SNAPSHOT --only-fixed Vulnerability DB [no update available] Loaded image Parsed image Cataloged packages [61 packages] Scanned image [3 vulnerabilities] No vulnerabilities found |
No vulnerabilities are found anymore.
Solution 2: you do need the dependency. You can replace this transitive dependency by means of dependencyManagement
in the pom. This is a bit more tricky, because the updated transitive dependency is not tested with the spring-boot-starter-web
dependency. It is a trade-off whether you want to do this or not. Upgrade the version of the snakeyaml dependency, see here for the full list of available versions (thanks to zimeron for this addition). Add the updated version to the properties
section.
< properties > < java.version >17</ java.version > < snakeyaml.version >1.32</ snakeyaml.version > </ properties > |
When it is not possible to change the version as mentioned above, the following alternative can be used:
< dependencyManagement > < dependencies > < dependency > < groupId >org.yaml</ groupId > < artifactId >snakeyaml</ artifactId > < version >1.32</ version > </ dependency > </ dependencies > </ dependencyManagement > |
Build the jar file, build the Docker image and run the scan again:
$ mvn clean verify ... $ mvn dockerfile:build ... $ grype docker:mydeveloperplanet /mygrypeplanet :0.0.1-SNAPSHOT --only-fixed Vulnerability DB [no update available] Loaded image Parsed image Cataloged packages [62 packages] Scanned image [3 vulnerabilities] No vulnerabilities found |
Again, no vulnerabilities are present anymore.
Solution 3: this is the solution when you do not want to do anything or whether it is a false positive notification. Create a .grype.yaml
file where you exclude the vulnerability with High severity and execute the scan with the --config
flag followed by the .grype.yaml
file containing the exclusions.
The .grype.yaml
file looks as follows:
ignore: - vulnerability : GHSA-3mc7-4q67-w48m |
Run the scan again:
$ grype docker:mydeveloperplanet/mygrypeplanet:0.0.1-SNAPSHOT --only-fixed Vulnerability DB [no update available] Loaded image Parsed image Cataloged packages [62 packages] Scanned image [10 vulnerabilities] NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY snakeyaml 1.30 1.31 java-archive GHSA-98wm-3w3q-mw94 Medium snakeyaml 1.30 1.31 java-archive GHSA-c4r9-r8fh-9vj2 Medium snakeyaml 1.30 1.31 java-archive GHSA-hhhw-99gj-p3c3 Medium snakeyaml 1.30 1.32 java-archive GHSA-9w3m-gqgf-c4p9 Medium snakeyaml 1.30 1.32 java-archive GHSA-w37g-rhq8-7m4j Medium |
The High vulnerability is not shown anymore.
7. Continuous Integration
Now you know how to manually scan your Docker images. However, you probably want to scan the images as part of your continuous integration pipeline. In this section, a solution is provided when using Jenkins as CI platform.
First question to answer is how you will be notified when vulnerabilities are found? Up till now, you only noticed the vulnerabilities by looking at the standard output. This is not a solution for a CI pipeline. You want to get notified and this can be done by failing the build. Grype has the --fail-on <severity>
flag for this purpose. You probably do not want to fail the pipeline when a vulnerability with severity negligible
has been found.
Let’s see what happens when you execute this manually. First of all, introduce the vulnerabilities again in the Spring Boot application and in the Docker image.
Build the jar file, build the Docker image and run the scan with flag --fail-on
:
$ mvn clean verify ... $ mvn dockerfile:build ... $ grype docker:mydeveloperplanet /mygrypeplanet :0.0.1-SNAPSHOT --only-fixed --fail-on high ... 1 error occurred: * discovered vulnerabilities at or above the severity threshold |
Not all output has been shown here, but only the important part. And, as you can see, at the end of the output, a message is shown that the scan has generated an error. This will cause your Jenkins pipeline to fail and as a consequence, the developers are notified that something went wrong.
In order to add this to your Jenkins pipeline, several options exist. Here it is chosen to create the Docker image and to execute the grype Docker scan from within Maven. There is no seperate Maven plugin for grype, but you can use the exec-maven-plugin for this purpose. Add the following to the build-plugins section of the pom.
< build > < plugins > < plugin > < groupId >org.codehaus.mojo</ groupId > < artifactId >exec-maven-plugin</ artifactId > < version >3.1.0</ version > < configuration > < executable >grype</ executable > < arguments > < argument >docker:mydeveloperplanet/mygrypeplanet:${project.version}</ argument > < argument >--scope</ argument > < argument >all-layers</ argument > < argument >--fail-on</ argument > < argument >high</ argument > < argument >--only-fixed</ argument > < argument >-q</ argument > </ arguments > </ configuration > </ plugin > </ plugins > </ build > |
Two extra flags are added here:
--scope all-layers
: This will scan all layers involved in the Docker image;-q
: This will use quiet logging and will show only the vulnerabilities and possible failure.
You can invoke this with the following command:
$ mvnd exec : exec |
You can add this to your Jenkinsfile inside the withMaven wrapper:
withMaven() { sh 'mvn dockerfile:build dockerfile:push exec:exec' } |
8. Conclusion
In this blog, you learned how to scan your Docker images by means of grype. Grype has some interesting, user-friendly features which allow you to efficiently add them to your Jenkins pipeline. Also, installing grype is quite easy. Grype is definitely a great improvement over Anchor Engine.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK