diff --git a/README.md b/README.md index 3871a509..29bf45c8 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,10 @@ The builder performs an analysis using one of the pre-defined Dependency-Check C ![builder configuration](https://raw.githubusercontent.com/jenkinsci/dependency-check-plugin/master/docs/images/builder-config.png) +With 9.0.0 dependency-check has moved from using the NVD data-feed to the NVD API. Users of dependency-check are highly encouraged to obtain an NVD API Key; see https://nvd.nist.gov/developers/request-an-api-key Without an NVD API Key dependency-check's updates will be extremely slow. Please see the documentation for the cli, maven, gradle, or ant integrations on how to set the NVD API key. + +The NVD API has enforced rate limits. If you are using a single API KEY and multiple builds occur you could hit the rate limit and receive 403 errors. In a CI environment one must use a caching strategy or an external database updated with a scheduled weekly job + #### Publisher The publisher works independently of the tool configuration or builder and is responsible for reading dependency-check-report.xml and generating metrics, trends, findings, and optionally failing the build or putting it into a warning state based on configurable thresholds. diff --git a/docs/images/builder-config.png b/docs/images/builder-config.png index 326f0628..32fcec79 100644 Binary files a/docs/images/builder-config.png and b/docs/images/builder-config.png differ diff --git a/pom.xml b/pom.xml index 8052a90e..f291a1d8 100644 --- a/pom.xml +++ b/pom.xml @@ -113,6 +113,10 @@ io.jenkins.plugins commons-lang3-api + + org.jenkins-ci.plugins + plain-credentials + org.apache.commons commons-digester3 diff --git a/src/main/java/org/jenkinsci/plugins/DependencyCheck/DependencyCheckToolBuilder.java b/src/main/java/org/jenkinsci/plugins/DependencyCheck/DependencyCheckToolBuilder.java index a0a0bbc3..1b821f2a 100644 --- a/src/main/java/org/jenkinsci/plugins/DependencyCheck/DependencyCheckToolBuilder.java +++ b/src/main/java/org/jenkinsci/plugins/DependencyCheck/DependencyCheckToolBuilder.java @@ -18,9 +18,11 @@ import static hudson.Util.fixEmptyAndTrim; import static hudson.Util.replaceMacro; import static hudson.util.QuotedStringTokenizer.tokenize; +import static org.apache.commons.lang3.StringUtils.trimToEmpty; import java.io.IOException; import java.io.Serializable; +import java.util.Collections; import java.util.List; import org.apache.commons.io.FileUtils; @@ -29,28 +31,51 @@ import org.jenkinsci.plugins.DependencyCheck.tools.DependencyCheckInstallation; import org.jenkinsci.plugins.DependencyCheck.tools.DependencyCheckInstaller; import org.jenkinsci.plugins.DependencyCheck.tools.Version; +import org.jenkinsci.plugins.plaincredentials.StringCredentials; +import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.verb.POST; +import org.springframework.security.core.Authentication; +import com.cloudbees.plugins.credentials.CredentialsMatcher; +import com.cloudbees.plugins.credentials.CredentialsMatchers; +import com.cloudbees.plugins.credentials.CredentialsProvider; +import com.cloudbees.plugins.credentials.common.StandardListBoxModel; +import com.cloudbees.plugins.credentials.common.StandardUsernameListBoxModel; + +import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.AbortException; import hudson.EnvVars; import hudson.Extension; import hudson.FilePath; import hudson.Launcher; +import hudson.Util; import hudson.XmlFile; import hudson.model.AbstractProject; import hudson.model.Cause; import hudson.model.Computer; +import hudson.model.Item; +import hudson.model.ItemGroup; import hudson.model.Node; +import hudson.model.Queue; import hudson.model.Result; import hudson.model.Run; import hudson.model.TaskListener; +import hudson.model.queue.Tasks; +import hudson.security.ACL; +import hudson.security.AccessControlled; +import hudson.security.Permission; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.Builder; import hudson.tools.InstallSourceProperty; import hudson.triggers.SCMTrigger; import hudson.util.ArgumentListBuilder; +import hudson.util.FormValidation; +import hudson.util.ListBoxModel; +import jenkins.model.Jenkins; import jenkins.tasks.SimpleBuildStep; /** @@ -65,6 +90,7 @@ public class DependencyCheckToolBuilder extends Builder implements SimpleBuildSt private final String odcInstallation; private String additionalArguments; + private String nvdCredentialsId; private boolean skipOnScmChange; private boolean skipOnUpstreamChange; private boolean stopBuild = false; @@ -192,8 +218,10 @@ private DependencyCheckInstallation getDependencyCheck() { return DependencyCheckUtil.getDependencyCheck(odcInstallation); } - protected ArgumentListBuilder buildArgumentList(@NonNull final String odcScript, @NonNull final Run build, - @NonNull final FilePath workspace, @NonNull final EnvVars env) { + protected ArgumentListBuilder buildArgumentList(@NonNull final String odcScript, + @NonNull final Run build, + @NonNull final FilePath workspace, + @NonNull final EnvVars env) throws AbortException { final ArgumentListBuilder cliArguments = new ArgumentListBuilder(odcScript); if (!StringUtils.contains(additionalArguments, "--project")) { cliArguments.add("--project", build.getFullDisplayName()); @@ -211,6 +239,13 @@ protected ArgumentListBuilder buildArgumentList(@NonNull final String odcScript, } } } + if (nvdCredentialsId != null) { + StringCredentials c = CredentialsProvider.findCredentialById(nvdCredentialsId, StringCredentials.class, build); + if (c == null) { + throw new AbortException(Messages.Builder_DescriptorImpl_invalidCredentialsId()); + } + cliArguments.add("--nvdApiKey").addMasked(c.getSecret()); + } return cliArguments; } @@ -244,6 +279,15 @@ private boolean isSkip(final Run build, final TaskListener listener) { return skip; } + public String getNvdCredentialsId() { + return nvdCredentialsId; + } + + @DataBoundSetter + public void setNvdCredentialsId(String nvdCredentialsId) { + this.nvdCredentialsId = Util.fixEmpty(nvdCredentialsId); + } + @Extension @Symbol({"dependencyCheck", "dependencycheck"}) public static class DependencyCheckToolBuilderDescriptor extends BuildStepDescriptor { @@ -260,6 +304,54 @@ public void purge() { FileUtils.deleteQuietly(globalConfig.getFile()); } + @POST + public FormValidation doCheckdoNvdCredentialsId(@CheckForNull @AncestorInPath Item projectOrFolder, + @QueryParameter String nvdCredentialsId) { + if ((projectOrFolder == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER)) || + (projectOrFolder != null && !projectOrFolder.hasPermission(Item.EXTENDED_READ) && !projectOrFolder.hasPermission(CredentialsProvider.USE_ITEM))) { + return FormValidation.ok(); + } + if (StringUtils.isBlank(nvdCredentialsId)) { + return FormValidation.warning(Messages.Builder_DescriptorImpl_emptyCredentialsId()); + } + + Authentication authentication = getAuthentication(projectOrFolder); + CredentialsMatcher matcher = CredentialsMatchers.withId(nvdCredentialsId); + if (CredentialsProvider.listCredentialsInItem(StringCredentials.class, projectOrFolder, authentication, null, matcher).isEmpty()) { + return FormValidation.error(Messages.Builder_DescriptorImpl_invalidCredentialsId()); + } + return FormValidation.ok(); + } + + @POST + public ListBoxModel doFillNvdCredentialsIdItems(final @CheckForNull @AncestorInPath ItemGroup context, + final @CheckForNull @AncestorInPath Item projectOrFolder, + @QueryParameter String nvdCredentialsId) { + Permission permToCheck = projectOrFolder == null ? Jenkins.ADMINISTER : Item.CONFIGURE; + AccessControlled contextToCheck = projectOrFolder == null ? Jenkins.get() : projectOrFolder; + + // If we're on the global page and we don't have administer + // permission or if we're in a project or folder + // and we don't have configure permission there + if (!contextToCheck.hasPermission(permToCheck)) { + return new StandardUsernameListBoxModel().includeCurrentValue(trimToEmpty(nvdCredentialsId)); + } + + Authentication authentication = getAuthentication(projectOrFolder); + CredentialsMatcher matcher = CredentialsMatchers.instanceOf(StringCredentials.class); + Class type = StringCredentials.class; + ItemGroup credentialsContext = context == null ? Jenkins.get() : context; + + return new StandardListBoxModel() // + .includeMatchingAs(authentication, credentialsContext, type, Collections.emptyList(), matcher) // + .includeEmptyValue(); + } + + @NonNull + protected Authentication getAuthentication(AccessControlled item) { + return item instanceof Queue.Task ? Tasks.getAuthenticationOf2((Queue.Task) item) : ACL.SYSTEM2; + } + @NonNull @Override public String getDisplayName() { diff --git a/src/main/resources/org/jenkinsci/plugins/DependencyCheck/DependencyCheckToolBuilder/config.jelly b/src/main/resources/org/jenkinsci/plugins/DependencyCheck/DependencyCheckToolBuilder/config.jelly index f4349d9b..c5e055b2 100644 --- a/src/main/resources/org/jenkinsci/plugins/DependencyCheck/DependencyCheckToolBuilder/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/DependencyCheck/DependencyCheckToolBuilder/config.jelly @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - + + + + + diff --git a/src/main/resources/org/jenkinsci/plugins/DependencyCheck/DependencyCheckToolBuilder/config.properties b/src/main/resources/org/jenkinsci/plugins/DependencyCheck/DependencyCheckToolBuilder/config.properties index d21748d8..c26c49d7 100644 --- a/src/main/resources/org/jenkinsci/plugins/DependencyCheck/DependencyCheckToolBuilder/config.properties +++ b/src/main/resources/org/jenkinsci/plugins/DependencyCheck/DependencyCheckToolBuilder/config.properties @@ -19,4 +19,5 @@ installation.notspecified=Please define a Dependency-Check installation in the J arguments=Arguments scm.skip=Skip if triggered by SCM changes upstream.skip=Skip if triggered by upstream changes -stopBuild.title=Stop current build when scanner return exception \ No newline at end of file +stopBuild.title=Stop current build when scanner return exception +nvdCredentialsId.title=NVD API Key (version 9 or later) \ No newline at end of file diff --git a/src/main/resources/org/jenkinsci/plugins/DependencyCheck/DependencyCheckToolBuilder/help-nvdCredentialsId.html b/src/main/resources/org/jenkinsci/plugins/DependencyCheck/DependencyCheckToolBuilder/help-nvdCredentialsId.html new file mode 100644 index 00000000..2c49cc38 --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/DependencyCheck/DependencyCheckToolBuilder/help-nvdCredentialsId.html @@ -0,0 +1,13 @@ +
+

+ With 9.0.0 dependency-check has moved from using the NVD data-feed to the NVD API.
+ Users of dependency-check are highly encouraged to obtain an NVD API Key; + see https://nvd.nist.gov/developers/request-an-api-key
+ Without an NVD API Key dependency-check's updates will be extremely slow. +

+

+ The NVD API Key, CI, and Rate Limiting
+ The NVD API has enforced rate limits. If you are using a single API KEY and multiple builds occur you could hit the rate limit and receive 403 errors.
+ In a CI environment one must use a caching strategy or use a set API KEY to use for different jobs. +

+
\ No newline at end of file diff --git a/src/main/resources/org/jenkinsci/plugins/DependencyCheck/Messages.properties b/src/main/resources/org/jenkinsci/plugins/DependencyCheck/Messages.properties index 745d2d02..2bd566b0 100755 --- a/src/main/resources/org/jenkinsci/plugins/DependencyCheck/Messages.properties +++ b/src/main/resources/org/jenkinsci/plugins/DependencyCheck/Messages.properties @@ -28,6 +28,8 @@ Builder.noExecutableFound=Couldn\u2019t find any executable in "{0}" Builder.Skip=Skipping Dependency-Check analysis Builder.noInstallationFound=No installation {0} found. Please define one in manager Jenkins. Builder.nodeOffline=Cannot get installation for node, since it is not online +Builder.DescriptorImpl.emptyCredentialsId=Credentials is required +Builder.DescriptorImpl.invalidCredentialsId=Current credentials does not exists Platform.unknown=Unknown OS name: {0} SystemTools.nodeNotAvailable=Node could be offline or there are no executor defined for Node {0}