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

Pr add break build thresholds #440

Merged
merged 11 commits into from
Oct 8, 2020
Merged

Conversation

AvivCx
Copy link
Contributor

@AvivCx AvivCx commented Oct 5, 2020

By submitting a PR to this repository, you agree to the terms within the Checkmarx Code of Conduct. Please see the contributing guidelines for how to create and submit a high-quality PR for this repo.

Description

Purpose of this PR is to add threshold logic for cxflow cli flows
Now user can configure thresholds to control break-build operations

References

Documentation guide: https://github.com/checkmarx-ltd/cx-flow/wiki/Thresholds-and-policies

Testing

Add automation voerage to cover cli flows in the following feature files:

  • thresholds.feature
  • sca-thresholds.feature

Checklist

  • I have added documentation for new/changed functionality in this PR (if applicable). If documentaiton is a Wiki Update, please indicate desired changes within PR MD Comment
  • All active GitHub checks for tests, formatting, and security are passing
  • The correct base branch is being used

private boolean checkIfBreakBuild(ScanRequest request, ScanResults results){
boolean breakBuildResult = false;

if(thresholdValidator.isThresholdsConfigurationExist(request)){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge the 2 if statements to 1 (like you do in the else if

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or make thresholdValidator.thresholdsExceeded test for isThresholdsConfigurationExist

Copy link
Contributor Author

@AvivCx AvivCx Oct 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually this is different case.
If thresholds not exist I want to go check the break build, but if exist and exceeded, shouldn't go to the 'else' flow
so 'exceeded' vs 'not exist' is a different result

@@ -335,6 +335,11 @@ private boolean requiresCxCustomFields(List<Field> fields) {
}

public boolean filteredIssuesPresent(ScanResults results) {
if(results.getAdditionalDetails()==null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try to a void 2 return statements.
ex.
return Optional.ofNullable(results.getAdditionalDetails())
.map(details -> details.get(Constants.SUMMARY_KEY))
.map(flow -> !flow.isEmpty())
.orElse(false);

if (!isMergeAllowed) {
log.info("Merge is not allowed, because some thresholds were exceeded.");
requestResult = new OperationResult(OperationStatus.FAILURE, MERGE_FAILURE_DESCRIPTION);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are not logging the flow of is merged is allowed here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added log

boolean sastThresholds;
boolean scaThresholds = false;

sastThresholds = scanRequest.getThresholds() != null || flowProperties.getThresholds() != null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please inline this statement

}

if (isAllowed && scaScanner.isEnabled()) {
isAllowed = isAllowedSca(scanResults, pullRequestReport);
isAllowed = isAllowedSca(scanResults, request);
}

return isAllowed;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks more readable to me:

boolean isSastAllowed = isSast() ? isAllowedSast(scanResults, request) : true;
boolean isScaAllowed = scaScanner.isEnabled() ?  isAllowedSca(scanResults, request) : true;
return isSastAllowed && isScaAllowed 

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result would be different. we want that if one of the scanner is enabled and its thresholds were exceeded - to return false (not allowed)
in your case, if SAST not enabled (only SCA), and SCA thresholds not exceeded - still we will return notAllowed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if SAST not enabled -> isSast() == false -> boolean isSastAllowed = true
SCA thresholds not exceeded -> scaScanner.isEnabled() == true -> isAllowedSca(scanResults, request) -> true
return isSastAllowed && isScaAllowed -> true && true -> true
still we will return notAllowed <- no as you can see we will return true

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right I changed that (and simplified per intelij suggestion)

@@ -507,6 +508,25 @@ private void processResults(ScanRequest request, ScanResults results) throws Exi
}
}

private boolean checkIfBreakBuild(ScanRequest request, ScanResults results){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd move this method into ThresholdValidator. E.g. ThresholdValidator.shouldInterruptBuild(...)
This way we won't need to expose the isThresholdsConfigurationExist method.

Copy link
Contributor Author

@AvivCx AvivCx Oct 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the ThresholdValidator shouldn't be aware of 'build', 'merge' or 'pull request' terminology
This is why I added 'isThresholdsConfigurationExist' as a generic check

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ThresholdValidator is already aware of 'merge' and 'pull request' - see ThresholdValidatorImpl#isMergeAllowed. isMergeAllowed is reused in several services. I don't see any problem with this.
Adding shouldInterruptBuild to ThresholdValidator will put 2 similar checks next to each other. This will make the code more consistent.

The real problem with ThresholdValidator is that it does 2 things at once: validates both SAST and SCA results. There are "parallel" methods for each of them, e.g.

failSastPrIfResultHasAnyFindings
failScaPrIfResultHasAnyFindings

getSastFindingCountPerSeverity
getScaFindingsCountsPerSeverity

isAllowedSast
isAllowedSca

isAnySastThresholdExceeded
isAnyScaThresholdsExceeded

This is not good code design. The SAST and SCA functionalities should be separated.

Comment on lines +515 to +516
if(thresholdValidator.thresholdsExceeded(request, results)){
log.info("Fail build on Thresholds exceeded.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to log what were the actual results and what were the thresholds so the user will understand why we failed the build? (like we do on the --web flow).
Moreover, you can set both 'if' statements inline..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is logged inside the ThresholdValidator.
regarding 'if' statement see 1st Ofer comment

breakBuildResult = true;
}
} else if(flowProperties.isBreakBuild() && resultsService.filteredIssuesPresent(results)){
log.info("Fail build on issues presented after executing filters");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand this message...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If thresholds not exist, the logic is to fail the build if there were left vulnerabilities after the processing
https://github.com/checkmarx-ltd/cx-flow/wiki/Thresholds-and-policies#thresholds-vs-basic-filters

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something like: "Build failed, because some issues were found"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

Comment on lines -118 to +145
log.info(isAllowedScanner ? "Merge is allowed, because no thresholds were exceeded." :
"Merge is not allowed, because some of the thresholds were exceeded.");
log.info(isAllowedScanner ? "No thresholds were exceeded." :
"Thresholds were exceeded.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's wrong with the original detailed message?

Copy link
Contributor Author

@AvivCx AvivCx Oct 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ThresholdValidator check the thresholds logic, and shouldn't be aware of 'build', 'merge' or 'pull request'.
Moreover in CLI flow, the original message is wrong

There are still places in ThresholdValidator with such references, but the refactoring was too dramatic for this stage

@@ -507,6 +508,25 @@ private void processResults(ScanRequest request, ScanResults results) throws Exi
}
}

private boolean checkIfBreakBuild(ScanRequest request, ScanResults results){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ThresholdValidator is already aware of 'merge' and 'pull request' - see ThresholdValidatorImpl#isMergeAllowed. isMergeAllowed is reused in several services. I don't see any problem with this.
Adding shouldInterruptBuild to ThresholdValidator will put 2 similar checks next to each other. This will make the code more consistent.

The real problem with ThresholdValidator is that it does 2 things at once: validates both SAST and SCA results. There are "parallel" methods for each of them, e.g.

failSastPrIfResultHasAnyFindings
failScaPrIfResultHasAnyFindings

getSastFindingCountPerSeverity
getScaFindingsCountsPerSeverity

isAllowedSast
isAllowedSca

isAnySastThresholdExceeded
isAnyScaThresholdsExceeded

This is not good code design. The SAST and SCA functionalities should be separated.

breakBuildResult = true;
}
} else if(flowProperties.isBreakBuild() && resultsService.filteredIssuesPresent(results)){
log.info("Fail build on issues presented after executing filters");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something like: "Build failed, because some issues were found"?

// assuming SCA results only
return false;
}

Map<String, Integer> flow = (Map<String, Integer>) results.getAdditionalDetails().get(Constants.SUMMARY_KEY);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'flow' variable name is unclear. It should be called something like 'findingCountPerSeverity'.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

@@ -335,6 +335,11 @@ private boolean requiresCxCustomFields(List<Field> fields) {
}

public boolean filteredIssuesPresent(ScanResults results) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we only count SAST issues here, then the method should be called filteredSastIssuesPresent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

// assuming SCA results only
return false;
}

Map<String, Integer> flow = (Map<String, Integer>) results.getAdditionalDetails().get(Constants.SUMMARY_KEY);
return flow == null || !flow.isEmpty();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the case when the summary has 0 as a value? e.g. {high:0, medium:0}
In this case filteredIssuesPresent should return false.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in this case !flow.isEmpty() will return false (not present)

NimrodGolan
NimrodGolan previously approved these changes Oct 7, 2020
testContext.setCxFlowExecutionException(exception);
}

@And("cxflow should exit with {}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sould be {int} and not {}

@sonarcloud
Copy link

sonarcloud bot commented Oct 8, 2020

Kudos, SonarCloud Quality Gate passed!

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities (and Security Hotspot 0 Security Hotspots to review)
Code Smell A 1 Code Smell

0.0% 0.0% Coverage
0.0% 0.0% Duplication

warning The version of Java (1.8.0_265) you have used to run this analysis is deprecated and we will stop accepting it from October 2020. Please update to at least Java 11.
Read more here

Map<String, Integer> flow = (Map<String, Integer>) results.getAdditionalDetails().get(Constants.SUMMARY_KEY);
return flow == null || !flow.isEmpty();
Map<String, Integer> findingCountPerSeverity = (Map<String, Integer>) results.getAdditionalDetails().get(Constants.SUMMARY_KEY);
return findingCountPerSeverity == null || !findingCountPerSeverity.isEmpty();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If e.g. findingCountPerSeverity has a single 'HIGH' key with the value 0, then findingCountPerSeverity.isEmpty() will return false. The result of filteredSastIssuesPresent() will then be 'true'. However, in fact no issues are present.

@AvivCx AvivCx merged commit 9c37df6 into develop Oct 8, 2020
@AvivCx AvivCx deleted the pr-Add-break-build-thresholds branch November 16, 2020 06:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants