Skip to content
Closed
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

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.stackrox.jenkins.plugins.data.ImageCheckResults;
import com.stackrox.jenkins.plugins.data.PolicyViolation;
import com.stackrox.jenkins.plugins.jenkins.RunConfig;
import com.stackrox.jenkins.plugins.jenkins.ScanTask;
import com.stackrox.jenkins.plugins.report.ReportGenerator;
import com.stackrox.jenkins.plugins.services.ApiClientFactory;
import com.stackrox.jenkins.plugins.services.DetectionService;
Expand All @@ -34,7 +35,10 @@
import hudson.util.FormValidation;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import jenkins.security.MasterToSlaveCallable;
import jenkins.tasks.SimpleBuildStep;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import net.sf.json.JSONObject;
Expand All @@ -49,7 +53,11 @@

import javax.annotation.Nonnull;
import javax.net.ssl.SSLException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.List;
Expand Down Expand Up @@ -109,30 +117,27 @@ public void perform(
@Nonnull Launcher launcher,
@Nonnull TaskListener listener) throws IOException, InterruptedException {

runConfig = RunConfig.create(listener.getLogger(), run.getCharacteristicEnvVars().get("BUILD_TAG"), workspace, getImages());

try {
List<ImageCheckResults> results = checkImages();
List<ImageCheckResults> results = workspace.act(new ScanTask(
run.getCharacteristicEnvVars().get("BUILD_TAG"), workspace.getRemote(), getImages(),
getPortalAddress(), getApiToken(), getCaCertPEM(), getTLSValidationMode()));

runConfig = RunConfig.create(listener.getLogger(), run.getCharacteristicEnvVars().get("BUILD_TAG"), workspace, getImages(),
getPortalAddress(), getApiToken(), getCaCertPEM(), getTLSValidationMode());


ReportGenerator.generateBuildReport(results, runConfig.getReportsDir());
prepareArtifacts(run, workspace, launcher, listener);
run.addAction(new ViewStackroxResultsAction(results, run));
cleanupJenkinsWorkspace();

if (enforcedPolicyViolationExists(results)) {
throw new PolicyEvalException(
"At least one image violated at least one enforced system policy. Marking StackRox Image Security plugin build step failed. Check the report for additional details.");
if (this.failOnPolicyEvalFailure && enforcedPolicyViolationExists(results)) {
throw new AbortException("At least one image violated at least one enforced system policy. Marking StackRox Image Security plugin build step failed. Check the report for additional details.");
}

} catch (IOException e) {
if (this.failOnCriticalPluginError) {
throw new AbortException(String.format("Fatal error: %s. Aborting ...", e.getMessage()));
}
runConfig.getLog().println("Marking StackRox Image Security plugin build step as successful despite error.");
} catch (PolicyEvalException e) {
if (this.failOnPolicyEvalFailure) {
throw new AbortException(e.getMessage());
}
runConfig.getLog().println("Marking StackRox Image Security plugin build step as successful despite enforced policy violations.");
}

runConfig.getLog().println("No system policy violations found. Marking StackRox Image Security plugin build step as successful.");
Expand All @@ -144,29 +149,6 @@ private void prepareArtifacts(Run<?, ?> run, FilePath workspace, Launcher launch
artifactArchiver.perform(run, workspace, launcher, listener);
}

private List<ImageCheckResults> checkImages() throws IOException {
List<ImageCheckResults> results = Lists.newArrayList();

ApiClient apiClient = ApiClientFactory.newApiClient(
getPortalAddress(), getApiToken().getPlainText(), getCaCertPEM(), getTLSValidationMode());
ImageService imageService = new ImageService(apiClient);
DetectionService detectionService = new DetectionService(apiClient);

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);
results.add(new ImageCheckResults(name, cves, violatedPolicies));
}

results.sort((result1, result2) -> {
//descending order
return Boolean.compare(result1.isStatusPass(), result2.isStatusPass());
});
return results;
}

