Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ hs_err_pid*
.idea/
cookies.txt
nohup.out
.DS_Store

!functionaltest-jenkins-plugin/gradle/wrapper/gradle-wrapper.jar
16 changes: 14 additions & 2 deletions stackrox-container-image-scanner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,17 @@ freestyle projects and pipelines.
</blockquote></td>
</tr>
<tr class="even">
<td><p><code>cluster</code></p></td>
<td><p>The Secured Cluster <b>name</b> or <b>ID</b> to delegate image scans to</p></td>
<td>
<p>Leave this blank to use the default delegated scanning config.</p>
<blockquote>
<p><strong>Note</strong></p>
<p>Requires version <code>4.3+</code> of the StackRox Kubernetes Security Platform.</p>
</blockquote>
</td>
</tr>
<tr class="odd">
<td colspan="3"><p><em><sup>*</sup> Required</em></p></td>
</tr>
</tbody>
Expand Down Expand Up @@ -206,7 +217,8 @@ To use the StackRox Container Image Scanner plugin in your pipeline:
failOnCriticalPluginError: true,
failOnPolicyEvalFailure: true,
portalAddress: 'https://central.stackrox:443',
imageNames: "nginx:latest,ubuntu:bionic,busybox:stable"
imageNames: "nginx:latest,ubuntu:bionic,busybox:stable",
cluster: ""
)
}
}
Expand All @@ -215,7 +227,7 @@ To use the StackRox Container Image Scanner plugin in your pipeline:
```

- For more information about the variables, see the [plugin
configuration variables](#plugin-configuration-variables)
configuration variables](#freestyle-project)
section.

![plugin pipeline](./src/main/resources/img/plugin-pipeline.png)
Expand Down
6 changes: 6 additions & 0 deletions stackrox-container-image-scanner/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11139,6 +11139,9 @@ components:
type: array
items:
type: string
cluster:
type: string
description: Cluster to delegate scan to, may be the cluster's name or ID.
v1BuildDetectionResponse:
type: object
properties:
Expand Down Expand Up @@ -11968,6 +11971,9 @@ components:
type: boolean
includeSnoozed:
type: boolean
cluster:
type: string
description: Cluster to delegate scan to, may be the cluster's name or ID.
v1WatchImageRequest:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ public class StackroxBuilder extends Builder implements SimpleBuildStep {
private boolean enableTLSVerification;
@DataBoundSetter
private String caCertPEM;
@DataBoundSetter
private String cluster;

private RunConfig runConfig;

Expand Down Expand Up @@ -155,8 +157,8 @@ private List<ImageCheckResults> checkImages() throws IOException {
for (String name : runConfig.getImageNames()) {
runConfig.getLog().printf("Checking image %s...%n", name);

List<CVE> cves = imageService.getImageScanResults(name);
List<PolicyViolation> violatedPolicies = detectionService.getPolicyViolations(name);
List<CVE> cves = imageService.getImageScanResults(name, cluster);
List<PolicyViolation> violatedPolicies = detectionService.getPolicyViolations(name, cluster);
results.add(new ImageCheckResults(name, cves, violatedPolicies));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ public DetectionService(ApiClient client) {
api = new DetectionServiceApi(client);
}

public List<PolicyViolation> getPolicyViolations(String imageName) throws IOException {
public List<PolicyViolation> getPolicyViolations(String imageName, String cluster) throws IOException {

List<StorageAlert> alerts = getAlertsForImage(imageName);
List<StorageAlert> alerts = getAlertsForImage(imageName, cluster);

return emptyIfNull(alerts).stream()
.filter(a -> a.getPolicy() != null)
Expand All @@ -46,10 +46,12 @@ private String getViolations(StorageAlert a) {
.collect(Collectors.joining(" - "));
}

private List<StorageAlert> getAlertsForImage(String imageName) throws ServiceException {
private List<StorageAlert> getAlertsForImage(String imageName, String cluster) throws ServiceException {
try {
return api.detectionServiceDetectBuildTime(new V1BuildDetectionRequest()
.imageName(imageName))
.imageName(imageName)
.cluster(cluster)
)
.getAlerts();
} catch (ApiException e) {
throw ServiceException.fromApiException("Failed build time detection request", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ public ImageService(ApiClient client) {
}


public List<CVE> getImageScanResults(String imageName) throws IOException {
public List<CVE> getImageScanResults(String imageName, String cluster) throws IOException {
V1ScanImageRequest request = new V1ScanImageRequest()
.imageName(imageName)
.force(true);
.force(true)
.cluster(cluster);
StorageImageScan scan;
try {
scan = api.imageServiceScanImage(request).getScan();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
help="/plugin/stackrox-container-image-scanner/help/help-imageNames.html">
<f:expandableTextbox field="imageNames"/>
</f:entry>
<f:entry title="${%ClusterTitle}"
help="/plugin/stackrox-container-image-scanner/help/help-cluster.html">
<f:textbox field="cluster"/>
</f:entry>
<f:entry>
<j:if test="${instance != null}">
<f:optionalBlock title="${%EnableTLSVerification}" name="enableTLSVerification"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ ValidateConnectionTitle=Validate connection
EnableTLSVerification=Enable TLS verification
CACertificate=CA Certificate
ImageNames=Image Names
ClusterTitle=Cluster
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div>
Optionally specify the Secured Cluster <b>name</b> or <b>ID</b> to delegate image scans to.
<br />
<br />
This may be necessary in certain environments with isolated image registries.
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void shouldThrowOn500() {
MOCK_SERVER.stubFor(post(anyUrl()).willReturn(serverError()
.withBodyFile("v1/detect/build/error.json")));

Exception exception = assertThrows(IOException.class, () -> detectionService.getPolicyViolations("jenkins:lts"));
Exception exception = assertThrows(IOException.class, () -> detectionService.getPolicyViolations("jenkins:lts", ""));
String expected = "Failed build time detection request. Status code: 500. Error: image enrichment error: error getting metadata for image: docker.io/library/jenkins:lts errors: [error getting metadata from registry: \"Public DockerHub\": Failed to get the manifest digest : Head \"https://registry-1.docker.io/v2/library/jenkins/manifests/lts\": http: non-successful response (status=404 body=\"\"), error getting metadata from registry: \"Autogenerated https://registry-1.docker.io for cluster remote\": Failed to get the manifest digest : Head \"https://registry-1.docker.io/v2/library/jenkins/manifests/lts\": http: non-successful response (status=404 body=\"\")]";
assertEquals(expected, exception.getMessage());
}
Expand All @@ -51,15 +51,15 @@ public void shouldThrowOn500() {
public void shouldNotThrowWhenNoDataFor200() throws IOException {
MOCK_SERVER.stubFor(postDetectBuild().willReturn(
ok().withBody("{}")));
List<PolicyViolation> violations = detectionService.getPolicyViolations("nginx:latest");
List<PolicyViolation> violations = detectionService.getPolicyViolations("nginx:latest", "");
assertEquals(0, violations.size());
}

@Test
public void shouldNotFailOnMissingData() throws IOException {
MOCK_SERVER.stubFor(postDetectBuild().willReturn(
ok().withBodyFile("v1/detect/build/minimal.json")));
List<PolicyViolation> actual = detectionService.getPolicyViolations("nginx:latest");
List<PolicyViolation> actual = detectionService.getPolicyViolations("nginx:latest", "");

List<PolicyViolation> expected = ImmutableList.of(new PolicyViolation(new StoragePolicy().enforcementActions(FAIL_BUILD_ENFORCEMENTS), ""));

Expand All @@ -70,7 +70,7 @@ public void shouldNotFailOnMissingData() throws IOException {
public void shouldJoinViolations() throws IOException {
MOCK_SERVER.stubFor(postDetectBuild().willReturn(
ok().withBodyFile("v1/detect/build/violations.json")));
List<PolicyViolation> actual = detectionService.getPolicyViolations("nginx:latest");
List<PolicyViolation> actual = detectionService.getPolicyViolations("nginx:latest", "");

List<PolicyViolation> expected = ImmutableList.of(new PolicyViolation(new StoragePolicy().enforcementActions(Collections.emptyList()), "A - B - C"));

Expand All @@ -80,7 +80,7 @@ public void shouldJoinViolations() throws IOException {
private MappingBuilder postDetectBuild() {
return post(urlEqualTo("/v1/detect/build"))
.withHeader("Authorization", equalTo("Bearer {some token}"))
.withRequestBody(equalToJson("{\"imageName\" : \"nginx:latest\", \"policyCategories\" : [ ]}"));
.withRequestBody(equalToJson("{\"imageName\" : \"nginx:latest\", \"policyCategories\" : [ ], \"cluster\" : \"\"}"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public void shouldThrowOn500() {
MOCK_SERVER.stubFor(post(anyUrl()).willReturn(serverError()
.withBodyFile("v1/images/scan/error.json")));

Exception exception = assertThrows(IOException.class, () -> imageService.getImageScanResults("jenkins:lts"));
Exception exception = assertThrows(IOException.class, () -> imageService.getImageScanResults("jenkins:lts", ""));
String expected = "Failed image scan request. Status code: 500. Error" +
": image enrichment error" +
": error getting metadata for image: docker.io/library/jenkins:lts errors" +
Expand All @@ -58,7 +58,7 @@ public void shouldThrowOn500() {
public void shouldThrowWhenNoDataFor200() throws IOException {
MOCK_SERVER.stubFor(postImagesScan().willReturn(
ok().withBody("{}")));
Exception exception = assertThrows(NullPointerException.class, () -> imageService.getImageScanResults("nginx:latest"));
Exception exception = assertThrows(NullPointerException.class, () -> imageService.getImageScanResults("nginx:latest", ""));
assertEquals("Did not get scan results from StackRox", exception.getMessage());
}

Expand All @@ -67,7 +67,7 @@ public void shouldThrowWhenNoDataFor200() throws IOException {
public void shouldNotFailOnMissingData(String file) throws IOException {
MOCK_SERVER.stubFor(postImagesScan().willReturn(
ok().withBodyFile("v1/images/scan/" + file)));
List<CVE> actual = imageService.getImageScanResults("nginx:latest");
List<CVE> actual = imageService.getImageScanResults("nginx:latest", "");
ImmutableList<CVE> expected = ImmutableList.of(
new CVE(null, null, new StorageEmbeddedVulnerability()
.cve("CVE-MISSING-DATA")
Expand All @@ -80,7 +80,7 @@ public void shouldNotFailOnMissingData(String file) throws IOException {
public void shouldNotFailOnUnknownEnumValue() throws IOException {
MOCK_SERVER.stubFor(postImagesScan().willReturn(
ok().withBodyFile("v1/images/scan/unknown-enum.json")));
List<CVE> actual = imageService.getImageScanResults("nginx:latest");
List<CVE> actual = imageService.getImageScanResults("nginx:latest", "");
ImmutableList<CVE> expected = ImmutableList.of(
new CVE(null, null, new StorageEmbeddedVulnerability()
.cve("CVE-MISSING-DATA")
Expand All @@ -92,6 +92,6 @@ public void shouldNotFailOnUnknownEnumValue() throws IOException {
private MappingBuilder postImagesScan() {
return post(urlEqualTo("/v1/images/scan"))
.withHeader("Authorization", equalTo("Bearer {some token}"))
.withRequestBody(equalToJson("{\"imageName\" : \"nginx:latest\",\"force\" : true}"));
.withRequestBody(equalToJson("{\"imageName\" : \"nginx:latest\",\"force\" : true, \"cluster\" : \"\"}"));
}
}