diff --git a/build.gradle b/build.gradle index 0815bb55..66e8cb7a 100644 --- a/build.gradle +++ b/build.gradle @@ -53,16 +53,24 @@ dependencies { exclude group: 'org.apache.logging.log4j', module: 'log4j-core' } - compile 'com.checkmarx:cx-client-common:2022.3.16', - 'com.fasterxml.jackson.core:jackson-core:2.11.3', + compile ('com.checkmarx:cx-client-common:2022.4.3') { + exclude group: 'org.apache.commons', module: 'commons-compress' + exclude group: 'org.yaml' , module: 'snakeyaml' + exclude group: 'com.google.code.gson', module: 'gson' + } + + compile 'com.fasterxml.jackson.core:jackson-core:2.11.3', 'com.fasterxml.jackson.core:jackson-annotations:2.11.3', - 'com.fasterxml.jackson.core:jackson-databind:2.11.3', + 'com.fasterxml.jackson.core:jackson-databind:2.14.1', 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.3', 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.10.5', 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.10.5', 'org.apache.logging.log4j:log4j-slf4j-impl:2.17.1', 'org.apache.logging.log4j:log4j-api:2.17.1', - 'org.apache.logging.log4j:log4j-core:2.17.1' + 'org.apache.logging.log4j:log4j-core:2.17.1', + 'org.apache.commons:commons-compress:1.22', + 'com.google.code.gson:gson:2.8.9', + 'org.yaml:snakeyaml:1.33' constraints { implementation('io.vertx:vertx-web:3.9.7') { because 'previous versions have a bug impacting this application' diff --git a/gradle.properties b/gradle.properties index 9c2029a9..604c6b93 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ description = Provides automatic scan of code by Checkmarx server and shows results summary and trend in Jenkins interface. group = com.checkmarx.jenkins -version = 2022.3.3 +version = 2022.4.3 repositoryVersion= diff --git a/sample/cx.config b/sample/cx.config new file mode 100644 index 00000000..5d9dc7d9 --- /dev/null +++ b/sample/cx.config @@ -0,0 +1,21 @@ +project: + fullPath: "Testcac02" + origin: "jenkins" +sast: + engineConfiguration: "Korean" + excludeFolders: \"_cvs, .svn, .hg, .git, .bzr, bin, obj, backup, node_modules\" + includeExcludePattern: \"!**/.cxsca-results.json, !**/.cxsca-sast-results.json, !.checkmarx/cx.config,!**/*.DS_Store, !**/*.ipr, !**/*.iws, !**/*.TEST_SOMETHING, !**/*.bak, !**/*.tmp, !**/*.aac, !**/*.aif, !**/*.iff, !**/*.m3u, !**/*.mid, !**/*.mp3, !**/*.mpa, !**/*.ra, !**/*.wav, !**/*.wma, !**/*.3g2, !**/*.3gp, !**/*.asf, !**/*.asx, !**/*.avi, !**/*.flv, !**/*.mov, !**/*.mp4, !**/*.mpg, !**/*.rm, !**/*.swf, !**/*.vob, !**/*.wmv, !**/*.bmp, !**/*.gif, !**/*.jpg, !**/*.png, !**/*.psd, !**/*.tif, !**/*.jar, !**/*.zip, !**/*.rar, !**/*.exe, !**/*.dll, !**/*.pdb, !**/*.7z, !**/*.gz, !**/*.tar.gz, !**/*.tar, !**/*.ahtm, !**/*.ahtml, !**/*.fhtml, !**/*.hdm, !**/*.hdml, !**/*.hsql, !**/*.ht, !**/*.hta, !**/*.htc, !**/*.htd, !**/*.htmls, !**/*.ihtml, !**/*.mht, !**/*.mhtm, !**/*.mhtml, !**/*.ssi, !**/*.stm, !**/*.stml, !**/*.ttml, !**/*.txn, !**/*.class, !**/*.iml, !**/Checkmarx/Reports/*.*\" + high: 3 + medium: 1 + low: 2 + incremental: false + preset: "All" + privateScan: false + overrideProjectSetting: false +sca: + fileInclude: \"*.dll\" + fileExclude: \"nothing*.jar\" + pathExclude: \"!**/*.DS_Store, !**/*.ipr, !**/*.iws, !**/*.TEST_SOMETHING, !**/*.bak, !**/*.tmp, !**/*.aac, !**/*.aif, !**/*.iff, !**/*.m3u, !**/*.mid, !**/*.mp3, !**/*.mpa, !**/*.ra, !**/*.wav, !**/*.wma, !**/*.3g2, !**/*.3gp, !**/*.asf, !**/*.asx, !**/*.avi, !**/*.flv, !**/*.mov, !**/*.mp4, !**/*.mpg, !**/*.rm, !**/*.swf, !**/*.vob, !**/*.wmv, !**/*.bmp, !**/*.gif, !**/*.jpg, !**/*.png, !**/*.psd, !**/*.tif, !**/*.jar, !**/*.zip, !**/*.rar, !**/*.exe, !**/*.dll, !**/*.pdb, !**/*.7z, !**/*.gz, !**/*.tar.gz, !**/*.tar, !**/*.ahtm, !**/*.ahtml, !**/*.fhtml, !**/*.hdm, !**/*.hdml, !**/*.hsql, !**/*.ht, !**/*.hta, !**/*.htc, !**/*.htd, !**/*.htmls, !**/*.ihtml, !**/*.mht, !**/*.mhtm, !**/*.mhtml, !**/*.ssi, !**/*.stm, !**/*.stml, !**/*.ttml, !**/*.txn, !**/*.class, !**/*.iml, !**/Checkmarx/Reports/*.*\" + high: 3 + medium: 3 + low: 3 \ No newline at end of file diff --git a/src/main/java/com/checkmarx/jenkins/CxConnectionDetails.java b/src/main/java/com/checkmarx/jenkins/CxConnectionDetails.java index 54dd0a65..4b9e6513 100644 --- a/src/main/java/com/checkmarx/jenkins/CxConnectionDetails.java +++ b/src/main/java/com/checkmarx/jenkins/CxConnectionDetails.java @@ -21,8 +21,8 @@ public class CxConnectionDetails { private String serverUrl; private String username; private String encryptedPassword; - private Boolean isProxy; - private Boolean isScaProxy; + private boolean isProxy; + private boolean isScaProxy; public String getServerUrl() { return serverUrl; @@ -48,18 +48,18 @@ public void setPassword(String encryptedPassword) { this.encryptedPassword = encryptedPassword; } - public Boolean isProxy() { + public boolean isProxy() { return isProxy; } - public void setProxy(Boolean proxy) { + public void setProxy(boolean proxy) { isProxy = proxy; } - public Boolean isScaProxy() { + public boolean isScaProxy() { return isScaProxy; } - public void setScaProxy(Boolean scaProxy) { + public void setScaProxy(boolean scaProxy) { isScaProxy = scaProxy; } diff --git a/src/main/java/com/checkmarx/jenkins/CxScanBuilder.java b/src/main/java/com/checkmarx/jenkins/CxScanBuilder.java index 17ed3212..91414ee7 100644 --- a/src/main/java/com/checkmarx/jenkins/CxScanBuilder.java +++ b/src/main/java/com/checkmarx/jenkins/CxScanBuilder.java @@ -4,6 +4,7 @@ import com.checkmarx.configprovider.dto.ResourceType; import com.checkmarx.configprovider.dto.interfaces.ConfigReader; import com.checkmarx.jenkins.configascode.ConfigAsCode; +import com.checkmarx.jenkins.configascode.ProjectConfig; import com.checkmarx.jenkins.configascode.SastConfig; import com.checkmarx.jenkins.configascode.ScaConfig; import com.checkmarx.jenkins.exception.CxCredException; @@ -99,6 +100,8 @@ public class CxScanBuilder extends Builder implements SimpleBuildStep { // Persistent plugin configuration parameters ////////////////////////////////////////////////////////////////////////////////////// private boolean useOwnServerCredentials; + + private boolean overrideProjectSetting; private boolean configAsCode; @Nullable @@ -318,8 +321,17 @@ public boolean isConfigAsCode() { public void setConfigAsCode(boolean configAsCode) { this.configAsCode = configAsCode; } + + public boolean isOverrideProjectSetting() { + return overrideProjectSetting; + } - @Nullable + @DataBoundSetter + public void setOverrideProjectSetting(boolean overrideProjectSetting) { + this.overrideProjectSetting = overrideProjectSetting; + } + + @Nullable public String getServerUrl() { return serverUrl; } @@ -905,7 +917,7 @@ public void perform(@Nonnull Run run, @Nonnull FilePath workspace, @Nonnul try { overrideConfigAsCode(config, workspace); } catch (ConfigurationException e) { - log.warn("couldn't load config file", e.getMessage()); + log.warn("couldn't load config file: " + e.getMessage(), e); } } @@ -916,7 +928,7 @@ public void perform(@Nonnull Run run, @Nonnull FilePath workspace, @Nonnul //validate at least one scan type is enabled if (!config.isSastEnabled() && !config.isAstScaEnabled() && !config.isOsaEnabled()) { log.error("Both SAST and dependency scan are disabled. Exiting."); - run.setResult(Result.FAILURE); + run.setResult(Result.FAILURE); return; } @@ -1030,8 +1042,7 @@ private ConfigAsCode getConfigAsCode(ConfigReader reader) throws ConfigurationEx if (configProvider.hasConfiguration(CX_ORIGIN, "project")) configAsCodeFromFile.setProject( - configProvider.getStringConfiguration(CX_ORIGIN, "project") - ); + configProvider.getConfiguration(CX_ORIGIN, "project",ProjectConfig.class)); if (configProvider.hasConfiguration(CX_ORIGIN, "team")) configAsCodeFromFile.setTeam( @@ -1052,14 +1063,14 @@ private void overrideConfigAsCode(ConfigAsCode configAsCodeFromFile, CxScanConfi //map global Optional.ofNullable(configAsCodeFromFile).ifPresent(cac -> { - if (StringUtils.isNotEmpty(cac.getProject())) { - scanConfig.setProjectName(cac.getProject()); - overridesResults.put("Project Name:", String.valueOf(cac.getProject())); + if (StringUtils.isNotEmpty(cac.getProject().getFullPath())) { + scanConfig.setProjectName(cac.getProject().getFullPath()); + overridesResults.put("Project Name:", String.valueOf(cac.getProject().getFullPath())); } if (StringUtils.isNotEmpty(cac.getTeam())) { scanConfig.setTeamPath(cac.getTeam()); - overridesResults.put("Project Name:", String.valueOf(cac.getTeam())); + overridesResults.put("Team Name:", String.valueOf(cac.getTeam())); } }); @@ -1067,7 +1078,7 @@ private void overrideConfigAsCode(ConfigAsCode configAsCodeFromFile, CxScanConfi mapScaConfiguration(Optional.ofNullable(configAsCodeFromFile.getSca()), scanConfig, overridesResults); if (!overridesResults.isEmpty()) { - log.info("the following fields was overrides using config as code file : "); + log.info("The following fields are overridden using config as code file : "); overridesResults.keySet().forEach(key -> log.info(String.format("%s = %s", key, overridesResults.get(key)))); } } @@ -1156,6 +1167,12 @@ private void mapSastConfiguration(Optional sast, CxScanConfig scanCo scanConfig.setIncremental(pValue); overridesResults.put("Is Incremental", String.valueOf(pValue)); }); + + sast.map(SastConfig::isOverrideProjectSetting) + .ifPresent(pValue -> { + scanConfig.setIsOverrideProjectSetting(pValue); + overridesResults.put("Is OverrideProjectSetting", String.valueOf(pValue)); + }); sast.map(SastConfig::isPrivateScan) .ifPresent(pValue -> { @@ -1190,8 +1207,10 @@ private void mapSastConfiguration(Optional sast, CxScanConfig scanCo .filter(StringUtils::isNotBlank) .ifPresent(pValue -> { scanConfig.setPresetName(pValue); + scanConfig.setPresetId(null); overridesResults.put("Preset", pValue); }); + sast.map(SastConfig::getExcludeFolders) .filter(StringUtils::isNotBlank) @@ -1313,6 +1332,8 @@ private Boolean verifyCustomCharacters(String inputString) { } private CxScanConfig resolveConfiguration(Run run, DescriptorImpl descriptor, EnvVars env, CxLoggerAdapter log) throws IOException { CxScanConfig ret = new CxScanConfig(); + + ret.setIsOverrideProjectSetting(overrideProjectSetting); if (isIncremental() && isForceScan()) { throw new IOException("Force scan and incremental scan can not be configured in pair for SAST. Configure either Incremental or Force scan option"); @@ -1411,6 +1432,7 @@ private CxScanConfig resolveConfiguration(Run run, DescriptorImpl descript ret.setSastEnabled(this.sastEnabled == null || sastEnabled); //for backward compatibility, assuming if sastEnabled is not set, then sast is enabled if (ret.isSastEnabled()) { + int presetId = parseInt(preset, log, "Invalid presetId: [%s]. Using default preset.", 0); ret.setPresetId(presetId); @@ -1724,6 +1746,7 @@ private void printConfiguration(CxScanConfig config, CxLoggerAdapter log) { log.info("post scan action: " + config.getPostScanActionId()); log.info("is force scan: " + config.getForceScan()); log.info("scan level custom fields: " + config.getCustomFields()); + log.info("overrideProjectSetting value: " + overrideProjectSetting); ScannerType scannerType = getDependencyScannerType(config); String dependencyScannerType = scannerType != null ? scannerType.getDisplayName() : "NONE"; @@ -1855,7 +1878,7 @@ private void writeJsonObjectToFile(Object jsonObj, File to, String description) } } - private void failTheBuild(Run run, CxScanConfig config, ScanResults ret) { + private void failTheBuild(Run run, CxScanConfig config, ScanResults ret) throws AbortException { //assert if expected exception is thrown OR when vulnerabilities under threshold OR when policy violated ScanSummary scanSummary = new ScanSummary(config, ret.getSastResults(), ret.getOsaResults(), ret.getScaResults()); if (scanSummary.hasErrors() || ret.getGeneralException() != null || @@ -1863,15 +1886,27 @@ private void failTheBuild(Run run, CxScanConfig config, ScanResults ret) { (ret.getOsaResults() != null && ret.getOsaResults().getException() != null) || (ret.getScaResults() != null && ret.getScaResults().getException() != null)) { printBuildFailure(scanSummary.toString(), ret, log); - if (resolvedVulnerabilityThresholdResult != null) { - run.setResult(resolvedVulnerabilityThresholdResult); - } + + String statusToReturn = ""; + String msgPrefix = ""; + if (!scanSummary.getThresholdErrors().isEmpty() || (config.getSastNewResultsThresholdEnabled() && scanSummary.isSastThresholdForNewResultsExceeded() ) ) { + resolvedVulnerabilityThresholdResult = resolvedVulnerabilityThresholdResult == null? + Result.fromString(JobStatusOnError.FAILURE.toString()): resolvedVulnerabilityThresholdResult; + run.setResult(resolvedVulnerabilityThresholdResult); + statusToReturn = resolvedVulnerabilityThresholdResult.toString(); + msgPrefix = "Threshold exceeded."; + }else { + msgPrefix = "Scan error occurred."; + statusToReturn = getReturnStatusOnError(getDescriptor()); + run.setResult(Result.fromString(statusToReturn)); + } + + if(JobStatusOnError.ABORTED.toString().equalsIgnoreCase(statusToReturn)) { + String msg = msgPrefix + "Job is configured to return ABORTED and stop the build/pipeline."; + log.warn(msg); + throw new AbortException(msg); + } - if (useUnstableOnError(getDescriptor())) { - run.setResult(Result.UNSTABLE); - } else { - run.setResult(Result.FAILURE); - } } } @@ -1971,6 +2006,18 @@ private boolean useUnstableOnError(final DescriptorImpl descriptor) { || (JobStatusOnError.GLOBAL.equals(getJobStatusOnError()) && JobGlobalStatusOnError.UNSTABLE.equals(descriptor .getJobGlobalStatusOnError())); } + + private String getReturnStatusOnError(final DescriptorImpl descriptor) { + + String status = JobStatusOnError.FAILURE.toString(); + + if (JobStatusOnError.GLOBAL.equals(getJobStatusOnError())) + status = descriptor.getJobGlobalStatusOnError().toString(); + else + status = getJobStatusOnError().toString(); + + return status; + } /** * Checks if job should fail with UNSTABLE status instead of FAILED diff --git a/src/main/java/com/checkmarx/jenkins/JobGlobalStatusOnError.java b/src/main/java/com/checkmarx/jenkins/JobGlobalStatusOnError.java index 423fe053..460addb9 100644 --- a/src/main/java/com/checkmarx/jenkins/JobGlobalStatusOnError.java +++ b/src/main/java/com/checkmarx/jenkins/JobGlobalStatusOnError.java @@ -2,7 +2,7 @@ public enum JobGlobalStatusOnError { - FAILURE("Failure"), UNSTABLE("Unstable"); + FAILURE("Failure"), UNSTABLE("Unstable"), ABORTED("ABORTED"); private final String displayName; diff --git a/src/main/java/com/checkmarx/jenkins/JobStatusOnError.java b/src/main/java/com/checkmarx/jenkins/JobStatusOnError.java index ca4f6a71..275afd62 100644 --- a/src/main/java/com/checkmarx/jenkins/JobStatusOnError.java +++ b/src/main/java/com/checkmarx/jenkins/JobStatusOnError.java @@ -1,7 +1,7 @@ package com.checkmarx.jenkins; public enum JobStatusOnError { - GLOBAL("Use Global Settings"), FAILURE("Failure"), UNSTABLE("Unstable"); + GLOBAL("Use Global Settings"), FAILURE("Failure"), UNSTABLE("Unstable"), ABORTED("ABORTED"); private final String displayName; diff --git a/src/main/java/com/checkmarx/jenkins/configascode/ConfigAsCode.java b/src/main/java/com/checkmarx/jenkins/configascode/ConfigAsCode.java index cd188d7a..c77d351e 100644 --- a/src/main/java/com/checkmarx/jenkins/configascode/ConfigAsCode.java +++ b/src/main/java/com/checkmarx/jenkins/configascode/ConfigAsCode.java @@ -4,7 +4,7 @@ public class ConfigAsCode { @Optional - private String project; + private ProjectConfig project; @Optional private String team; @Optional @@ -31,11 +31,11 @@ public void setSast(SastConfig sast) { this.sast = sast; } - public String getProject() { + public ProjectConfig getProject() { return project; } - public void setProject(String project) { + public void setProject(ProjectConfig project) { this.project = project; } diff --git a/src/main/java/com/checkmarx/jenkins/configascode/ProjectConfig.java b/src/main/java/com/checkmarx/jenkins/configascode/ProjectConfig.java new file mode 100644 index 00000000..7113d31e --- /dev/null +++ b/src/main/java/com/checkmarx/jenkins/configascode/ProjectConfig.java @@ -0,0 +1,29 @@ +package com.checkmarx.jenkins.configascode; + +import com.typesafe.config.Optional; + +public class ProjectConfig { + @Optional + private String fullPath; + @Optional + private String origin; + + public ProjectConfig() { + } + + public String getOrigin() { + return origin; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public String getFullPath() { + return fullPath; + } + + public void setFullPath(String fullPath) { + this.fullPath = fullPath; + } +} diff --git a/src/main/java/com/checkmarx/jenkins/configascode/SastConfig.java b/src/main/java/com/checkmarx/jenkins/configascode/SastConfig.java index 1defa9b9..50a6d866 100644 --- a/src/main/java/com/checkmarx/jenkins/configascode/SastConfig.java +++ b/src/main/java/com/checkmarx/jenkins/configascode/SastConfig.java @@ -21,7 +21,8 @@ public class SastConfig { private int medium; @Optional private int high; - + @Optional + private boolean overrideProjectSetting; public SastConfig() { } @@ -97,4 +98,12 @@ public boolean isPrivateScan() { public void setPrivateScan(boolean privateScan) { this.privateScan = privateScan; } + + public boolean isOverrideProjectSetting() { + return overrideProjectSetting; + } + + public void setOverrideProjectSetting(boolean isOverrideProjectSetting) { + this.overrideProjectSetting = isOverrideProjectSetting; + } } diff --git a/src/main/resources/com/checkmarx/jenkins/CxScanBuilder/config.jelly b/src/main/resources/com/checkmarx/jenkins/CxScanBuilder/config.jelly index e4ff382b..3514ccc1 100644 --- a/src/main/resources/com/checkmarx/jenkins/CxScanBuilder/config.jelly +++ b/src/main/resources/com/checkmarx/jenkins/CxScanBuilder/config.jelly @@ -47,7 +47,7 @@
The following fields will be overriding by config file values if exists (project name ,team name ,sast scan settings and sca scan settings.).
- + @@ -125,6 +125,7 @@ + diff --git a/src/main/resources/com/checkmarx/jenkins/CxScanBuilder/help-configAsCode.html b/src/main/resources/com/checkmarx/jenkins/CxScanBuilder/help-configAsCode.html new file mode 100644 index 00000000..42c584fa --- /dev/null +++ b/src/main/resources/com/checkmarx/jenkins/CxScanBuilder/help-configAsCode.html @@ -0,0 +1,3 @@ +
+ Create cx.config config-as-code input file at the root of the repo in '.checkmarx' folder. For example .checkmarx/cx.config +
\ No newline at end of file diff --git a/src/main/resources/com/checkmarx/jenkins/CxScanBuilder/help-overrideProjectSetting.html b/src/main/resources/com/checkmarx/jenkins/CxScanBuilder/help-overrideProjectSetting.html new file mode 100644 index 00000000..c154268f --- /dev/null +++ b/src/main/resources/com/checkmarx/jenkins/CxScanBuilder/help-overrideProjectSetting.html @@ -0,0 +1,3 @@ +
+ When selected, preset & engine configuration value selected in the pipeline will be saved on the CxSAST project. +
\ No newline at end of file diff --git a/src/main/resources/com/checkmarx/jenkins/cxconfig.xml b/src/main/resources/com/checkmarx/jenkins/cxconfig.xml index 04cc81dd..d001def1 100644 --- a/src/main/resources/com/checkmarx/jenkins/cxconfig.xml +++ b/src/main/resources/com/checkmarx/jenkins/cxconfig.xml @@ -14,7 +14,7 @@ !**/*.htmls, !**/*.ihtml, !**/*.mht, !**/*.mhtm, !**/*.mhtml, !**/*.ssi, !**/*.stm, !**/*.bin,!**/*.lock,!**/*.svg,!**/*.obj, !**/*.stml, !**/*.ttml, !**/*.txn, !**/*.xhtm, !**/*.xhtml, !**/*.class, !**/*.iml, !Checkmarx/Reports/*.*, - !OSADependencies.json, !**/node_modules/**/* + !OSADependencies.json, !**/node_modules/**/*, !**/.cxsca-results.json, !**/.cxsca-sast-results.json, !.checkmarx/cx.config *.zip, *.war, *.ear, *.tgz https://api-sca.checkmarx.net https://platform.checkmarx.net diff --git a/src/main/webapp/CxIcon24x24.png b/src/main/webapp/CxIcon24x24.png index 030caafa..cef8b1af 100644 Binary files a/src/main/webapp/CxIcon24x24.png and b/src/main/webapp/CxIcon24x24.png differ diff --git a/src/main/webapp/CxIcon48x48.png b/src/main/webapp/CxIcon48x48.png index 5b52cb4f..96bf29fa 100644 Binary files a/src/main/webapp/CxIcon48x48.png and b/src/main/webapp/CxIcon48x48.png differ