diff --git a/build-image/jfrog-scanning/action.yaml b/build-image/jfrog-scanning/action.yaml index 93e9e685..5473b684 100644 --- a/build-image/jfrog-scanning/action.yaml +++ b/build-image/jfrog-scanning/action.yaml @@ -54,6 +54,19 @@ runs: JF_USER: ${{ inputs.jfrog-username }} JF_PASSWORD: ${{ inputs.jfrog-password }} + - name: Prepare JFrog report paths + id: report_paths + shell: bash + run: | + set -euo pipefail + + TIMESTAMP="$(date +"%Y-%m-%d_%H-%M-%S")" + FULL_REPORT="$GITHUB_WORKSPACE/jfrog-scan-full.json" + TABLE_REPORT="$GITHUB_WORKSPACE/jfrog-scan-table-${TIMESTAMP}.txt" + + echo "full_report_file=$FULL_REPORT" >> "$GITHUB_OUTPUT" + echo "table_report_file=$TABLE_REPORT" >> "$GITHUB_OUTPUT" + - name: Check for existing dist zip id: check_dist shell: bash @@ -126,31 +139,55 @@ runs: ${{ inputs.build-server-arguments }} - name: Run the JFrog scan - if: steps.check_dist.outputs.dist_exists == 'true' || steps.check_task.outputs.appDistZip == 'true' id: scan + if: steps.check_dist.outputs.dist_exists == 'true' || steps.check_task.outputs.appDistZip == 'true' shell: bash working-directory: ${{ inputs.working-directory }}/${{ inputs.server-path }} run: | - set -euo pipefail + set -uo pipefail - TIMESTAMP="$(date +"%Y-%m-%d_%H-%M-%S")" - FULL_REPORT="$GITHUB_WORKSPACE/jfrog-scan-full.json" - TABLE_REPORT="$GITHUB_WORKSPACE/jfrog-scan-table-${TIMESTAMP}.txt" + FULL_REPORT="${{ steps.report_paths.outputs.full_report_file }}" + TABLE_REPORT="${{ steps.report_paths.outputs.table_report_file }}" shopt -s nullglob zips=(*/build/dist/*.zip) if [ "${#zips[@]}" -eq 0 ]; then echo "::error::No distribution ZIP files were found for JFrog scanning." - exit 1 + + printf '%s\n' \ + '{"error":"No distribution ZIP files were found","vulnerabilities":[]}' \ + > "$FULL_REPORT" + + { + printf 'CVE\tSeverity\tComponent\tFixed Versions\n' + printf '%s\n' '--- --- --- ---' + } > "$TABLE_REPORT" + + echo "scan_exit_code=1" >> "$GITHUB_OUTPUT" + exit 0 fi + set +e + jf s \ --licenses \ --vuln \ --format=json \ "${zips[@]}" > "$FULL_REPORT" + JFROG_EXIT_CODE=$? + + set -e + + echo "scan_exit_code=$JFROG_EXIT_CODE" >> "$GITHUB_OUTPUT" + + if [ ! -s "$FULL_REPORT" ]; then + printf '%s\n' \ + "{\"error\":\"JFrog scan failed\",\"exit_code\":$JFROG_EXIT_CODE,\"vulnerabilities\":[]}" \ + > "$FULL_REPORT" + fi + if ! jq -r ' def roots: if type == "array" then @@ -181,44 +218,55 @@ runs: { printf 'CVE\tSeverity\tComponent\tFixed Versions\n' printf '%s\n' '--- --- --- ---' + printf 'N/A\tERROR\tJFrog scan failed\tN/A\n' } > "$TABLE_REPORT" fi - echo "full_report_file=$FULL_REPORT" >> "$GITHUB_OUTPUT" - echo "table_report_file=$TABLE_REPORT" >> "$GITHUB_OUTPUT" + exit 0 - name: Create markdown summary - if: steps.scan.outcome == 'success' + if: always() && steps.scan.outputs.scan_exit_code == '0' uses: genesislcap/appdev-workflows/build-image/json-to-md@develop with: - json_path: ${{ steps.scan.outputs.full_report_file }} + json_path: ${{ steps.report_paths.outputs.full_report_file }} - name: Upload JFrog full scan report - if: always() && steps.scan.outputs.full_report_file != '' + if: always() uses: actions/upload-artifact@v4 with: - name: xray-full-report--${{ inputs.app-name }}-${{ github.run_number }} - path: ${{ steps.scan.outputs.full_report_file }} - if-no-files-found: error + name: xray-full-report-${{ inputs.app-name }}-${{ github.run_number }} + path: ${{ steps.report_paths.outputs.full_report_file }} + if-no-files-found: warn - name: Upload JFrog scan table report - if: always() && steps.scan.outputs.table_report_file != '' + if: always() uses: actions/upload-artifact@v4 with: - name: xray-dependencies-scan--${{ inputs.app-name }}-${{ github.run_number }} - path: ${{ steps.scan.outputs.table_report_file }} - if-no-files-found: error + name: xray-dependencies-scan-${{ inputs.app-name }}-${{ github.run_number }} + path: ${{ steps.report_paths.outputs.table_report_file }} + if-no-files-found: warn - name: Evaluate JFrog vulnerability threshold - if: steps.scan.outcome == 'success' + if: always() shell: bash run: | set -euo pipefail - FULL_REPORT="${{ steps.scan.outputs.full_report_file }}" + FULL_REPORT="${{ steps.report_paths.outputs.full_report_file }}" + SCAN_EXIT_CODE="${{ steps.scan.outputs.scan_exit_code }}" FAIL_BUILD="${{ inputs.fail-build }}" - if [ -z "$FULL_REPORT" ] || [ ! -f "$FULL_REPORT" ]; then + if [ -z "$SCAN_EXIT_CODE" ]; then + echo "::error::JFrog scan did not execute." + exit 1 + fi + + if [ "$SCAN_EXIT_CODE" -ne 0 ]; then + echo "::error::JFrog scan failed with exit code $SCAN_EXIT_CODE." + exit 1 + fi + + if [ ! -s "$FULL_REPORT" ]; then echo "::error::JFrog scan report was not generated." exit 1 fi @@ -256,7 +304,7 @@ runs: exit 1 fi - echo "::warning::JFrog Xray found $HIGH_CRITICAL_FINDINGS High or Critical vulnerabilities. Blocking is disabled for this application." + echo "::warning::JFrog Xray found $HIGH_CRITICAL_FINDINGS High or Critical vulnerabilities. Blocking is disabled." exit 0 fi diff --git a/build-image/trivy-scanning/action.yaml b/build-image/trivy-scanning/action.yaml index 33270d6d..ace2a0fe 100644 --- a/build-image/trivy-scanning/action.yaml +++ b/build-image/trivy-scanning/action.yaml @@ -34,15 +34,15 @@ inputs: outputs: json_path: description: Path to the Trivy JSON report - value: ${{ steps.scan.outputs.json_path }} + value: ${{ steps.meta.outputs.json_file }} html_path: description: Path to the Trivy HTML report - value: ${{ steps.scan.outputs.html_path }} + value: ${{ steps.meta.outputs.html_file }} markdown_path: description: Path to the Trivy Markdown report - value: ${{ steps.scan.outputs.markdown_path }} + value: ${{ steps.meta.outputs.md_file }} threshold_findings: description: Number of vulnerabilities matching the configured threshold @@ -65,7 +65,6 @@ runs: sh -s -- -b "$TRIVY_BIN_DIR" echo "$TRIVY_BIN_DIR" >> "$GITHUB_PATH" - "$TRIVY_BIN_DIR/trivy" --version - name: Prepare report paths @@ -104,12 +103,11 @@ runs: id: scan shell: bash run: | - set -euo pipefail + set -uo pipefail TRIVY_BIN="$RUNNER_TEMP/trivy-bin/trivy" JSON_FILE="${{ steps.meta.outputs.json_file }}" HTML_FILE="${{ steps.meta.outputs.html_file }}" - MD_FILE="${{ steps.meta.outputs.md_file }}" IMAGE_REF="${{ inputs.image-ref }}" trivy_args=( @@ -125,29 +123,71 @@ runs: trivy_args+=("$IMAGE_REF") + set +e "$TRIVY_BIN" "${trivy_args[@]}" + TRIVY_EXIT_CODE=$? + set -e + + echo "scan_exit_code=$TRIVY_EXIT_CODE" >> "$GITHUB_OUTPUT" + + if [ ! -s "$JSON_FILE" ]; then + cat > "$JSON_FILE" < "$HTML_FILE" < + + + + Trivy scan report + + +

Trivy scan report

+

Image: $IMAGE_REF

+

HTML report generation failed.

+

See the JSON artifact for complete scan output.

+ + + EOF + fi - echo "json_path=$JSON_FILE" >> "$GITHUB_OUTPUT" - echo "html_path=$HTML_FILE" >> "$GITHUB_OUTPUT" - echo "markdown_path=$MD_FILE" >> "$GITHUB_OUTPUT" + exit 0 - name: Create markdown summary + if: always() shell: bash run: | set -euo pipefail - JSON_FILE="${{ steps.scan.outputs.json_path }}" - MD_FILE="${{ steps.scan.outputs.markdown_path }}" + JSON_FILE="${{ steps.meta.outputs.json_file }}" + MD_FILE="${{ steps.meta.outputs.md_file }}" IMAGE_REF="${{ inputs.image-ref }}" SEVERITY_THRESHOLD="${{ inputs.severity-threshold }}" - if [ ! -f "$JSON_FILE" ]; then - echo "::error::Trivy JSON report not found: $JSON_FILE" - exit 1 + if [ ! -s "$JSON_FILE" ]; then + { + printf '# Trivy Scan Summary\n\n' + printf 'Image: `%s`\n\n' "$IMAGE_REF" + printf 'Trivy did not generate a JSON report.\n' + } > "$MD_FILE" + + exit 0 fi jq_count() { @@ -195,47 +235,57 @@ runs: printf '%s\n' '---' >> "$MD_FILE" - echo "Wrote markdown report to $MD_FILE" - if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then cat "$MD_FILE" >> "$GITHUB_STEP_SUMMARY" fi - name: Upload Trivy JSON report - if: always() && steps.scan.outputs.json_path != '' + if: always() uses: actions/upload-artifact@v4 with: - name: trivy-scan-json--${{ steps.meta.outputs.app_name }}-${{ github.run_number }} - path: ${{ steps.scan.outputs.json_path }} - if-no-files-found: error + name: trivy-scan-json-${{ steps.meta.outputs.app_name }}-${{ github.run_number }} + path: ${{ steps.meta.outputs.json_file }} + if-no-files-found: warn - name: Upload Trivy HTML report - if: always() && steps.scan.outputs.html_path != '' + if: always() uses: actions/upload-artifact@v4 with: - name: trivy-scan-html--${{ steps.meta.outputs.app_name }}-${{ github.run_number }} - path: ${{ steps.scan.outputs.html_path }} - if-no-files-found: error + name: trivy-scan-html-${{ steps.meta.outputs.app_name }}-${{ github.run_number }} + path: ${{ steps.meta.outputs.html_file }} + if-no-files-found: warn - name: Upload Trivy markdown report - if: always() && steps.scan.outputs.markdown_path != '' + if: always() uses: actions/upload-artifact@v4 with: - name: trivy-scan-md--${{ steps.meta.outputs.app_name }}-${{ github.run_number }} - path: ${{ steps.scan.outputs.markdown_path }} - if-no-files-found: error + name: trivy-scan-md-${{ steps.meta.outputs.app_name }}-${{ github.run_number }} + path: ${{ steps.meta.outputs.md_file }} + if-no-files-found: warn - name: Evaluate Trivy vulnerability threshold id: gate + if: always() shell: bash run: | set -euo pipefail - JSON_FILE="${{ steps.scan.outputs.json_path }}" + JSON_FILE="${{ steps.meta.outputs.json_file }}" + SCAN_EXIT_CODE="${{ steps.scan.outputs.scan_exit_code }}" SEVERITY_THRESHOLD="${{ inputs.severity-threshold }}" FAIL_BUILD="${{ inputs.fail-build }}" - if [ -z "$JSON_FILE" ] || [ ! -f "$JSON_FILE" ]; then + if [ -z "$SCAN_EXIT_CODE" ]; then + echo "::error::Trivy scan did not execute." + exit 1 + fi + + if [ "$SCAN_EXIT_CODE" -ne 0 ]; then + echo "::error::Trivy scan failed with exit code $SCAN_EXIT_CODE." + exit 1 + fi + + if [ ! -s "$JSON_FILE" ]; then echo "::error::Trivy JSON report was not generated." exit 1 fi @@ -277,7 +327,7 @@ runs: exit 1 fi - echo "::warning::Trivy found $THRESHOLD_FINDINGS vulnerabilities matching threshold '$SEVERITY_THRESHOLD'. Blocking is disabled for this application." + echo "::warning::Trivy found $THRESHOLD_FINDINGS vulnerabilities matching threshold '$SEVERITY_THRESHOLD'. Blocking is disabled." exit 0 fi diff --git a/build-image/upload-sast-report/action.yaml b/build-image/upload-sast-report/action.yaml index 124b6292..ce57fff9 100644 --- a/build-image/upload-sast-report/action.yaml +++ b/build-image/upload-sast-report/action.yaml @@ -18,6 +18,18 @@ runs: using: composite steps: + - name: Prepare SAST report path + id: report + working-directory: ${{ inputs.working-directory }} + shell: bash + run: | + set -euo pipefail + + REPORT_FILE="sast-report-${{ inputs.app-name }}-${{ github.run_number }}.sarif" + REPORT_PATH="$PWD/$REPORT_FILE" + + echo "report_file=$REPORT_PATH" >> "$GITHUB_OUTPUT" + - name: Install SAST CLI working-directory: ${{ inputs.working-directory }} shell: bash @@ -35,10 +47,9 @@ runs: working-directory: ${{ inputs.working-directory }} shell: bash run: | - set -u + set -uo pipefail - REPORT_FILE="sast-report-${{ inputs.app-name }}-${{ github.run_number }}.sarif" - REPORT_PATH="$PWD/$REPORT_FILE" + REPORT_PATH="${{ steps.report.outputs.report_file }}" set +e @@ -46,7 +57,7 @@ runs: --scanner sast \ --report security \ --format sarif \ - --output "$REPORT_FILE" \ + --output "$REPORT_PATH" \ --severity critical,high,medium,low,warning \ --fail-on-severity critical,high \ --exit-code 1 @@ -55,19 +66,27 @@ runs: set -e - echo "report_file=$REPORT_PATH" >> "$GITHUB_OUTPUT" echo "scan_exit_code=$SCAN_EXIT_CODE" >> "$GITHUB_OUTPUT" - # Always allow the artifact upload step to execute. + if [ ! -s "$REPORT_PATH" ]; then + cat > "$REPORT_PATH" <