diff --git a/.gitignore b/.gitignore index 442cdf1c..5a71cf4e 100644 --- a/.gitignore +++ b/.gitignore @@ -30,5 +30,6 @@ hs_err_pid* .idea/ cookies.txt nohup.out +.DS_Store !functionaltest-jenkins-plugin/gradle/wrapper/gradle-wrapper.jar diff --git a/stackrox-container-image-scanner/README.md b/stackrox-container-image-scanner/README.md index 85a7f4ec..ecc00083 100644 --- a/stackrox-container-image-scanner/README.md +++ b/stackrox-container-image-scanner/README.md @@ -156,6 +156,17 @@ freestyle projects and pipelines. +

cluster

+

The Secured Cluster name or ID to delegate image scans to

+ +

Leave this blank to use the default delegated scanning config.

+
+

Note

+

Requires version 4.3+ of the StackRox Kubernetes Security Platform.

+
+ + +

* Required

@@ -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: "" ) } } @@ -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) diff --git a/stackrox-container-image-scanner/api.yaml b/stackrox-container-image-scanner/api.yaml index 08945a77..cb7a5dad 100644 --- a/stackrox-container-image-scanner/api.yaml +++ b/stackrox-container-image-scanner/api.yaml @@ -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: @@ -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: diff --git a/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/StackroxBuilder.java b/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/StackroxBuilder.java index a7207151..dd4bf51e 100644 --- a/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/StackroxBuilder.java +++ b/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/StackroxBuilder.java @@ -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; @@ -155,8 +157,8 @@ private List checkImages() throws IOException { for (String name : runConfig.getImageNames()) { runConfig.getLog().printf("Checking image %s...%n", name); - List cves = imageService.getImageScanResults(name); - List violatedPolicies = detectionService.getPolicyViolations(name); + List cves = imageService.getImageScanResults(name, cluster); + List violatedPolicies = detectionService.getPolicyViolations(name, cluster); results.add(new ImageCheckResults(name, cves, violatedPolicies)); } diff --git a/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/DetectionService.java b/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/DetectionService.java index 5316e666..97f4129f 100644 --- a/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/DetectionService.java +++ b/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/DetectionService.java @@ -27,9 +27,9 @@ public DetectionService(ApiClient client) { api = new DetectionServiceApi(client); } - public List getPolicyViolations(String imageName) throws IOException { + public List getPolicyViolations(String imageName, String cluster) throws IOException { - List alerts = getAlertsForImage(imageName); + List alerts = getAlertsForImage(imageName, cluster); return emptyIfNull(alerts).stream() .filter(a -> a.getPolicy() != null) @@ -46,10 +46,12 @@ private String getViolations(StorageAlert a) { .collect(Collectors.joining(" - ")); } - private List getAlertsForImage(String imageName) throws ServiceException { + private List 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); diff --git a/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/ImageService.java b/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/ImageService.java index 024b1e70..3be57b2a 100644 --- a/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/ImageService.java +++ b/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/ImageService.java @@ -27,10 +27,11 @@ public ImageService(ApiClient client) { } - public List getImageScanResults(String imageName) throws IOException { + public List 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(); diff --git a/stackrox-container-image-scanner/src/main/resources/com/stackrox/jenkins/plugins/StackroxBuilder/config.jelly b/stackrox-container-image-scanner/src/main/resources/com/stackrox/jenkins/plugins/StackroxBuilder/config.jelly index 1890d21b..b703eaa2 100644 --- a/stackrox-container-image-scanner/src/main/resources/com/stackrox/jenkins/plugins/StackroxBuilder/config.jelly +++ b/stackrox-container-image-scanner/src/main/resources/com/stackrox/jenkins/plugins/StackroxBuilder/config.jelly @@ -12,6 +12,10 @@ help="/plugin/stackrox-container-image-scanner/help/help-imageNames.html"> + + + + Optionally specify the Secured Cluster name or ID to delegate image scans to. +
+
+ This may be necessary in certain environments with isolated image registries. + diff --git a/stackrox-container-image-scanner/src/test/java/com/stackrox/jenkins/plugins/services/DetectionServiceTest.java b/stackrox-container-image-scanner/src/test/java/com/stackrox/jenkins/plugins/services/DetectionServiceTest.java index 0a79f420..e4bd46ad 100644 --- a/stackrox-container-image-scanner/src/test/java/com/stackrox/jenkins/plugins/services/DetectionServiceTest.java +++ b/stackrox-container-image-scanner/src/test/java/com/stackrox/jenkins/plugins/services/DetectionServiceTest.java @@ -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()); } @@ -51,7 +51,7 @@ public void shouldThrowOn500() { public void shouldNotThrowWhenNoDataFor200() throws IOException { MOCK_SERVER.stubFor(postDetectBuild().willReturn( ok().withBody("{}"))); - List violations = detectionService.getPolicyViolations("nginx:latest"); + List violations = detectionService.getPolicyViolations("nginx:latest", ""); assertEquals(0, violations.size()); } @@ -59,7 +59,7 @@ public void shouldNotThrowWhenNoDataFor200() throws IOException { public void shouldNotFailOnMissingData() throws IOException { MOCK_SERVER.stubFor(postDetectBuild().willReturn( ok().withBodyFile("v1/detect/build/minimal.json"))); - List actual = detectionService.getPolicyViolations("nginx:latest"); + List actual = detectionService.getPolicyViolations("nginx:latest", ""); List expected = ImmutableList.of(new PolicyViolation(new StoragePolicy().enforcementActions(FAIL_BUILD_ENFORCEMENTS), "")); @@ -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 actual = detectionService.getPolicyViolations("nginx:latest"); + List actual = detectionService.getPolicyViolations("nginx:latest", ""); List expected = ImmutableList.of(new PolicyViolation(new StoragePolicy().enforcementActions(Collections.emptyList()), "A - B - C")); @@ -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\" : \"\"}")); } } diff --git a/stackrox-container-image-scanner/src/test/java/com/stackrox/jenkins/plugins/services/ImageServiceTest.java b/stackrox-container-image-scanner/src/test/java/com/stackrox/jenkins/plugins/services/ImageServiceTest.java index 6a6783f4..d3c141a7 100644 --- a/stackrox-container-image-scanner/src/test/java/com/stackrox/jenkins/plugins/services/ImageServiceTest.java +++ b/stackrox-container-image-scanner/src/test/java/com/stackrox/jenkins/plugins/services/ImageServiceTest.java @@ -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" + @@ -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()); } @@ -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 actual = imageService.getImageScanResults("nginx:latest"); + List actual = imageService.getImageScanResults("nginx:latest", ""); ImmutableList expected = ImmutableList.of( new CVE(null, null, new StorageEmbeddedVulnerability() .cve("CVE-MISSING-DATA") @@ -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 actual = imageService.getImageScanResults("nginx:latest"); + List actual = imageService.getImageScanResults("nginx:latest", ""); ImmutableList expected = ImmutableList.of( new CVE(null, null, new StorageEmbeddedVulnerability() .cve("CVE-MISSING-DATA") @@ -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\" : \"\"}")); } }