// Runs an image scan and the build time policy checks on the image

private boolean enforcedPolicyViolationExists(List<ImageCheckResults> results) {
Expand All @@ -178,17 +160,6 @@ private boolean enforcedPolicyViolationExists(List<ImageCheckResults> results) {
return false;
}

private void cleanupJenkinsWorkspace() {
runConfig.getLog().println("Cleaning up the workspace ...");

try {
runConfig.getBaseWorkDir().deleteRecursive();
runConfig.getReportsDir().deleteRecursive();
} catch (IOException | InterruptedException e) {
runConfig.getLog().println("WARN: Failed to cleanup.");
}
}

@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl) super.getDescriptor();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package com.stackrox.jenkins.plugins.jenkins;

import com.stackrox.jenkins.plugins.services.ApiClientFactory;

import hudson.AbortException;
import hudson.FilePath;
import hudson.util.Secret;
import lombok.Data;
import org.kohsuke.stapler.DataBoundSetter;

import javax.annotation.Nonnull;
import java.io.File;
Expand All @@ -14,6 +18,8 @@

@Data
public class RunConfig {
private static final long serialVersionUID = 1L;

private static final String IMAGE_LIST_FILENAME = "rox_images_to_scan";
private static final String REPORTS_DIR_NAME = "rox_image_security_reports/";

Expand All @@ -23,8 +29,18 @@ public class RunConfig {
private final List<String> imageNames;
private final String artifactsRelativePath;

public static RunConfig create(PrintStream log, String buildTag, FilePath workspace, List<String> images) throws AbortException {
try {
private final String portalAddress;
private final Secret apiToken;
private final String caCertPEM;

private final ApiClientFactory.StackRoxTlsValidationMode TLSValidationMode;


public static RunConfig createForTest(PrintStream log, String buildTag, FilePath workspace, List<String> images) throws IOException, InterruptedException {
return create(log, buildTag, workspace, images, "", Secret.fromString(""), "", ApiClientFactory.StackRoxTlsValidationMode.INSECURE_ACCEPT_ANY);
}
public static RunConfig create(PrintStream log, String buildTag, FilePath workspace, List<String> images,
String portalAddress, Secret apiToken, String caCertPEM, ApiClientFactory.StackRoxTlsValidationMode tlsValidationMode) throws IOException, InterruptedException {
FilePath baseWorkDir = new FilePath(workspace, buildTag);
FilePath reportsDir = new FilePath(workspace, REPORTS_DIR_NAME);

Expand All @@ -35,11 +51,12 @@ public static RunConfig create(PrintStream log, String buildTag, FilePath worksp
baseWorkDir,
reportsDir,
imageNames,
REPORTS_DIR_NAME
REPORTS_DIR_NAME,
portalAddress,
apiToken,
caCertPEM,
tlsValidationMode
);
} catch (IOException | InterruptedException e) {
throw new AbortException(String.format("Error in creating a run configuration: %s", e.getMessage()));
}
}

@Nonnull
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.stackrox.jenkins.plugins.jenkins;

import com.google.common.collect.Lists;

import com.stackrox.invoker.ApiClient;
import com.stackrox.jenkins.plugins.data.CVE;
import com.stackrox.jenkins.plugins.data.ImageCheckResults;
import com.stackrox.jenkins.plugins.data.PolicyViolation;
import com.stackrox.jenkins.plugins.services.ApiClientFactory;
import com.stackrox.jenkins.plugins.services.DetectionService;
import com.stackrox.jenkins.plugins.services.ImageService;

import hudson.FilePath;
import hudson.util.Secret;
import jenkins.security.MasterToSlaveCallable;
import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Serializable;
import java.nio.file.Path;
import java.util.List;

@Data
@AllArgsConstructor
public class ScanTask extends MasterToSlaveCallable<List<ImageCheckResults>, IOException> implements Serializable {

private static final long serialVersionUID = 1L;

private final String buildTag;
private final String workspace;
private final List<String> images;
private final String portalAddress;
private final Secret apiToken;
private final String caCertPEM;
private final ApiClientFactory.StackRoxTlsValidationMode tlsValidationMode;


@Override
public List<ImageCheckResults> call() throws IOException {
RunConfig runConfig = null;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
PrintStream errorOut = new PrintStream(byteArrayOutputStream);
PrintStream log = new PrintStream(errorOut);

File folder = new File(workspace);
try {
runConfig = RunConfig.create(log, buildTag, new FilePath(folder), images,
portalAddress, apiToken, caCertPEM, tlsValidationMode);
} catch (InterruptedException e) {
throw new IOException(e);
}
List<ImageCheckResults> imageCheckResults = checkImages(runConfig);
cleanupJenkinsWorkspace(runConfig);
return imageCheckResults;

}

private List<ImageCheckResults> checkImages(RunConfig runConfig) throws IOException {
List<ImageCheckResults> results = Lists.newArrayList();

ApiClient apiClient = ApiClientFactory.newApiClient(
runConfig.getPortalAddress(),
runConfig.getApiToken().getPlainText(),
runConfig.getCaCertPEM(),
runConfig.getTLSValidationMode()
);
ImageService imageService = new ImageService(apiClient);
DetectionService detectionService = new DetectionService(apiClient);

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);
results.add(new ImageCheckResults(name, cves, violatedPolicies));
}

results.sort((result1, result2) -> {
//descending order
return Boolean.compare(result1.isStatusPass(), result2.isStatusPass());
});
return results;
}

private void cleanupJenkinsWorkspace(RunConfig runConfig) {
runConfig.getLog().println("Cleaning up the workspace ...");

try {
runConfig.getBaseWorkDir().deleteRecursive();
runConfig.getReportsDir().deleteRecursive();
} catch (IOException | InterruptedException e) {
runConfig.getLog().println("WARN: Failed to cleanup.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

public class ReportGenerator {

private static final String[] CVES_HEADER = {"COMPONENT", "VERSION", "CVE", "FIXABLE", "SEVERITY", "CVSS SCORE", "SCORE TYPE", "LINK"};
private static final String[] CVES_HEADER = {"COMPONENT", "VERSION", "CVE", "SEVERITY", "FIXABLE", "CVSS SCORE", "SCORE TYPE", "LINK"};
private static final String[] VIOLATED_POLICIES_HEADER = {"POLICY", "SEVERITY", "DESCRIPTION", "VIOLATION", "REMEDIATION", "ENFORCED"};
private static final String CVES_FILENAME = "cves.csv";
private static final String POLICY_VIOLATIONS_FILENAME = "policyViolations.csv";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
package com.stackrox.jenkins.plugins.jenkins;

import com.google.common.collect.ImmutableList;

import com.stackrox.jenkins.plugins.StackroxBuilder;

import com.stackrox.jenkins.plugins.services.ApiClientFactory;

import hudson.AbortException;
import hudson.FilePath;

import hudson.util.Secret;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.nio.file.Path;
import java.util.Collections;
Expand All @@ -30,20 +41,20 @@ class RunConfigTest {

@Test
void createShouldFailWhenNoImagesSpecifiedAndFileDoesNotExist() {
Exception exception = assertThrows(AbortException.class, () -> RunConfig.create(LOG, BUILD_TAG, new FilePath(folder.toFile()), Collections.emptyList()));
assertTrue(exception.getMessage().contains("Error in creating a run configuration: rox_images_to_scan not found at"));
Exception exception = assertThrows(IOException.class, () -> RunConfig.createForTest(LOG, BUILD_TAG, new FilePath(folder.toFile()), Collections.emptyList()));
assertTrue(exception.getMessage().contains("rox_images_to_scan not found"));
}

@Test
void createShouldFailWhenNoImagesToScan() throws IOException {
assertTrue(imagesToScanFile().createNewFile());
Exception exception = assertThrows(AbortException.class, () -> RunConfig.create(LOG, BUILD_TAG, new FilePath(folder.toFile()), Collections.emptyList()));
assertEquals("Error in creating a run configuration: no images to scan", exception.getMessage());
Exception exception = assertThrows(IOException.class, () -> RunConfig.createForTest(LOG, BUILD_TAG, new FilePath(folder.toFile()), Collections.emptyList()));
assertEquals("no images to scan", exception.getMessage());
}

@Test
void createShouldReturnListOfProvidedImagesAndCreateRequiredDirs() throws IOException, InterruptedException {
RunConfig runConfig = RunConfig.create(LOG, BUILD_TAG, new FilePath(folder.toFile()), ImmutableList.of("A", "B", "C"));
RunConfig runConfig = RunConfig.createForTest(LOG, BUILD_TAG, new FilePath(folder.toFile()), ImmutableList.of("A", "B", "C"));
assertEquals(ImmutableList.of("A", "B", "C"), runConfig.getImageNames());
assertTrue(runConfig.getReportsDir().exists());
assertFalse(runConfig.getBaseWorkDir().exists());
Expand All @@ -55,13 +66,41 @@ void createShouldReturnListOfFromFileAndCreateRequiredDirs() throws IOException,
FileWriter writer = new FileWriter(imagesToScan);
writer.write("A\nB\nC\n");
writer.close();
RunConfig runConfig = RunConfig.create(LOG, BUILD_TAG, new FilePath(folder.toFile()), Collections.emptyList());
RunConfig runConfig = RunConfig.createForTest(LOG, BUILD_TAG, new FilePath(folder.toFile()), Collections.emptyList());
assertEquals(ImmutableList.of("A", "B", "C"), runConfig.getImageNames());
assertTrue(runConfig.getReportsDir().exists());
assertTrue(runConfig.getBaseWorkDir().exists());
assertEquals("rox_image_security_reports/", runConfig.getArtifactsRelativePath());
}

@Test
void shouldBeSerializable() throws IOException, InterruptedException, ClassNotFoundException {
File imagesToScan = imagesToScanFile();
FileWriter writer = new FileWriter(imagesToScan);
writer.write("A\nB\nC\n");
writer.close();

ScanTask original = new ScanTask(BUILD_TAG, folder.toString(), ImmutableList.of( "A", "B", "C"), "", Secret.fromString(""), "", ApiClientFactory.StackRoxTlsValidationMode.VALIDATE);

FileOutputStream fileOutputStream
= new FileOutputStream("yourfile.txt");
ObjectOutputStream objectOutputStream
= new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(original);
objectOutputStream.flush();
objectOutputStream.close();

FileInputStream fileInputStream
= new FileInputStream("yourfile.txt");
ObjectInputStream objectInputStream
= new ObjectInputStream(fileInputStream);
ScanTask copy = (ScanTask) objectInputStream.readObject();
objectInputStream.close();

assertEquals(ImmutableList.of("A", "B", "C"), copy.getImages());
assertEquals(BUILD_TAG, copy.getBuildTag());
}

private File imagesToScanFile() {
File workDir = new File(folder.toFile(), BUILD_TAG);
workDir.mkdirs();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
COMPONENT,VERSION,CVE,FIXABLE,SEVERITY,CVSS SCORE,SCORE TYPE,LINK
COMPONENT,VERSION,CVE,SEVERITY,FIXABLE,CVSS SCORE,SCORE TYPE,LINK
util-linux,2.25.2-6,CVE-2015-5224,IMPORTANT,false,9.8,V3,https://security-tracker.debian.org/tracker/CVE-2015-5224
gcc-4.8,4.8.4-1,CVE-2017-11671,MODERATE,false,4.0,V3,https://security-tracker.debian.org/tracker/CVE-2017-11671
bzip2,1.0.6-7,CVE-2016-3189,LOW,true,6.5,V3,https://security-tracker.debian.org/tracker/CVE-2016-3189
Loading