Skip to content

Commit 3657233

Browse files
xieshenzhagunn303
andauthored
feat: support dockerfile base image vulnerability scan (#150)
* feat: support dockerfile base image vulnerability scan * feat: discontinue support for synk * docs: support dockerfile base image vulnerability scan * 4/18/24 - ritz303 : Added content for Docker scanning feature * 4/19/24 - ritz303 : Added Docker scanning settings * docs: update docs for dockerfile scan * docs: update change notes * chore: bump development version to 1.0.0-SNAPSHOT --------- Co-authored-by: Aron Gunn <[email protected]>
1 parent 0f4283c commit 3657233

34 files changed

+1655
-220
lines changed

.github/workflows/IJ.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
runs-on: ubuntu-latest
1515
strategy:
1616
matrix:
17-
IJ: [IC-2021.1]
17+
IJ: [IC-2022.1]
1818

1919
steps:
2020
- uses: actions/checkout@v2

README.md

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ vulnerability report.
2020

2121
**IMPORTANT:**
2222
<br >Currently, Dependency Analytics only supports projects that use Maven (`mvn`), Node (`npm`), Golang (`go mod`) and
23-
Python (`pip`) ecosystems.
23+
Python (`pip`) ecosystems, and base images in `Dockerfile`.
2424
In future releases, Red Hat plans to support other programming languages.
2525

2626
##### Table of Contents
@@ -45,6 +45,10 @@ In future releases, Red Hat plans to support other programming languages.
4545
- For Golang projects, analyzing a `go.mod` file, you must have the `go` binary in your IDE's `PATH` environment.
4646
- For Python projects, analyzing a `requirements.txt` file, you must have the `python3` and `pip3` binaries in your
4747
IDE's `PATH` environment.
48+
- For base images, analyzing a `Dockerfile`, you must have
49+
the [`syft`](https://github.com/anchore/syft?tab=readme-ov-file#installation)
50+
and [`skopeo`](https://github.com/containers/skopeo/blob/main/install.md) binaries in your IDE's `PATH`
51+
environment.
4852

4953
**Procedure**
5054

@@ -77,36 +81,57 @@ according to your preferences.
7781
- **Maven** :
7882
<br >Set the full path of the Maven executable, which allows Exhort to locate and execute the `mvn` command to resolve
7983
dependencies for Maven projects.
80-
Path of the `JAVA_HOME` directory is required by the `mvn` executable.
81-
If the paths are not provided, your IDE's `PATH` and `JAVA_HONE` environments will be used to locate the executables.
84+
<br >Path of the `JAVA_HOME` directory is required by the `mvn` executable.
85+
<br >If the paths are not provided, your IDE's `PATH` and `JAVA_HONE` environments will be used to locate the
86+
executables.
8287

8388
- **Node** :
8489
<br >Set the full path of the Node executable, which allows Exhort to locate and execute the `npm` command to resolve
8590
dependencies for Node projects.
86-
Path of the directory containing the `node` executable is required by the `npm` executable.
87-
If the paths are not provided, your IDE's `PATH` environment will be used to locate the executables.
91+
<br >Path of the directory containing the `node` executable is required by the `npm` executable.
92+
<br >If the paths are not provided, your IDE's `PATH` environment will be used to locate the executables.
8893

8994
- **Golang** :
9095
<br >Set the full path of the Go executable, which allows Exhort to locate and execute the `go` command to resolve
9196
dependencies for Go projects.
92-
If the path is not provided, your IDE's `PATH` environment will be used to locate the executable.
93-
When option `Strictly match package version` is selected, the resolved dependency versions will be compared to the
94-
versions specified in the manifest file, and users will be alerted if any mismatch is detected.
97+
<br >If the path is not provided, your IDE's `PATH` environment will be used to locate the executable.
98+
<br >When option `Strictly match package version` is selected, the resolved dependency versions will be compared to
99+
the versions specified in the manifest file, and users will be alerted if any mismatch is detected.
95100

96101
- **Python** :
97102
<br >Set the full paths of the Python and the package installer for Python executables, which allows Exhort to locate
98103
and execute the `pip3` commands to resolve dependencies for Python projects.
99-
Python 2 executables `python` and `pip` can be used instead, if the `Use python 2.x` option is selected.
100-
If the paths are not provided, your IDE's `PATH` environment will be used to locate the executables.
101-
When option `Strictly match package version` is selected, the resolved dependency versions will be compared to the
102-
versions specified in the manifest file, and users will be alerted if any mismatch is detected.
103-
Python virtual environment can be applied, when selecting the `Use python virtual environment` option.
104-
If selecting option `Allow alternate package version` while using virtual environment, the dependency versions
104+
<br >Python 2 executables `python` and `pip` can be used instead, if the `Use python 2.x` option is selected.
105+
<br >If the paths are not provided, your IDE's `PATH` environment will be used to locate the executables.
106+
<br >When option `Strictly match package version` is selected, the resolved dependency versions will be compared to
107+
the versions specified in the manifest file, and users will be alerted if any mismatch is detected.
108+
<br >Python virtual environment can be applied, when selecting the `Use python virtual environment` option.
109+
<br >If selecting option `Allow alternate package version` while using virtual environment, the dependency versions
105110
specified in the manifest file will be ignored, and dependency versions will be resolved dynamically instead (this
106111
feature cannot be enabled when `Strictly match package version` is selected).
107112

113+
- **Image** :
114+
<br >Set the full path of the Syft executable, which allows Exhort to locate and execute the `syft` command to
115+
generate Software Bill of Materials for the base images.
116+
<br >Optionally, set the full path of the Docker or Podman executable. Syft will attempt to find the images in the
117+
Docker or Podman daemon with the executable. Otherwise, Syft will try direct remote registry access.
118+
<br >Set the full path of the Skopeo executable, which allows Exhort to locate and execute the `skopeo` command to
119+
determine the image digests.
120+
<br >If the paths are not provided, your IDE's `PATH` environment will be used to locate the executables.
121+
<br >If a Syft configuration file is used and not at the
122+
default [paths](https://github.com/anchore/syft/blob/469b4c13bbc52c43bc5216924b6ffd9d6d47bbd6/README.md#configuration),
123+
set the full path to the configuration file in configuration.
124+
<br >If
125+
an [authentication file](https://github.com/containers/skopeo/blob/3eacbe5ae2fe859f872a02bf28c16371fb1de7b8/docs/skopeo-inspect.1.md#options)
126+
is applied for `skopeo inspect`, set the full path to the file in configuration.
127+
<br >If platform is not specified in the `Dockerfile` for multi-platform images and a default platform should be
128+
applied, set the default platform in the configuration. Otherwise, set the full path of the Docker or Podman
129+
executable, then Exhort will use the executable to determine the image platform based on the OS and architecture of
130+
the container runtime.
131+
108132
- **Inline Vulnerability Severity Alerts** :
109-
<br >You can set the vulnerability severity alert level to `Error` or `Warning` for inline notifications of detected vulnerabilities.
133+
<br >You can set the vulnerability severity alert level to `Error` or `Warning` for inline notifications of detected
134+
vulnerabilities.
110135

111136
## Features
112137

@@ -121,6 +146,28 @@ according to your preferences.
121146

122147
![ Animated screenshot showing the inline reporting feature of Dependency Analytics ](src/main/resources/images/component-analysis.gif)
123148

149+
- **Docker scanning**
150+
<br >Upon opening a Dockerfile, a vulnerability scan starts analyzing the images within the Dockerfile.
151+
After the analysis finishes, you can view any recommendations and remediation by clicking the _More actions..._ menu
152+
from the highlighted image name.
153+
Any recommendations for an alternative image does not replace the current image.
154+
By clicking _Switch to..._, you go to Red Hat's Ecosystem Catalog for the recommended image.
155+
156+
<br >You must have the [`syft`](https://github.com/anchore/syft#installation)
157+
and [`skopeo`](https://www.redhat.com/en/topics/containers/what-is-skopeo) binaries installed on your workstation to
158+
use the Docker scanning feature.
159+
You can specify a specific path to these binaries, and others by settings the following parameters:
160+
161+
* `syft.executable.path` : Specify the absolute path of `syft` executable.
162+
* `syft.config.path` : Specify the absolute path to the Syft configuration file.
163+
* `skopeo.executable.path` : Specify the absolute path of `skopeo` executable.
164+
* `skopeo.config.path` : Specify the absolute path to the authentication file used by the `skopeo inspect` command.
165+
* `docker.executable.path` : Specify the absolute path of `docker` executable.
166+
* `podman.executable.path` : Specify the absolute path of `podman` executable.
167+
* `image.platform` : Specify the platform used for multi-arch images.
168+
169+
![ Animated screenshot showing the inline reporting feature of Image Analysis ](src/main/resources/images/image-analysis.gif)
170+
124171
- **Excluding dependencies with `exhortignore`**
125172
<br >You can exclude a package from analysis by marking the package for exclusion.
126173
If you want to ignore vulnerabilities for a dependency in a `pom.xml` file, you must add `exhortignore` as a comment

build.gradle

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ intellij {
4040
version = ideaVersion //for a full list of IntelliJ IDEA releases please see https://www.jetbrains.com/intellij-repository/releases
4141
pluginName = 'org.jboss.tools.intellij.analytics'
4242
plugins = ['com.redhat.devtools.intellij.telemetry:1.1.0.52',
43-
"org.jetbrains.plugins.go:211.6693.111"]
43+
"org.jetbrains.plugins.go:221.5080.9",
44+
"Docker:221.5080.9"]
4445
updateSinceUntilBuild = false
4546
}
4647

@@ -61,7 +62,7 @@ dependencies {
6162

6263
implementation 'org.kohsuke:github-api:1.314'
6364
implementation 'org.apache.commons:commons-compress:1.21'
64-
implementation 'com.redhat.exhort:exhort-java-api:0.0.6-SNAPSHOT'
65+
implementation 'com.redhat.exhort:exhort-java-api:0.0.7-SNAPSHOT'
6566
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
6667
// https://mvnrepository.com/artifact/com.github.package-url/packageurl-java
6768
implementation group: 'com.github.package-url', name: 'packageurl-java', version: '1.4.1'

gradle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ideaVersion = 2021.1
2-
projectVersion=0.9.2-SNAPSHOT
1+
ideaVersion = 2022.1
2+
projectVersion=1.0.0-SNAPSHOT
33
jetBrainsToken=invalid
44
jetBrainsChannel=stable

src/main/java/org/jboss/tools/intellij/componentanalysis/SAIntentionAction.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.intellij.openapi.vfs.VirtualFile;
2222
import com.intellij.psi.PsiFile;
2323
import com.intellij.util.IncorrectOperationException;
24+
import org.jboss.tools.intellij.report.AnalyticsReportUtils;
2425
import org.jboss.tools.intellij.stackanalysis.SaUtils;
2526
import org.jetbrains.annotations.NotNull;
2627

@@ -57,7 +58,8 @@ public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws
5758
JsonObject manifestDetails = saUtils.performSA(vf);
5859
if (manifestDetails != null) {
5960
try {
60-
saUtils.openCustomEditor(FileEditorManager.getInstance(project), manifestDetails);
61+
AnalyticsReportUtils analyticsReportUtils = new AnalyticsReportUtils();
62+
analyticsReportUtils.openCustomEditor(FileEditorManager.getInstance(project), manifestDetails);
6163
} catch (IOException ex) {
6264
throw new RuntimeException(ex);
6365
}

src/main/java/org/jboss/tools/intellij/exhort/ApiService.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,5 @@ private void setRequestProperties(final String manifestName) {
205205
if (!"go.mod".equals(manifestName) && !"requirements.txt".equals(manifestName)) {
206206
System.clearProperty("MATCH_MANIFEST_VERSIONS");
207207
}
208-
if (settings.snykToken != null && !settings.snykToken.isBlank()) {
209-
System.setProperty("EXHORT_SNYK_TOKEN", settings.snykToken);
210-
} else {
211-
System.clearProperty("EXHORT_SNYK_TOKEN");
212-
}
213208
}
214209
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Red Hat, Inc.
3+
* Distributed under license by Red Hat, Inc. All rights reserved.
4+
* This program is made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution,
6+
* and is available at http://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* Contributors:
9+
* Red Hat, Inc. - initial API and implementation
10+
******************************************************************************/
11+
12+
package org.jboss.tools.intellij.image;
13+
14+
import com.intellij.ide.plugins.PluginManagerCore;
15+
import com.intellij.openapi.application.ApplicationInfo;
16+
import com.intellij.openapi.application.ApplicationManager;
17+
import com.intellij.openapi.components.Service;
18+
import com.intellij.openapi.diagnostic.Logger;
19+
import com.intellij.openapi.extensions.PluginDescriptor;
20+
import com.intellij.openapi.extensions.PluginId;
21+
import com.redhat.exhort.Api;
22+
import com.redhat.exhort.api.AnalysisReport;
23+
import com.redhat.exhort.image.ImageRef;
24+
import com.redhat.exhort.impl.ExhortApi;
25+
import org.jboss.tools.intellij.exhort.TelemetryService;
26+
import org.jboss.tools.intellij.settings.ApiSettingsState;
27+
28+
import java.io.IOException;
29+
import java.nio.file.Files;
30+
import java.nio.file.Path;
31+
import java.util.Collection;
32+
import java.util.Map;
33+
import java.util.Set;
34+
import java.util.concurrent.CompletionException;
35+
import java.util.concurrent.ExecutionException;
36+
import java.util.function.Function;
37+
import java.util.stream.Collectors;
38+
39+
@Service(Service.Level.APP)
40+
public final class ApiService {
41+
42+
private static final Logger LOG = Logger.getInstance(ApiService.class);
43+
44+
private final Api exhortApi;
45+
46+
public ApiService() {
47+
this.exhortApi = new ExhortApi();
48+
}
49+
50+
static ApiService getInstance() {
51+
return ApplicationManager.getApplication().getService(ApiService.class);
52+
}
53+
54+
Map<BaseImage, ImageRef> getImageRefs(final Collection<BaseImage> images) {
55+
setServiceEnvironment();
56+
return images.stream().collect(Collectors.toMap(
57+
Function.identity(),
58+
image -> new ImageRef(image.getImageName(), image.getPlatform())
59+
));
60+
}
61+
62+
Map<ImageRef, AnalysisReport> getImageAnalysis(final Set<ImageRef> imageRefs) {
63+
var telemetryMsg = TelemetryService.instance().action("image-analysis");
64+
telemetryMsg.property("ecosystem", "image");
65+
telemetryMsg.property("platform", System.getProperty("os.name"));
66+
telemetryMsg.property("images", String.join(";", imageRefs.toString()));
67+
telemetryMsg.property("rhda_token", ApiSettingsState.getInstance().rhdaToken);
68+
69+
try {
70+
setServiceEnvironment();
71+
var imageReports = exhortApi.imageAnalysis(imageRefs);
72+
var reports = imageReports.get();
73+
telemetryMsg.send();
74+
return reports;
75+
} catch (IOException | InterruptedException | ExecutionException ex) {
76+
telemetryMsg.error(ex);
77+
telemetryMsg.send();
78+
throw new RuntimeException(ex);
79+
} catch (IllegalArgumentException ex) {
80+
telemetryMsg.error(ex);
81+
telemetryMsg.send();
82+
LOG.warn("Invalid image reference submitted.", ex);
83+
} catch (CompletionException ex) {
84+
telemetryMsg.error(ex);
85+
telemetryMsg.send();
86+
LOG.warn("Invalid vulnerability report returned.", ex);
87+
}
88+
return null;
89+
}
90+
91+
Path getImageAnalysisReport(final Set<ImageRef> imageRefs) {
92+
var telemetryMsg = TelemetryService.instance().action("image-analysis-report");
93+
telemetryMsg.property("ecosystem", "image");
94+
telemetryMsg.property("platform", System.getProperty("os.name"));
95+
telemetryMsg.property("images", String.join(";", imageRefs.toString()));
96+
telemetryMsg.property("rhda_token", ApiSettingsState.getInstance().rhdaToken);
97+
98+
try {
99+
setServiceEnvironment();
100+
var htmlContent = exhortApi.imageAnalysisHtml(imageRefs);
101+
var tmpFile = Files.createTempFile("exhort_image_", ".html");
102+
Files.write(tmpFile, htmlContent.get());
103+
telemetryMsg.send();
104+
return tmpFile;
105+
} catch (IOException | InterruptedException | ExecutionException exc) {
106+
telemetryMsg.error(exc);
107+
telemetryMsg.send();
108+
throw new RuntimeException(exc);
109+
}
110+
}
111+
112+
private void setServiceEnvironment() {
113+
var ideName = ApplicationInfo.getInstance().getFullApplicationName();
114+
PluginDescriptor pluginDescriptor = PluginManagerCore.getPlugin(PluginId.getId("org.jboss.tools.intellij.analytics"));
115+
if (pluginDescriptor != null) {
116+
var pluginName = pluginDescriptor.getName() + " " + pluginDescriptor.getVersion();
117+
System.setProperty("RHDA_SOURCE", ideName + " / " + pluginName);
118+
} else {
119+
System.setProperty("RHDA_SOURCE", ideName);
120+
}
121+
122+
var settings = ApiSettingsState.getInstance();
123+
System.setProperty("RHDA_TOKEN", settings.rhdaToken);
124+
125+
if (settings.syftPath != null && !settings.syftPath.isBlank()) {
126+
System.setProperty("EXHORT_SYFT_PATH", settings.syftPath);
127+
} else {
128+
System.clearProperty("EXHORT_SYFT_PATH");
129+
}
130+
131+
if (settings.syftConfigPath != null && !settings.syftConfigPath.isBlank()) {
132+
System.setProperty("EXHORT_SYFT_CONFIG_PATH", settings.syftConfigPath);
133+
} else {
134+
System.clearProperty("EXHORT_SYFT_CONFIG_PATH");
135+
}
136+
137+
if (settings.skopeoPath != null && !settings.skopeoPath.isBlank()) {
138+
System.setProperty("EXHORT_SKOPEO_PATH", settings.skopeoPath);
139+
} else {
140+
System.clearProperty("EXHORT_SKOPEO_PATH");
141+
}
142+
143+
if (settings.skopeoConfigPath != null && !settings.skopeoConfigPath.isBlank()) {
144+
System.setProperty("EXHORT_SKOPEO_CONFIG_PATH", settings.skopeoConfigPath);
145+
} else {
146+
System.clearProperty("EXHORT_SKOPEO_CONFIG_PATH");
147+
}
148+
149+
if (settings.dockerPath != null && !settings.dockerPath.isBlank()) {
150+
System.setProperty("EXHORT_DOCKER_PATH", settings.dockerPath);
151+
} else {
152+
System.clearProperty("EXHORT_DOCKER_PATH");
153+
}
154+
155+
if (settings.podmanPath != null && !settings.podmanPath.isBlank()) {
156+
System.setProperty("EXHORT_PODMAN_PATH", settings.podmanPath);
157+
} else {
158+
System.clearProperty("EXHORT_PODMAN_PATH");
159+
}
160+
161+
if (settings.imagePlatform != null && !settings.imagePlatform.isBlank()) {
162+
System.setProperty("EXHORT_IMAGE_PLATFORM", settings.imagePlatform);
163+
} else {
164+
System.clearProperty("EXHORT_IMAGE_PLATFORM");
165+
}
166+
}
167+
}

0 commit comments

Comments
 (0)