From 48074be7e7a097129c167be4dd25e664475192cb Mon Sep 17 00:00:00 2001 From: Ruud Senden <8635138+rsenden@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:13:46 +0200 Subject: [PATCH] chore: Action updates --- .../action/helper/ActionDescriptor.java | 2 + .../common/action/helper/ActionRunner.java | 11 +- .../action/helper/ActionSpelFunctions.java | 18 +- .../common/json/JSONDateTimeConverter.java | 2 +- .../fortify/cli/common/util/StringUtils.java | 4 + .../cli/fod/actions/zip/release-summary.yaml | 57 +++++ .../ssc/actions/zip/appversion-summary.yaml | 197 ++++++++++++++++++ 7 files changed, 287 insertions(+), 4 deletions(-) create mode 100644 fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/release-summary.yaml create mode 100644 fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/appversion-summary.yaml diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionDescriptor.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionDescriptor.java index dad1b4f23b..f95523ae8f 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionDescriptor.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionDescriptor.java @@ -327,6 +327,8 @@ public static final class ActionStepForEachDescriptor implements IActionIfSuppli /** Processor that runs the forEach steps. This expression must evaluate to an * IActionStepForEachProcessor instance. */ private SimpleExpression processor; + /** Values to iterate over */ + private SimpleExpression values; /** Optional if-expression, executing steps only if condition evaluates to true */ @JsonProperty("if") private SimpleExpression _if; /** Optional break-expression, terminating forEach if condition evaluates to true */ diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionRunner.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionRunner.java index e31809b918..eedec5f3ca 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionRunner.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionRunner.java @@ -451,8 +451,15 @@ private void processThrowStep(TemplateExpression message) { } private void processForEachStep(ActionStepForEachDescriptor forEach) { - spelEvaluator.evaluate(forEach.getProcessor(), localData, IActionStepForEachProcessor.class) - .process(node->processForEachStepNode(forEach, node)); + var processor = forEach.getProcessor(); + var values = forEach.getValues(); + if ( processor!=null ) { + spelEvaluator.evaluate(processor, localData, IActionStepForEachProcessor.class) + .process(node->processForEachStepNode(forEach, node)); + } else if ( values!=null ) { + spelEvaluator.evaluate(values, localData, ArrayNode.class) + .forEach(value->processForEachStepNode(forEach, value)); + } } private boolean processForEachStepNode(ActionStepForEachDescriptor forEach, JsonNode node) { diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionSpelFunctions.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionSpelFunctions.java index eae9122f72..245a3571a1 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionSpelFunctions.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionSpelFunctions.java @@ -84,6 +84,15 @@ public static final String abbreviate(String text, int maxWidth) { return StringUtils.abbreviate(text, maxWidth); } + public static final String repeat(String text, int count) { + if ( count<0 ) { return ""; } + StringBuilder sb = new StringBuilder(); + for ( int i=0; i:[:]" + type: release_single + +steps: + - set: + # Add short alias for release object + - name: r + value: ${parameters.release} + - name: dateFmt + value: YYYY-MM-dd HH:mm + - write: + - to: ${parameters.file} + valueTemplate: summary-md + - if: parameters.file!='stdout' + to: stdout + value: | + Output written to ${parameters.file} + +valueTemplates: + - name: summary-md + contents: | + # Fortify on Demand Release Summary + + ## [${r.applicationName}${#isNotBlank(r.microserviceNae)?'- '+r.microserviceName:''} - ${r.releaseName}](${#fod.releaseBrowserUrl(r)}) + + Summary generated on: ${#formatDateTime(dateFmt)} + + ### Security Policy + **Rating:** ${#repeat("★", r.rating)}${#repeat("☆", 5-r.rating)} + **Status:** ${r.isPassed?'Pass':'Fail'} + + ### Issue Counts + | Type | Last Scan Date | Critical | High | Medium | Low | + | ----------- | ---------------- | -------- | -------- | -------- | -------- | + ${ + #isNotBlank(r.staticScanDate)? '| **Static** | '+#formatDateTime(dateFmt, r.staticScanDate) +' | '+#fmt(r.staticCritical, '%8s')+' | '+#fmt(r.staticHigh, '%8s')+' | '+#fmt(r.staticMedium, '%8s')+' | '+#fmt(r.staticLow, '%8s')+' |\n':'' + +#isNotBlank(r.dynamicScanDate)?'| **Dynamic** | '+#formatDateTime(dateFmt, r.dynamicScanDate)+' | '+#fmt(r.dynamicCritical, '%8s')+' | '+#fmt(r.dynamicHigh, '%8s')+' | '+#fmt(r.dynamicMedium, '%8s')+' | '+#fmt(r.dynamicLow, '%8s')+' |\n':'' + +#isNotBlank(r.mobileScanDate)? '| **Mobile** | '+#formatDateTime(dateFmt, r.mobileScanDate) +' | '+#fmt(r.mobileCritical, '%8s')+' | '+#fmt(r.mobileHigh, '%8s')+' | '+#fmt(r.mobileMedium, '%8s')+' | '+#fmt(r.mobileLow, '%8s')+' |\n':'' + } + \ No newline at end of file diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/appversion-summary.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/appversion-summary.yaml new file mode 100644 index 0000000000..fe94cfbff4 --- /dev/null +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/appversion-summary.yaml @@ -0,0 +1,197 @@ +usage: + header: Generate application version summary. + description: | + This action generates a short summary listing issue counts and other statistics + for a given application version. + +defaults: + requestTarget: ssc + +parameters: + - name: file + cliAliases: f + description: "Optional output file name (or 'stdout' / 'stderr'). Default value: stdout" + required: false + defaultValue: stdout + - name: appversion + cliAliases: av + description: "Required application version id or :" + type: appversion_single + - name: filtersets + cliAliases: fs + description: "Filter set names or guid's to display in the summary. If not specified, all filter sets will be included." + required: false + +steps: + - progress: Loading issue selector sets + - requests: + - name: issueSelectorSet + uri: /api/v1/projectVersions/${parameters.appversion.id}/issueSelectorSet?fields=filterBySet + - forEach: + values: issueSelectorSet.filterBySet.^[displayName=='Analysis Type'].selectorOptions + name: analysisType + do: + - progress: Loading ${analysisType.displayName} data + - requests: + - name: artifacts + uri: /api/v1/projectVersions/${parameters.appversion.id}/artifacts + type: paged + query: + embed: scans + forEach: + name: artifact + breakIf: lastScans!=null && lastScans[analysisType.guid]!=null + do: + - set: + - name: lastScans + value: "${{analysisType.guid: artifact._embed.scans?.^[type==#root.analysisType.guid]}}" + operation: merge + - requests: + - name: issueGroups + uri: /api/v1/projectVersions/${parameters.appversion.id}/issueGroups + query: + qm: issues + groupingtype: FOLDER + filter: ISSUE[11111111-1111-1111-1111-111111111151]:${analysisType.guid} + type: paged + forEach: + name: folder + do: + - set: + - name: issueCounts2 + value: "${{analysisType.displayName: folder}}" + operation: append + - write: + - to: stdout + value: ${issueCounts2} +# - progress: Processing issue data +# - requests: +# - name: issues +# uri: /api/v1/projectVersions/${parameters.appversion.id}/issues?limit=100 +# query: +# filter: ISSUE[11111111-1111-1111-1111-111111111151]:SCA +# filterset: ${parameters.filterset.guid} +# pagingProgress: +# postPageProcess: Processed ${totalIssueCount?:0} of ${issues_raw.count} issues +# forEach: +# name: issue +# embed: +# - name: details +# uri: /api/v1/issueDetails/${issue.id} +# do: +# - set: +# - name: ruleCategories +# operation: merge +# - name: results +# operation: append +# - progress: Processing rule data +# - forEach: +# processor: "#ssc.ruleDescriptionsProcessor(parameters.appversion.id)" +# name: rule +# do: +# - set: +# - if: "#isNotBlank(ruleCategories[rule.id])" +# name: rules +# operation: append +# - write: +# - to: ${parameters.file} +# valueTemplate: github-sast-report +# - if: parameters.file!='stdout' +# to: stdout +# value: | +# Output written to ${parameters.file} + +valueTemplates: + - name: ruleCategories + contents: "${{issue.primaryRuleGuid: issue.issueName}}" + + - name: github-sast-report + contents: + "$schema": https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json + version: '2.1.0' + runs: + - tool: + driver: + name: 'Fortify SCA' + version: ${lastStaticScan?.engineVersion?:'unknown'} + rules: ${rules?:{}} + properties: + copyright: ${#copyright()} + applicationName: ${parameters.appversion.project.name} + applicationId: ${parameters.appversion.project.id} + versionName: ${parameters.appversion.name} + versionId: ${parameters.appversion.id} + results: ${results?:{}} + + - name: rules + contents: + id: ${rule.id} + shortDescription: + text: ${ruleCategories[rule.id]} + fullDescription: + text: | + ## ${ruleCategories[rule.id]} + + ${rule.abstract} + help: + text: | + ${rule.explanation?:'No explanation available'} + + ## Recommendations + + ${rule.recommendations?:'Not available'} + + ## Tips + + ${#join('\n\n', rule.tips)?:'Not available'} + + ## References + + ${#numberedList(rule.references.![title + +(#isNotBlank(publisher)?", "+publisher:"") + +(#isNotBlank(author)?", "+author:"") + +(#isNotBlank(source)?", "+source:"")])?:'Not available'} + + ${#copyright()} + + - name: results + contents: + ruleId: ${issue.primaryRuleGuid} + message: + text: ${issue.details?.brief} [More information](${#ssc.issueBrowserUrl(issue,parameters.filterset)}) + level: ${(issue.friority matches "(Critical|High)") ? "warning":"note" } + partialFingerprints: + issueInstanceId: ${issue.issueInstanceId} + locations: + - physicalLocation: + artifactLocation: + uri: ${issue.fullFileName} + region: + startLine: ${issue.lineNumber==0||issue.lineNumber==null?1:issue.lineNumber} + endLine: ${issue.lineNumber==0||issue.lineNumber==null?1:issue.lineNumber} + startColumn: ${1} # Needs to be specified as an expression in order to end up as integer instead of string in JSON + endColumn: ${80} + codeFlows: |- + ${ + issue.details?.traceNodes==null ? {} + : + {{ + threadFlows: issue.details?.traceNodes.![{ + locations: #this.![{ + location: { + message: { + text: text + }, + physicalLocation: { + artifactLocation: { + uri: fullPath + }, + region: { + startLine: line==0||line==null?1:line + } + } + } + }] + }] + }} + } \ No newline at end of file