Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Policy violations #122

Closed
wants to merge 48 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
daed450
Lombom @Value marks fields private and final
Aug 23, 2022
51ba145
Added PolicyViolation check to the "backend" of the plugin
Aug 25, 2022
cf79b3f
Added Policy Violations to the summary part of the plugin
Aug 26, 2022
16378e8
Added Policy Violations to the detailed/findings part of the plugin
Aug 30, 2022
96156c2
Added Policy Violations to Trend chart
Aug 30, 2022
af50174
Update the change log
Aug 30, 2022
bd35598
Fix NPE
Sep 1, 2022
5ddad7d
Correct CHANGELOG
Sep 1, 2022
d337ee0
Add separate thresholds for violations - fail, warn and info
Nov 23, 2022
17e83e3
Merge branch 'jenkinsci:master' into policy-violations
HagarJNode Jan 20, 2023
c12d7c6
Merge branch 'jenkinsci:master' into master
HagarJNode Jan 20, 2023
27af748
More info from failing sonar build fail
Jan 20, 2023
ad9d887
Test SONAR_ORGANIZATION using hardcoded sephiroth-j
Jan 20, 2023
ef54dc7
Revert
Jan 20, 2023
8507b29
Test SONAR_ORGANIZATION using hardcoded default
Jan 24, 2023
ae24bf2
Revert "Test SONAR_ORGANIZATION using hardcoded default"
Jan 24, 2023
e549947
Test https://stackoverflow.com/questions/64331408/decorating-the-pull…
Jan 24, 2023
414cc7e
Test https://docs.sonarqube.org/latest/analyzing-source-code/pull-req…
Jan 24, 2023
2b8a892
Revert "Test https://docs.sonarqube.org/latest/analyzing-source-code/…
Jan 24, 2023
4debc70
Revert "Test https://stackoverflow.com/questions/64331408/decorating-…
Jan 24, 2023
438f6f5
Debug info
Jan 26, 2023
11be31f
More debug info
Jan 26, 2023
657206d
As the SonarCube account most likely is Community it doesn't support …
Jan 26, 2023
3c530c5
Try using ' instead of "
Jan 26, 2023
5f75ce3
Merge branch 'policy-violations' into master
Jan 26, 2023
850e2d1
Merge branch 'jenkinsci_master' into master
Feb 2, 2023
e816aff
Merge pull request #2 from jenkinsci/master
HagarJNode Feb 5, 2023
def62a0
Merge pull request #3 from jenkinsci/master
HagarJNode Feb 5, 2023
4a98582
Merge pull request #4 from jenkinsci/master
HagarJNode Feb 22, 2023
8f848ff
Merge pull request #5 from jenkinsci/master
HagarJNode Feb 22, 2023
4e01c5c
Merge branch 'master' into policy-violations
Mar 13, 2023
6b9a9e5
Merge branch 'jenkinsci_master' into policy-violations
Mar 13, 2023
5b77307
Fix merge issues
Mar 13, 2023
8bfe861
Merge pull request #7 from jenkinsci/master
HagarJNode Apr 12, 2023
f297a60
Merge pull request #8 from jenkinsci/master
HagarJNode Apr 12, 2023
1f97b3f
Merge pull request #9 from jenkinsci/master
HagarJNode Apr 14, 2023
ed685a0
Merge pull request #10 from jenkinsci/master
HagarJNode Apr 14, 2023
25bf89f
Merge remote-tracking branch 'HagarJNode/master'
May 26, 2023
27bf1e8
Merge branch 'master' into policy-violations
May 26, 2023
26f9dc9
Merge pull request #12 from jenkinsci/master
HagarJNode Sep 20, 2023
3563c9f
Merge pull request #13 from jenkinsci/master
HagarJNode Sep 20, 2023
d4dfa3a
Merge remote-tracking branch 'HagarJNode/master' into policy-violations
Jan 23, 2024
23501ad
Fix merge issues
Jan 23, 2024
be0dd93
Merge remote-tracking branch 'HagarJNode/master' into policy-violations
Feb 2, 2024
c8be0ed
Update the getPolicyViolation method
Feb 2, 2024
f4d5533
Merge branch 'policy-violations'
Feb 2, 2024
70ea6ff
Merge remote-tracking branch 'HagarJNode/master' into policy-violations
Husted-1 Feb 16, 2024
07e683f
Merge branch 'policy-violations'
Husted-1 Feb 16, 2024
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
### 🐞 Bugs Fixed
- Searching on the result page was partially broken due to [a bug in bootstrap-vue 2.22+](https://github.com/bootstrap-vue/bootstrap-vue/issues/6967)

## v4.2.1 - 2022-09-01
### ⚠ Breaking
### ⭐ New Features
- Added Policy Violations to the plugin, so that they now act as vulnerabilities - that's they can make a build fail, if a policy is violated. Also added charts etc., so that it follow the looks of the rest of the plugin.

### 🐞 Bugs Fixed

## v4.2.0 - 2022-07-04
### ⚠ Breaking
### ⭐ New Features
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,7 @@
import okhttp3.Request;
import okhttp3.RequestBody;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.DependencyTrack.model.Finding;
import org.jenkinsci.plugins.DependencyTrack.model.Project;
import org.jenkinsci.plugins.DependencyTrack.model.Team;
import org.jenkinsci.plugins.DependencyTrack.model.UploadResult;
import org.jenkinsci.plugins.DependencyTrack.model.*;
import org.springframework.http.HttpStatus;
import org.springframework.retry.RetryPolicy;
import org.springframework.retry.backoff.UniformRandomBackOffPolicy;
Expand All @@ -60,6 +57,7 @@ public class ApiClient {
private static final String API_URL = "/api/v1";
static final String API_KEY_HEADER = "X-Api-Key";
static final String PROJECT_FINDINGS_URL = API_URL + "/finding/project";
static final String PROJECT_POLICY_VIOLATION_URL = API_URL + "/violation/project";
static final String BOM_URL = API_URL + "/bom";
static final String BOM_TOKEN_URL = BOM_URL + "/token";
static final String PROJECT_URL = API_URL + "/project";
Expand Down Expand Up @@ -264,6 +262,28 @@ public List<Finding> getFindings(@NonNull final String projectUuid) throws ApiCl
});
}

