Skip to content

Commit

Permalink
[JENKINS-72557] Add ability to configure NVD API Key for OWSP depende…
Browse files Browse the repository at this point in the history
…ncy-check library
  • Loading branch information
nfalco79 committed Feb 25, 2024
1 parent 0ce9650 commit dc2b961
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 4 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Binary file modified docs/images/builder-config.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@
<groupId>io.jenkins.plugins</groupId>
<artifactId>commons-lang3-api</artifactId>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plain-credentials</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-digester3</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/**
Expand All @@ -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;
Expand Down Expand Up @@ -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());
Expand All @@ -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;
}

Expand Down Expand Up @@ -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<Builder> {
Expand All @@ -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<StringCredentials> 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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<?jelly escape-by-default="true"?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:c="/lib/credentials">
<f:entry title="${%installation.title}" description="${%installation.description}">
<select class="setting-input" name="_.odcInstallation">
<j:forEach var="inst" items="${descriptor.installations}">
Expand All @@ -23,6 +23,10 @@ limitations under the License.
</select>
</f:entry>

<f:entry title="${%nvdCredentialsId.title}" field="nvdCredentialsId">
<c:select />
</f:entry>

<f:entry title="${%arguments}" field="additionalArguments">
<f:textarea/>
</f:entry>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
stopBuild.title=Stop current build when scanner return exception
nvdCredentialsId.title=NVD API Key (version 9 or later)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<div>
<p>
With 9.0.0 dependency-check has moved from using the NVD data-feed to the NVD API.<br>
Users of dependency-check are <b>highly</b> encouraged to obtain an NVD API Key;
see https://nvd.nist.gov/developers/request-an-api-key<br>
Without an NVD API Key dependency-check's updates will be <b>extremely slow</b>.
</p>
<p>
<b>The NVD API Key, CI, and Rate Limiting</b><br>
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.<br>
<u>In a CI environment one must use a caching strategy or use a set API KEY to use for different jobs.</u>
</p>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down

0 comments on commit dc2b961

Please sign in to comment.