diff --git a/internal/run-script/action.yml b/internal/run-script/action.yml index 8cff093..d691b53 100644 --- a/internal/run-script/action.yml +++ b/internal/run-script/action.yml @@ -27,13 +27,14 @@ runs: - run: echo "_RUN_SCRIPTS_DIR=$(pwd)/scripts" >> $GITHUB_ENV shell: bash working-directory: ${{ github.action_path }} + - run: echo "TEMP_DIR=$(pwd)" >> $GITHUB_ENV + shell: bash + working-directory: ${{ runner.temp }} - uses: fortify/github-action/internal/run-script/js@feat-1.3.0 with: dir: ${{ env._RUN_SCRIPTS_DIR }} script: ${{ inputs.script }} post: ${{ inputs.post }} - env: - TEMP_DIR: ${{ env.RUNNER_TEMP }} branding: icon: 'shield' diff --git a/internal/run-script/scripts/common.sh b/internal/run-script/scripts/common.sh index 821d1f0..4ad4c04 100644 --- a/internal/run-script/scripts/common.sh +++ b/internal/run-script/scripts/common.sh @@ -1,23 +1,60 @@ #!/usr/bin/env bash if [ -n "$RUNNER_DEBUG" ]; then set -v -x + echo "Bash version: $BASH_VERSION" fi -echo "Bash version: $BASH_VERSION" +function printOutputFileName { + local operation=$1 + local type=$2 + mkdir -p "${TEMP_DIR}" + printf '%s/output_%s_%s.txt' "${TEMP_DIR}" "${operation}" "${type}" +} + +function printOutput { + local operation=$1 + local type=$2 + cat $(printOutputFileName "${operation}" "${type}") +} declare -a runs -declare -a runsWithError declare -A runResults declare -A runCommands function run { local operation=$1; shift; + local cmd=( ) + for arg in "$@"; do + # Expand environment variables that potentially contain multiple arguments. + # This is commonly used for *_EXTRA_OPTS environment variables, needed to + # properly handle quoted arguments containing whitespace. + if [[ "$arg" == "__expand:"* ]]; then + local varName=${arg#"__expand:"} + if [ ! -z "${!varName}" ]; then + readarray -d '' expandedArgs < <(xargs printf '%s\0' <<<"${!varName}") + cmd+=("${expandedArgs[@]}") + fi + else + cmd+=("$arg") + fi + done runs+=($operation) - runCommands[$operation]="$@" - echo RUN $operation: "$@" - "$@" - local exitCode=$? + runCommands[$operation]="${cmd[@]}" + echo "::group::RUN $operation: ${cmd[@]}" + # Any better way of doing this, avoiding writing exit code to temporary file? + local exitCodeFile="$TEMP_DIR/exit_code.txt" + { ("${cmd[@]}"; echo >"$exitCodeFile" $?) 2>&1 1>&3 3>&- \ + | tee $(printOutputFileName "${operation}" "stderr"); } 3>&1 1>&2 \ + | tee $(printOutputFileName "${operation}" "stdout") + local exitCode=$(cat "$exitCodeFile") + rm -f ${exitCodeFile} + runResults[$operation]=$exitCode + echo "::endgroup::" +} + +function overrideExitCode { + local operation=$1 + local exitCode=$2 runResults[$operation]=$exitCode - requireRun $operation || runsWithError+=($operation) } function requireRun { @@ -25,22 +62,49 @@ function requireRun { [[ "${runResults[$operation]}" == "0" ]] } +function printRunStatus { + local operations=("$@"); + local fail=0, success=0; + for op in "${operations[@]}"; do + if [ -z "${runResults[$op]}" ]; then + skip=1 + elif [[ "${runResults[$op]}" == "0" ]]; then + success=1 + else + fail=1 + fi + done + if [[ ${fail} == 1 ]]; then + echo "FAILED" + elif [[ ${success} == 1 ]]; then + echo "SUCCESS" + else + echo "SKIPPED" + fi +} + function printRunSummary { echo "Summary:" + local failingOperations=() for value in "${runs[@]}"; do echo -n " $value: " - requireRun $value && echo "SUCCESS" || echo "ERROR" + if requireRun $value; then + echo "SUCCESS" + else + echo "ERROR" + failingOperations+=("${value}") + fi done - if [ ! ${#runsWithError[@]} -eq 0 ]; then + if [ ! ${#failingOperations[@]} -eq 0 ]; then echo "Failing commands:" - for value in "${runsWithError[@]}"; do + for value in "${failingOperations[@]}"; do echo " $value: ${runCommands[$value]}" done fi } function failOnError { - if [ ! ${#runsWithError[@]} -eq 0 ]; then + if [[ ! "$(printRunSummary)" == *"ERROR"* ]]; then exit 1; fi } diff --git a/internal/run-script/scripts/sc-sast-and-debricked-scan.sh b/internal/run-script/scripts/sc-sast-and-debricked-scan.sh index cd45f6f..d3250b3 100755 --- a/internal/run-script/scripts/sc-sast-and-debricked-scan.sh +++ b/internal/run-script/scripts/sc-sast-and-debricked-scan.sh @@ -10,19 +10,69 @@ requireIf "DO_DEBRICKED_SCAN" "DEBRICKED_CLI_CMD" requireIf "DO_DEBRICKED_SCAN" "DEBRICKED_TOKEN" checkRequirements +# Disable Debricked CLI colors +export NO_COLOR=true + if [ "${DO_SC_SAST_SCAN}" == "true" ]; then - run "SAST_SCAN" ${FCLI_CMD} sc-sast scan start --publish-to "${SSC_APPVERSION}" -p package.zip -v "${SC_SAST_SENSOR_VERSION}" --store sc_sast_scan ${EXTRA_SC_SAST_SCAN_OPTS} + run "SAST_SCAN" ${FCLI_CMD} sc-sast scan start \ + --publish-to "${SSC_APPVERSION}" -p package.zip -v "${SC_SAST_SENSOR_VERSION}" \ + --store sc_sast_scan __expand:EXTRA_SC_SAST_SCAN_OPTS fi if [ "${DO_DEBRICKED_SCAN}" == "true" ]; then # Debricked may return non-zero exit code on automation rule failures, in which case # we still want to run the import, so we don't explicitly check for Debricked scan success. run "DEBRICKED_SCAN" ${DEBRICKED_CLI_CMD} scan -t "${DEBRICKED_TOKEN}" -i "Fortify GitHub Action" - run "DEBRICKED_IMPORT" ${FCLI_CMD} ssc artifact import-debricked --av "${SSC_APPVERSION}" --repository "${GITHUB_REPOSITORY}" --branch "${GITHUB_HEAD_REF:-$GITHUB_REF_NAME}" -t "${DEBRICKED_TOKEN}" --store debricked_scan + run "DEBRICKED_IMPORT" ${FCLI_CMD} ssc artifact import-debricked \ + --av "${SSC_APPVERSION}" --repository "${GITHUB_REPOSITORY}" \ + --branch "${GITHUB_HEAD_REF:-$GITHUB_REF_NAME}" -t "${DEBRICKED_TOKEN}" \ + --store debricked_scan fi if [ "${DO_WAIT}" == "true" ] || [ "${DO_EXPORT}" == "true" ]; then - requireRun "SAST_SCAN" && run "SAST_PUBLISH" ${FCLI_CMD} sc-sast scan wait-for ::sc_sast_scan:: - requireRun "DEBRICKED_IMPORT" && run "DEBRICKED_PUBLISH" ${FCLI_CMD} ssc artifact wait-for ::debricked_scan:: + requireRun "SAST_SCAN" && run "SAST_PUBLISH" \ + ${FCLI_CMD} sc-sast scan wait-for ::sc_sast_scan:: + requireRun "DEBRICKED_IMPORT" && run "DEBRICKED_PUBLISH" \ + ${FCLI_CMD} ssc artifact wait-for ::debricked_scan:: fi +# Collect Debricked scan output +DEBRICKED_SCAN_RESULTS=$(printOutput DEBRICKED_SCAN stdout | fgrep -e '───' -e '│' -e 'vulnerabilities found' -e 'For full details') + +# Collect scan/publish statuses for inclusion in job summary. +SAST_SCAN_STATUS=$(printRunStatus "SAST_SCAN") +SAST_PUBLISH_STATUS=$(printRunStatus "SAST_PUBLISH") +DEBRICKED_SCAN_STATUS=$(printRunStatus "DEBRICKED_SCAN") +DEBRICKED_PUBLISH_STATUS=$(printRunStatus "DEBRICKED_IMPORT" "DEBRICKED_PUBLISH") +[ "${DEBRICKED_SCAN_STATUS}" == "ERROR" ] && [ ! -z "${DEBRICKED_SCAN_RESULTS}" ] && DEBRICKED_SCAN_STATUS="FAILED_RULES" + +cat <> $GITHUB_STEP_SUMMARY +# Scan Summary +This section provides a status overview of the scans types supported by this GitHub Action, +together with their status. + +| Analysis Type | Scan Status | Publish Status | +| ------------- | ----------- | -------------- | +| SCA | ${SAST_SCAN_STATUS} | ${SAST_PUBLISH_STATUS} | +| DEBRICKED | ${DEBRICKED_SCAN_STATUS} | ${DEBRICKED_PUBLISH_STATUS} | +EOF + +[ ! -z "${DEBRICKED_SCAN_RESULTS}" ] && cat <> $GITHUB_STEP_SUMMARY +# Debricked Scan Results + +\`\`\` +${DEBRICKED_SCAN_RESULTS} +\`\`\` +EOF + +APPVERSION_SUMMARY_ACTION="${APPVERSION_SUMMARY_ACTION:-appversion-summary}" +run "APPVERSION_SUMMARY" ${FCLI_CMD} ssc action run "${APPVERSION_SUMMARY_ACTION}" \ + --av "${SSC_APPVERSION}" --progress=none __expand:APPVERSION_SUMMARY_ACTION_EXTRA_OPTS +requireRun "APPVERSION_SUMMARY" \ + && printOutput "APPVERSION_SUMMARY" "stdout" >> $GITHUB_STEP_SUMMARY \ + || cat<> $GITHUB_STEP_SUMMARY +# SSC Application Version Summary +There was an error generating the application version summary; please review pipeline log for details. +EOF + + printRunSummary failOnError