@NonNull
@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
public List<PolicyViolation> getPolicyViolation(@NonNull final String projectUuid) throws ApiClientException {
final var uri = UriComponentsBuilder.fromUriString(PROJECT_POLICY_VIOLATION_URL).pathSegment("{uuid}").build(projectUuid);
final var request = createRequest(uri);
return executeWithRetry(() -> {
try (var response = httpClient.newCall(request).execute()) {
final var body = response.body().string();
if (!response.isSuccessful()) {
final int status = response.code();
logger.log(body);
throw new ApiClientException(Messages.ApiClient_Error_RetrieveFindings(status, HttpStatus.valueOf(status).getReasonPhrase()));
}
return PolicyViolationsParser.parse(body);
} catch (ApiClientException e) {
throw e;
} catch (IOException e) {
throw new ApiClientException(Messages.ApiClient_Error_Connection(StringUtils.EMPTY, StringUtils.EMPTY), e);
}
});
}

@NonNull
@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
public UploadResult upload(@Nullable final String projectId, @Nullable final String projectName, @Nullable final String projectVersion, @NonNull final FilePath artifact,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,7 @@
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.DependencyTrack.model.Finding;
import org.jenkinsci.plugins.DependencyTrack.model.RiskGate;
import org.jenkinsci.plugins.DependencyTrack.model.SeverityDistribution;
import org.jenkinsci.plugins.DependencyTrack.model.Thresholds;
import org.jenkinsci.plugins.DependencyTrack.model.UploadResult;
import org.jenkinsci.plugins.DependencyTrack.model.Vulnerability;
import org.jenkinsci.plugins.DependencyTrack.model.*;
import org.jenkinsci.plugins.plaincredentials.StringCredentials;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
Expand Down Expand Up @@ -235,6 +230,67 @@ public final class DependencyTrackPublisher extends Recorder implements SimpleBu
*/
private Integer failedNewUnassigned;


/**
* Threshold level for total number of fail violations for job status FAILED
*/
private Integer failedTotalFail;

/**
* Threshold level for total number of warn violations for job status FAILED
*/
private Integer failedTotalWarn;

/**
* Threshold level for total number of info violations for job status FAILED
*/
private Integer failedTotalInfo;

/**
* Threshold level for total number of fail violations for job status UNSTABLE
*/
private Integer unstableTotalFail;

/**
* Threshold level for total number of warn violations for job status UNSTABLE
*/
private Integer unstableTotalWarn;

/**
* Threshold level for total number of info violations for job status UNSTABLE
*/
private Integer unstableTotalInfo;

/**
* Threshold level for new number of fail violations for job status FAILED
*/
private Integer failedNewFail;

/**
* Threshold level for new number of warn violations for job status FAILED
*/
private Integer failedNewWarn;

/**
* Threshold level for new number of info violations for job status FAILED
*/
private Integer failedNewInfo;

/**
* Threshold level for new number of fail violations for job status UNSTABLE
*/
private Integer unstableNewFail;

/**
* Threshold level for new number of warn violations for job status UNSTABLE
*/
private Integer unstableNewWarn;

/**
* Threshold level for new number of info violations for job status UNSTABLE
*/
private Integer unstableNewInfo;

@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
private transient ApiClientFactory clientFactory;
Expand Down Expand Up @@ -327,7 +383,12 @@ public void perform(@NonNull final Run<?, ?> run, @NonNull final FilePath worksp
}
}

private void publishAnalysisResult(final ConsoleLogger logger, final ApiClient apiClient, final String token, final Run<?, ?> build, final String effectiveProjectName, final String effectiveProjectVersion) throws InterruptedException, ApiClientException, AbortException {
private void publishAnalysisResult(final ConsoleLogger logger,
final ApiClient apiClient,
final String token,
final Run<?, ?> build,
final String effectiveProjectName,
final String effectiveProjectVersion) throws InterruptedException, ApiClientException, AbortException {
final long timeout = System.currentTimeMillis() + (60000L * getEffectivePollingTimeout());
final long interval = 1000L * getEffectivePollingInterval();
logger.log(Messages.Builder_Polling());
Expand All @@ -341,13 +402,35 @@ private void publishAnalysisResult(final ConsoleLogger logger, final ApiClient a
}
}
final String effectiveProjectId = lookupProjectId(logger, apiClient, effectiveProjectName, effectiveProjectVersion);

logger.log(Messages.Builder_Findings_Processing());

final List<Finding> findings = apiClient.getFindings(effectiveProjectId);
final SeverityDistribution severityDistribution = new SeverityDistribution(build.getNumber());
findings.stream().map(Finding::getVulnerability).map(Vulnerability::getSeverity).forEach(severityDistribution::add);
final ResultAction projectAction = new ResultAction(findings, severityDistribution);

logger.log(Messages.Builder_PolicyViolations_Processing());

final List<PolicyViolation> policyViolations = apiClient.getPolicyViolation(effectiveProjectId);
final ViolationDistribution violationDistribution = new ViolationDistribution(build.getNumber());

policyViolations
.stream()
.map(PolicyViolation::getPolicyCondition)
.map(PolicyCondition::getPolicy)
.map(Policy::getViolationState)
.forEach(violationDistribution::add);

final ResultAction projectAction =
new ResultAction(
findings,
severityDistribution,
policyViolations,
violationDistribution);

projectAction.setDependencyTrackUrl(getEffectiveFrontendUrl());
projectAction.setProjectId(effectiveProjectId);

build.addOrReplaceAction(projectAction);

// update ResultLinkAction with one that surely contains a projectId
Expand All @@ -356,23 +439,45 @@ private void publishAnalysisResult(final ConsoleLogger logger, final ApiClient a
linkAction.setProjectVersion(effectiveProjectVersion);
build.addOrReplaceAction(linkAction);

// Get previous results and evaluate to thresholds
final SeverityDistribution previousSeverityDistribution = Optional.ofNullable(getPreviousBuildWithAnalysisResult(build))
.map(previousBuild -> previousBuild.getAction(ResultAction.class))
.map(ResultAction::getSeverityDistribution)
.orElse(null);
final Optional<ResultAction> resultAction =
Optional.ofNullable(getPreviousBuildWithAnalysisResult(build))
.map(previousBuild -> previousBuild.getAction(ResultAction.class));

evaluateRiskGates(build, logger, severityDistribution, previousSeverityDistribution);
// Get previous results and evaluate to thresholds
final SeverityDistribution previousSeverityDistribution =
resultAction
.map(ResultAction::getSeverityDistribution)
.orElseGet(() -> new SeverityDistribution(0));

final ViolationDistribution previousViolationDistribution =
resultAction
.map(ResultAction::getViolationDistribution)
.orElseGet(() -> new ViolationDistribution(0));

evaluateRiskGates(
build,
logger,
severityDistribution,
previousSeverityDistribution,
violationDistribution,
previousViolationDistribution);
}

private void evaluateRiskGates(final Run<?, ?> build, final ConsoleLogger logger, final SeverityDistribution currentDistribution, final SeverityDistribution previousDistribution) throws AbortException {
if (previousDistribution != null) {
logger.log(Messages.Builder_Threshold_ComparingTo(previousDistribution.getBuildNumber()));
} else {
logger.log(Messages.Builder_Threshold_NoComparison());
}
final RiskGate riskGate = new RiskGate(getThresholds());
final Result result = riskGate.evaluate(currentDistribution, previousDistribution);
private void evaluateRiskGates(final Run<?, ?> build,
final ConsoleLogger logger,
final SeverityDistribution currentDistribution,
final SeverityDistribution previousDistribution,
final ViolationDistribution currentViolationDistribution,
final ViolationDistribution previousViolationDistribution) throws AbortException {
logger.log(Messages.Builder_Threshold_ComparingTo(previousDistribution.getBuildNumber()));
final RiskGate riskGate = new RiskGate(getThresholds());
final Result result =
riskGate
.evaluate(
currentDistribution,
previousDistribution,
currentViolationDistribution,
previousViolationDistribution);
if (result.isWorseOrEqualTo(Result.UNSTABLE) && result.isCompleteBuild()) {
logger.log(Messages.Builder_Threshold_Exceed());
// allow build to proceed, but mark overall build unstable
Expand Down Expand Up @@ -553,7 +658,22 @@ private Thresholds getThresholds() {
thresholds.newFindings.failedMedium = failedNewMedium;
thresholds.newFindings.failedLow = failedNewLow;
thresholds.newFindings.failedUnassigned = failedNewUnassigned;
return thresholds;

thresholds.totalViolations.unstableFail = unstableTotalFail;
thresholds.totalViolations.unstableWarn = unstableTotalWarn;
thresholds.totalViolations.unstableInfo = unstableTotalInfo;
thresholds.totalViolations.failedFail = failedTotalFail;
thresholds.totalViolations.failedWarn = failedTotalWarn;
thresholds.totalViolations.failedInfo = failedTotalInfo;

thresholds.newViolations.unstableFail = unstableNewFail;
thresholds.newViolations.unstableWarn = unstableNewWarn;
thresholds.newViolations.unstableInfo = unstableNewInfo;
thresholds.newViolations.failedFail = failedNewFail;
thresholds.newViolations.failedWarn = failedNewWarn;
thresholds.newViolations.failedInfo = failedNewInfo;

return thresholds;
}

private void updateProjectProperties(final ConsoleLogger logger, final ApiClient apiClient, final String effectiveProjectName, final String effectiveProjectVersion) throws ApiClientException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,12 @@ private FormValidation testConnection(final String dependencyTrackUrl, final Str
return FormValidation.error(Messages.Publisher_ConnectionTest_InputError());
}

private FormValidation checkTeamPermissions(final ApiClient apiClient, final String poweredBy, final boolean autoCreateProjects, final boolean synchronous, final boolean projectProperties) throws ApiClientException {
private FormValidation checkTeamPermissions(final ApiClient apiClient,
final String poweredBy,
final boolean autoCreateProjects,
final boolean synchronous,
final boolean projectProperties) throws ApiClientException
{
final Set<String> requiredPermissions = Stream.of(BOM_UPLOAD, VIEW_PORTFOLIO, VULNERABILITY_ANALYSIS).map(Enum::toString).collect(Collectors.toSet());
final Set<String> optionalPermissions = new HashSet<>();

Expand All @@ -295,6 +300,9 @@ private FormValidation checkTeamPermissions(final ApiClient apiClient, final Str
} else {
optionalPermissions.add(VIEW_VULNERABILITY.toString());
}

optionalPermissions.add(VIEW_POLICY_VIOLATION.toString());

if (projectProperties) {
requiredPermissions.add(PORTFOLIO_MANAGEMENT.toString());
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
import org.jenkinsci.plugins.DependencyTrack.model.Vulnerability;

@UtilityClass
class FindingParser {
class FindingParser
{

List<Finding> parse(final String jsonResponse) {
final JSONArray jsonArray = JSONArray.fromObject(jsonResponse);
Expand All @@ -49,22 +50,13 @@ List<Finding> parse(final String jsonResponse) {
}

private Finding parseFinding(JSONObject json) {
final Component component = parseComponent(json.getJSONObject("component"));
final Component component = ParserUtil.parseComponent(json.getJSONObject("component"));
final Vulnerability vulnerability = parseVulnerability(json.getJSONObject("vulnerability"));
final Analysis analysis = parseAnalysis(json.optJSONObject("analysis"));
final String matrix = getKeyOrNull(json, "matrix");
return new Finding(component, vulnerability, analysis, matrix);
}

private Component parseComponent(JSONObject json) {
final String uuid = getKeyOrNull(json, "uuid");
final String name = getKeyOrNull(json, "name");
final String group = getKeyOrNull(json, "group");
final String version = getKeyOrNull(json, "version");
final String purl = getKeyOrNull(json, "purl");
return new Component(uuid, name, group, version, purl);
}

private Vulnerability parseVulnerability(JSONObject json) {
final String uuid = getKeyOrNull(json, "uuid");
final String source = getKeyOrNull(json, "source");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import lombok.RequiredArgsConstructor;
import net.sf.json.JSONArray;
import org.jenkinsci.plugins.DependencyTrack.model.SeverityDistribution;
import org.jenkinsci.plugins.DependencyTrack.model.TrendDistribution;
import org.jenkinsci.plugins.DependencyTrack.model.ViolationDistribution;
import org.kohsuke.stapler.WebApp;
import org.kohsuke.stapler.bind.JavaScriptMethod;

Expand Down Expand Up @@ -59,14 +61,36 @@ public boolean isTrendVisible() {
* @return the UI model as JSON
*/
@JavaScriptMethod
public JSONArray getSeverityDistributionTrend() {
public JSONArray getTrendDistribution() {
project.checkPermission(hudson.model.Item.READ);
final List<SeverityDistribution> severityDistributions = project.getBuilds().stream()

final List<TrendDistribution> trendDistributions = project.getBuilds().stream()
.sorted(Comparator.naturalOrder())
.map(run -> run.getAction(ResultAction.class)).filter(Objects::nonNull)
.map(ResultAction::getSeverityDistribution)
.map(_resultAction ->
{
final SeverityDistribution severityDistribution = _resultAction.getSeverityDistribution();

final ViolationDistribution violationDistribution = _resultAction.getViolationDistribution() != null ?
_resultAction.getViolationDistribution() : new ViolationDistribution(severityDistribution.getBuildNumber());

return
TrendDistribution.of(severityDistribution.getBuildNumber())
.addInfo(severityDistribution.getInfo())
.addInfo(violationDistribution.getInfo())
.addUnassigned(severityDistribution.getUnassigned())
.addUnassigned(violationDistribution.getUnassigned())
.addCritical(severityDistribution.getCritical())
.addHigh(severityDistribution.getHigh())
.addMedium(severityDistribution.getMedium())
.addLow(severityDistribution.getLow())
.addFail(violationDistribution.getFail())
.addWarn(violationDistribution.getWarn());
}
)
.collect(Collectors.toList());
return JSONArray.fromObject(severityDistributions);

return JSONArray.fromObject(trendDistributions);
}

public String getBindUrl() {
Expand Down
Loading