diff --git a/.github/workflows/ci-main-pull-request.yml b/.github/workflows/ci-main-pull-request.yml index 9669dac..8a1a08d 100644 --- a/.github/workflows/ci-main-pull-request.yml +++ b/.github/workflows/ci-main-pull-request.yml @@ -41,6 +41,16 @@ name: CI flow containing PR checks for main & release, v2 on: workflow_call: inputs: + github-event-name: + description: 'GitHub event name (pass github.event_name from calling workflow for PR comment detection)' + required: false + type: string + default: '' + github-branch-name: + description: 'GitHub branch name (pass github.ref_name from calling workflow for branch-specific logic)' + required: false + type: string + default: '' application: # NEW IN 1.0.7 description: 'Application set in repository custom properties, typically primaryApplication' @@ -146,6 +156,11 @@ on: required: false type: boolean default: false + grype-image-skip-aws: + description: 'Skip Grype image scan on AWS ECR images to avoid rate limits (assumes these images are scanned with Amazon ECR scan or Trivy)' + required: false + type: boolean + default: false build: description: 'CI Build (language-specific)' required: false @@ -509,7 +524,7 @@ jobs: echo "GA_BUILD_PROFILE=$GABuildProfile" >> $GITHUB_ENV continue-on-error: true env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }} - name: generate-filename-slug # description: Generate a simple slug based on repo and date for use in any output artifacts @@ -713,7 +728,7 @@ jobs: if: inputs.language == 'go' env: GOPRIVATE: ${{ inputs.go-private-modules }} - run: git config --global url."https://${{ secrets.GH_TOKEN }}@github.com/".insteadOf "https://github.com/" + run: git config --global url."https://${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }}@github.com/".insteadOf "https://github.com/" - name: Go linting and security checks if: inputs.language == 'go' run: echo "Running Go linting and security checks" @@ -782,6 +797,8 @@ jobs: uses: chef/common-github-actions/.github/workflows/trufflehog.yml@main needs: checkout with: + github-event-name: ${{ inputs.github-event-name }} + github-branch-name: ${{ inputs.github-branch-name }} fail-trufflehog-on-secrets-found: ${{ inputs.fail-trufflehog-on-secrets-found }} run-trivy: @@ -890,6 +907,7 @@ jobs: with: fail-grype-on-high: ${{ inputs.grype-image-fail-on-high }} fail-grype-on-critical: ${{ inputs.grype-image-fail-on-critical }} + grype-image-skip-aws: ${{ inputs.grype-image-skip-aws }} # run-srcclr: # if: ${{ inputs.perform-srcclr-scan == true }} @@ -922,7 +940,7 @@ jobs: if: inputs.language == 'go' env: GOPRIVATE: ${{ inputs.go-private-modules }} - run: git config --global url."https://${{ secrets.GH_TOKEN }}@github.com/".insteadOf "https://github.com/" + run: git config --global url."https://${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }}@github.com/".insteadOf "https://github.com/" - name: 'Go build' if: ${{ inputs.language == 'go' && env.GA_BUILD_PROFILE == 'cli' }} continue-on-error: true @@ -1172,176 +1190,29 @@ jobs: # udf3: ${{ inputs.udf3 }} BlackDuck-Polaris-SAST: - # branding: applied at action.yml level, not workflow, see https://docs.github.com/en/actions/reference/workflows-and-actions/metadata-syntax#branding - # icon: 'shield' - # color: 'red' - # TODO: add new flags to BLACKDUCK ${{ polaris-blackduck-executable }} && ${{ polaris-executable-detect-path }} - # NOW THE LATEST COMMUNITY - https://github.com/prgs-community/githubactions-securityscans/blob/main/polaris/action.yml - # # updated to https://documentation.blackduck.com/bundle/bridge/page/documentation/c_download.html#polaris-download - # # https://github.com/marketplace/actions/black-duck-security-scan - # NOT USED - from https://documentation.blackduck.com/bundle/bridge/page/documentation/t_github-polaris-quickstart.html - # parameters @ https://documentation.blackduck.com/bundle/bridge/page/documentation/c_github-polaris.html - # internal confluence https://progresssoftware.atlassian.net/wiki/spaces/TCE/pages/1010336076/Polaris#Examples - # and https://progresssoftware.atlassian.net/wiki/spaces/TCE/pages/1010303415/Polaris+-+Github+Actions+Templates - # chef-vault at https://polaris.blackduck.com/portfolio/portfolios/8b7ad6f7-6dcb-49ec-bded-bfc4f190d4f8/portfolio-items/fe369baf-11d2-4989-bcb7-045577856dcc/projects/2460eabd-d033-48a1-a378-6cadd49be6d1/tests/sast?branchId=a6d2c02a-05f8-4557-bfa1-c40e9337ee5d - if: ${{ inputs.perform-blackduck-polaris == true }} - runs-on: ubuntu-latest - needs: checkout # TODO: fix set-application-version - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Configure git for private - run: git config --global url."https://${{ secrets.GH_TOKEN }}@github.com/".insteadOf "https://github.com/" - - - name: Install build tools for Erlang - if: inputs.language == 'erlang' - run: | - sudo apt-get update - sudo apt-get install -y build-essential - - - name: Set up Erlang/OTP and rebar3 - if: inputs.language == 'erlang' - uses: erlef/setup-beam@v1 - with: - otp-version: '25.3.2.16' - rebar3-version: '3.22.0' - - - name: Set up Ruby - if: inputs.language == 'ruby' - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.4' - bundler-cache: false - - - name: Create bundle stub for Erlang SAST scan - if: inputs.language == 'erlang' - working-directory: ${{ github.workspace }} - run: | - # Polaris scans Erlang source code for SAST - Ruby gems not needed - # System ruby-dev from apt provides Ruby runtime (already installed) - # Create bundle stub to skip gem installation during Polaris scan - echo "Creating bundle stub to bypass Ruby gem installation" - - # Create no-op bundle script - mkdir -p "$HOME/.polaris-stubs" - cat > "$HOME/.polaris-stubs/bundle" << 'EOF' - #!/bin/bash - # Stub: skips gem installation during SAST scan - echo "[STUB] Skipping bundle $@ - not needed for Erlang SAST" - exit 0 - EOF - chmod +x "$HOME/.polaris-stubs/bundle" - - # Prepend to PATH so stub is found before any system bundler - echo "$HOME/.polaris-stubs" >> $GITHUB_PATH - - echo "Bundle stub created and added to PATH" - - - name: Starting Black Duck Polaris scan - run: | - echo "Starting Polaris SAST scan" - echo "Below variable should be https://polaris.blackduck.com" - echo "POLARIS_SERVER_URL: ${{ secrets.POLARIS_SERVER_URL }}" - echo "Polaris application name: ${{ inputs.polaris-application-name }}" - echo "Polaris project name: ${{ inputs.polaris-project-name }}" - echo "POLARIS_ACCESS_TOKEN: ${{ secrets.POLARIS_ACCESS_TOKEN }}" - continue-on-error: true - # other polaris/coverity flags (unused): working-directory, config-path, coverity-config-path, coverity-build-command, coverity-clean-command, coverity-args, - # detect-search-depth, detect-args, assessment-mode, source-archive, source-excludes, preserve-symlinks, wait-for-scan, mark-build-status:, pr-comment-severities, diagnostic - - - name: Check input variables - run: | - if [[ -z "${{ inputs.polaris-application-name }}" ]] || [[ -z "${{ inputs.polaris-project-name }}" ]]; then - echo "Polaris application name and project name must be set" - exit 1 - fi - - - name: BlackDuck Polaris scan - id: polaris-scan - uses: blackduck-inc/black-duck-security-scan@v2 - # copied from uses: prgs-community/githubactions-securityscans/polaris@v0.5 in https://github.com/prgs-community/githubactions-securityscans/blob/main/polaris/README.md - # uses: blackduck-inc/black-duck-security-scan@805cbd09e806b01907bbea0f990723c2bb85abe9 # 2.0.0 - Jan's version - with: - ### SCANNING: Required fields - polaris_server_url: ${{ secrets.POLARIS_SERVER_URL }} # https://polaris.blackduck.com - polaris_access_token: ${{ secrets.POLARIS_ACCESS_TOKEN }} - polaris_application_name: "${{ inputs.polaris-application-name }}" - # project name schema: --- - polaris_project_name: ${{ inputs.polaris-project-name }} # typically GitHub repo name - polaris_assessment_types: "SAST" # or "CI" or "SOURCE_UPLOAD", a license type - # new in 1.0.7, OPTIONAL FIELDS - project_directory: ${{ inputs.polaris-working-directory }} - detect_config_path: ${{ inputs.polaris-config-path != '' && inputs.polaris-config-path || null}} - coverity_config_path: ${{ inputs.polaris-coverity-config-path != '' && inputs.polaris-coverity-config-path || null }} - coverity_build_command: ${{ inputs.polaris-coverity-build-command != '' && inputs.polaris-coverity-build-command || null }} - coverity_clean_command: ${{ inputs.polaris-coverity-clean-command != '' && inputs.polaris-coverity-clean-command || null }} - coverity_args: ${{ inputs.polaris-coverity-args != '' && inputs.polaris-coverity-args || null }} - detect_search_depth: ${{ inputs.polaris-detect-search-depth != '' && inputs.polaris-detect-search-depth || null }} - detect_args: ${{ inputs.polaris-detect-args != '' && inputs.polaris-detect-args || null }} - polaris_assessment_mode: ${{ inputs.polaris-assessment-mode }} - # TODO: warning in GA - polaris_assessment_mode is deprecated. Use polaris_test_sast_location=remote and/or polaris_test_sca_location=remote for source upload scans instead. - polaris_waitForScan: 'true' # ${{ inputs.polaris-wait-for-scan }} # defaults to true - is this a boolean or string? - # not yet enabled from https://github.com/prgs-community/githubactions-securityscans/blob/main/polaris/README.md - # project_source_archive: ${{ inputs.polaris-source-archive != '' && inputs.polaris-source-archive || null }} - # project_source_excludes: ${{ inputs.polaris-source-excludes != '' && inputs.polaris-source-excludes || null }} - # project_source_preserveSymLinks: ${{ inputs.polaris-preserve-symlinks }} - # include_diagnostics: ${{ inputs.polaris-diagnostic }} - # mark_build_status: ${{ inputs.polaris-mark-build-status != '' && inputs.polaris-mark-build-status || null }} - # pr-comment-severities: "CRITICAL,HIGH" - github_token: ${{ secrets.GITHUB_TOKEN }} - - # options from Jan's (FlowMon. GitLab) - # polaris_prComment_enabled: ${{ github.event_name == 'pull_request' && 'true' || 'false' }} - # polaris_prComment_severities: "CRITICAL,HIGH" - # from Blackduck page at https://polaris.blackduck.com/portfolio/portfolios/8b7ad6f7-6dcb-49ec-bded-bfc4f190d4f8/portfolio-items/fe369baf-11d2-4989-bcb7-045577856dcc/projects - # SARIF report parameters - # polaris_reports_sarif_create: true - # polaris_upload_sarif_report: true - # Mark build status if policy violating issues are found - # mark_build_status: 'success' - continue-on-error: false - - - name: Check Polaris scan results and fail on HIGH or CRITICAL vulnerabilities - if: ${{ inputs.polaris-fail-on-high == true || inputs.polaris-fail-on-critical == true }} - run: | - echo "Checking Polaris SAST scan results..." - echo "Enforcement policy: HIGH=${{ inputs.polaris-fail-on-high }}, CRITICAL=${{ inputs.polaris-fail-on-critical }}" - - # Parse bridge.log for vulnerability counts - BRIDGE_LOG=".bridge/bridge.log" - - if [ ! -f "$BRIDGE_LOG" ]; then - echo "⚠️ Bridge log not found - failing as precaution" - exit 1 - fi - - # Extract vulnerability counts from log - HIGH_COUNT=$(grep -oP '"high":\s*\K\d+' "$BRIDGE_LOG" | tail -1 || echo 0) - CRITICAL_COUNT=$(grep -oP '"critical":\s*\K\d+' "$BRIDGE_LOG" | tail -1 || echo 0) - - echo "Found HIGH: $HIGH_COUNT, CRITICAL: $CRITICAL_COUNT" - - # Check for policy violations - SHOULD_FAIL=false - - if [ "${{ inputs.polaris-fail-on-critical }}" == "true" ] && [ "$CRITICAL_COUNT" -gt 0 ]; then - echo "❌ Found $CRITICAL_COUNT CRITICAL vulnerabilities (policy violation)" - SHOULD_FAIL=true - fi - - if [ "${{ inputs.polaris-fail-on-high }}" == "true" ] && [ "$HIGH_COUNT" -gt 0 ]; then - echo "❌ Found $HIGH_COUNT HIGH vulnerabilities (policy violation)" - SHOULD_FAIL=true - fi - - if [ "$SHOULD_FAIL" == "true" ]; then - exit 1 - else - echo "✅ No policy-violating vulnerabilities found" - fi + name: 'BlackDuck Polaris SAST scan' + if: ${{ inputs.perform-blackduck-polaris }} + uses: chef/common-github-actions/.github/workflows/polaris-sast.yml@main + needs: checkout + secrets: inherit + with: + github-event-name: ${{ inputs.github-event-name }} + github-branch-name: ${{ inputs.github-branch-name }} + language: ${{ inputs.language }} + polaris-application-name: ${{ inputs.polaris-application-name }} + polaris-project-name: ${{ inputs.polaris-project-name }} + polaris-working-directory: ${{ inputs.polaris-working-directory }} + polaris-config-path: ${{ inputs.polaris-config-path }} + polaris-coverity-config-path: ${{ inputs.polaris-coverity-config-path }} + polaris-coverity-clean-command: ${{ inputs.polaris-coverity-clean-command }} + polaris-coverity-build-command: ${{ inputs.polaris-coverity-build-command }} + polaris-coverity-args: ${{ inputs.polaris-coverity-args }} + polaris-detect-search-depth: ${{ inputs.polaris-detect-search-depth }} + polaris-detect-args: ${{ inputs.polaris-detect-args }} + polaris-assessment-mode: ${{ inputs.polaris-assessment-mode }} + wait-for-scan: ${{ inputs.wait-for-scan }} + polaris-fail-on-high: ${{ inputs.polaris-fail-on-high }} + polaris-fail-on-critical: ${{ inputs.polaris-fail-on-critical }} package-binary: name: 'Creating packaged binaries' @@ -1592,6 +1463,8 @@ jobs: needs: checkout # TODO: fix set-application-version secrets: inherit with: + github-event-name: ${{ inputs.github-event-name }} + github-branch-name: ${{ inputs.github-branch-name }} version: ${{ inputs.version }} export-github-sbom: ${{ inputs.export-github-sbom }} perform-blackduck-sca-scan: ${{ inputs.perform-blackduck-sca-scan }} diff --git a/.github/workflows/grype.yml b/.github/workflows/grype.yml index 97c5f23..f9d5931 100644 --- a/.github/workflows/grype.yml +++ b/.github/workflows/grype.yml @@ -17,6 +17,11 @@ on: required: false type: boolean default: false + grype-image-skip-aws: + description: 'Skip Grype image scan on AWS ECR images to avoid rate limits (assumes these images are scanned with Amazon ECR scan or Trivy)' + required: false + type: boolean + default: false jobs: grype-scan: @@ -42,6 +47,7 @@ jobs: - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 + if: ${{ !inputs.grype-image-skip-aws }} with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -50,6 +56,7 @@ jobs: - name: Login to Amazon ECR id: login-ecr + if: ${{ !inputs.grype-image-skip-aws }} uses: aws-actions/amazon-ecr-login@v2 - name: Scan with Grype @@ -133,9 +140,9 @@ jobs: fi # Extract vulnerability counts by severity from multiple JSON documents - # Use jq -s to slurp all JSON objects and combine matches - CRITICAL_COUNT=$(jq -s '[.[] | .matches[]? | select(.vulnerability.severity == "Critical")] | length' "$JSON_FILE" 2>/dev/null || echo "0") - HIGH_COUNT=$(jq -s '[.[] | .matches[]? | select(.vulnerability.severity == "High")] | length' "$JSON_FILE" 2>/dev/null || echo "0") + # Use jq -s to slurp, deduplicate by CVE+package+version, then count + CRITICAL_COUNT=$(jq -s '[.[] | .matches[]? | select(.vulnerability.severity == "Critical")] | unique_by(.vulnerability.id + .artifact.name + .artifact.version) | length' "$JSON_FILE" 2>/dev/null || echo "0") + HIGH_COUNT=$(jq -s '[.[] | .matches[]? | select(.vulnerability.severity == "High")] | unique_by(.vulnerability.id + .artifact.name + .artifact.version) | length' "$JSON_FILE" 2>/dev/null || echo "0") echo "" echo "============================================" diff --git a/.github/workflows/polaris-sast.yml b/.github/workflows/polaris-sast.yml new file mode 100644 index 0000000..cf9ef06 --- /dev/null +++ b/.github/workflows/polaris-sast.yml @@ -0,0 +1,311 @@ +# polaris-sast.yml +# BlackDuck Polaris SAST (Static Application Security Testing) scan +# See https://github.com/prgs-community/githubactions-securityscans/blob/main/polaris/action.yml +# Updated to https://documentation.blackduck.com/bundle/bridge/page/documentation/c_download.html#polaris-download +# Parameters @ https://documentation.blackduck.com/bundle/bridge/page/documentation/c_github-polaris.html + +name: BlackDuck Polaris SAST scan + +on: + workflow_call: + inputs: + github-event-name: + description: 'GitHub event name (pass github.event_name from calling workflow for PR comment detection)' + required: false + type: string + default: '' + github-branch-name: + description: 'GitHub branch name (pass github.ref_name from calling workflow for branch-specific logic)' + required: false + type: string + default: '' + language: + description: 'Build language (Go, Ruby, Rust, Erlang, etc.)' + required: false + type: string + default: 'ruby' + polaris-application-name: + description: 'Polaris application name, one of these {Chef-Agents | Chef-Automate | Chef-Chef360 | Chef-Habitat | Chef-Infrastructure-Server | Chef-Shared-Services}' + required: true + type: string + polaris-project-name: + description: 'Polaris project name, typically the application name, followed by - and the repository name' + required: false + default: "${{ github.event.repository.name }}" + type: string + polaris-working-directory: + description: 'Working directory for the scan, defaults to . but usually lang-dependent like ./src' + required: false + default: '.' + type: string + polaris-config-path: + description: 'Path to Detect configuration file' + required: false + default: '' + type: string + polaris-coverity-config-path: + description: 'Path to Coverity configuration file' + required: false + default: '' + type: string + polaris-coverity-clean-command: + description: 'Coverity clean command. Leave empty for buildless analysis (Ruby, Python, etc.)' + required: false + default: '' + type: string + polaris-coverity-build-command: + description: 'Coverity build command. Leave empty for buildless analysis (Ruby, Python, etc.)' + required: false + default: '' + type: string + polaris-coverity-args: + description: 'Additional Coverity arguments' + required: false + default: '' + type: string + polaris-detect-search-depth: + description: 'Detect search depth' + required: false + default: '' + type: string + polaris-detect-args: + description: 'Additional Detect arguments' + required: false + default: '' + type: string + polaris-assessment-mode: + description: 'Assessment mode (CI or SOURCE_UPLOAD)' + required: false + default: 'CI' + type: string + wait-for-scan: + description: 'Wait for scan completion' + required: false + default: true + type: boolean + polaris-fail-on-high: + description: 'Fail the pipeline if Polaris SAST scan finds HIGH vulnerabilities' + required: false + type: boolean + default: false + polaris-fail-on-critical: + description: 'Fail the pipeline if Polaris SAST scan finds CRITICAL vulnerabilities' + required: false + type: boolean + default: false + +jobs: + polaris-sast-scan: + name: 'BlackDuck Polaris SAST scan' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Configure git for private repos + run: | + GITHUB_TOKEN_TO_USE="${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }}" + git config --global url."https://${GITHUB_TOKEN_TO_USE}@github.com/".insteadOf "https://github.com/" + + - name: Install build tools for Erlang + if: inputs.language == 'erlang' + run: | + sudo apt-get update + sudo apt-get install -y build-essential + + - name: Set up Erlang/OTP and rebar3 + if: inputs.language == 'erlang' + uses: erlef/setup-beam@v1 + with: + otp-version: '25.3.2.16' + rebar3-version: '3.22.0' + + - name: Set up Ruby + if: inputs.language == 'ruby' + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.4' + bundler-cache: false + + - name: Fetch base branch for PR scan + if: ${{ inputs.github-event-name == 'pull_request' }} + run: | + git fetch origin main + + - name: Create bundle stub for Erlang SAST scan + if: inputs.language == 'erlang' + working-directory: ${{ github.workspace }} + run: | + # Polaris scans Erlang source code for SAST - Ruby gems not needed + # System ruby-dev from apt provides Ruby runtime (already installed) + # Create bundle stub to skip gem installation during Polaris scan + echo "Creating bundle stub to bypass Ruby gem installation" + + # Create no-op bundle script + mkdir -p "$HOME/.polaris-stubs" + cat > "$HOME/.polaris-stubs/bundle" << 'EOF' + #!/bin/bash + # Stub: skips gem installation during SAST scan + echo "[STUB] Skipping bundle $@ - not needed for Erlang SAST" + exit 0 + EOF + chmod +x "$HOME/.polaris-stubs/bundle" + + # Prepend to PATH so stub is found before any system bundler + echo "$HOME/.polaris-stubs" >> $GITHUB_PATH + + echo "Bundle stub created and added to PATH" + + - name: Starting Black Duck Polaris scan + run: | + echo "Starting Polaris SAST scan" + echo "Below variable should be https://polaris.blackduck.com" + echo "POLARIS_SERVER_URL: ${{ secrets.POLARIS_SERVER_URL }}" + echo "Polaris application name: ${{ inputs.polaris-application-name }}" + echo "Polaris project name: ${{ inputs.polaris-project-name }}" + continue-on-error: true + + - name: Check input variables + run: | + if [[ -z "${{ inputs.polaris-application-name }}" ]] || [[ -z "${{ inputs.polaris-project-name }}" ]]; then + echo "Polaris application name and project name must be set" + exit 1 + fi + + - name: BlackDuck Polaris Full scan + id: polaris-full-scan + if: ${{ inputs.github-event-name != 'pull_request' }} + uses: blackduck-inc/black-duck-security-scan@v2 + with: + ### SCANNING: Required fields + polaris_server_url: ${{ secrets.POLARIS_SERVER_URL }} + polaris_access_token: ${{ secrets.POLARIS_ACCESS_TOKEN }} + polaris_application_name: "${{ inputs.polaris-application-name }}" + polaris_project_name: ${{ inputs.polaris-project-name }} + polaris_assessment_types: "SAST" + # Optional fields + project_directory: ${{ inputs.polaris-working-directory }} + detect_config_path: ${{ inputs.polaris-config-path != '' && inputs.polaris-config-path || null}} + coverity_config_path: ${{ inputs.polaris-coverity-config-path != '' && inputs.polaris-coverity-config-path || null }} + coverity_build_command: ${{ inputs.polaris-coverity-build-command != '' && inputs.polaris-coverity-build-command || null }} + coverity_clean_command: ${{ inputs.polaris-coverity-clean-command != '' && inputs.polaris-coverity-clean-command || null }} + coverity_args: ${{ inputs.polaris-coverity-args != '' && inputs.polaris-coverity-args || null }} + detect_search_depth: ${{ inputs.polaris-detect-search-depth != '' && inputs.polaris-detect-search-depth || null }} + detect_args: ${{ inputs.polaris-detect-args != '' && inputs.polaris-detect-args || null }} + polaris_assessment_mode: ${{ inputs.polaris-assessment-mode }} + polaris_waitForScan: 'true' + # polaris_prComment_enabled: ${{ inputs.github-event-name == 'pull_request' && 'true' || 'false' }} + # polaris_branch_parent_name: ${{ inputs.github-event-name == 'pull_request' && 'main' || null }} + # polaris_branch_name: ${{ inputs.github-event-name == 'pull_request' && inputs.github-branch-name || null }} + # polaris_prComment_severities: "CRITICAL,HIGH" + github_token: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }} + continue-on-error: false + + - name: BlackDuck Polaris PR scan + id: polaris-pr-scan + if: ${{ inputs.github-event-name == 'pull_request' }} + uses: blackduck-inc/black-duck-security-scan@v2 + with: + ### SCANNING: Required fields + polaris_server_url: ${{ secrets.POLARIS_SERVER_URL }} + polaris_access_token: ${{ secrets.POLARIS_ACCESS_TOKEN }} + polaris_application_name: "${{ inputs.polaris-application-name }}" + polaris_project_name: ${{ inputs.polaris-project-name }} + polaris_assessment_types: "SAST" + polaris_test_sast_type: "SAST_RAPID" + # Optional fields + project_directory: ${{ inputs.polaris-working-directory }} + detect_config_path: ${{ inputs.polaris-config-path != '' && inputs.polaris-config-path || null}} + coverity_config_path: ${{ inputs.polaris-coverity-config-path != '' && inputs.polaris-coverity-config-path || null }} + coverity_build_command: ${{ inputs.polaris-coverity-build-command != '' && inputs.polaris-coverity-build-command || null }} + coverity_clean_command: ${{ inputs.polaris-coverity-clean-command != '' && inputs.polaris-coverity-clean-command || null }} + coverity_args: ${{ inputs.polaris-coverity-args != '' && inputs.polaris-coverity-args || null }} + detect_search_depth: ${{ inputs.polaris-detect-search-depth != '' && inputs.polaris-detect-search-depth || null }} + detect_args: ${{ inputs.polaris-detect-args != '' && inputs.polaris-detect-args || null }} + polaris_assessment_mode: ${{ inputs.polaris-assessment-mode }} + polaris_waitForScan: 'true' + polaris_prComment_enabled: true + polaris_branch_parent_name: 'main' + polaris_branch_name: ${{ inputs.github-branch-name }} + polaris_prComment_severities: "CRITICAL,HIGH,MEDIUM" + github_token: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }} + continue-on-error: false + + - name: Check Polaris scan results and fail on HIGH or CRITICAL vulnerabilities + if: ${{ inputs.polaris-fail-on-high == true || inputs.polaris-fail-on-critical == true }} + run: | + echo "Checking Polaris SAST scan results..." + echo "Enforcement policy: HIGH=${{ inputs.polaris-fail-on-high }}, CRITICAL=${{ inputs.polaris-fail-on-critical }}" + + # Parse bridge.log for vulnerability counts + BRIDGE_LOG=".bridge/bridge.log" + + if [ ! -f "$BRIDGE_LOG" ]; then + echo "⚠️ Bridge log not found - failing as precaution" + exit 1 + fi + + # For PR scans, check for NEW issues in the PR changes + # For full scans, check total issues + if [ "${{ inputs.github-event-name }}" == "pull_request" ]; then + echo "This is a PR scan - checking for NEW issues introduced in this PR" + + # Check if PR-specific issues were found + NO_PR_ISSUES=$(grep -i "Could not find.*issues for changes in the current branch" "$BRIDGE_LOG" || echo "") + + if [ -n "$NO_PR_ISSUES" ]; then + echo "✅ No new vulnerabilities introduced in this PR" + echo "Note: Total project issues may exist, but this PR does not introduce new ones" + exit 0 + fi + + # Check if PR issues were added to commenter + PR_ISSUES_FOUND=$(grep -i "Added entry to resource 'commenter.issues'" "$BRIDGE_LOG" || echo "") + + if [ -n "$PR_ISSUES_FOUND" ]; then + echo "❌ NEW vulnerabilities found in this PR" + echo "Check the PR comment for details on the specific issues introduced" + echo "Note: Failing because new issues were detected in PR changes" + exit 1 + fi + else + echo "This is a full scan - checking total project issues" + + # Extract total vulnerability counts from log + HIGH_COUNT=$(grep -oP '"high":\s*\K\d+' "$BRIDGE_LOG" | tail -1 || echo "0") + CRITICAL_COUNT=$(grep -oP '"critical":\s*\K\d+' "$BRIDGE_LOG" | tail -1 || echo "0") + MEDIUM_COUNT=$(grep -oP '"medium":\s*\K\d+' "$BRIDGE_LOG" | tail -1 || echo "0") + + echo "Found total issues - HIGH: $HIGH_COUNT, CRITICAL: $CRITICAL_COUNT, MEDIUM: $MEDIUM_COUNT" + fi + + # Ensure counts are numeric + HIGH_COUNT=${HIGH_COUNT:-0} + CRITICAL_COUNT=${CRITICAL_COUNT:-0} + MEDIUM_COUNT=${MEDIUM_COUNT:-0} + + # Check for policy violations + SHOULD_FAIL=false + + if [ "${{ inputs.polaris-fail-on-critical }}" == "true" ] && [ "$CRITICAL_COUNT" -gt 0 ]; then + echo "❌ Found $CRITICAL_COUNT CRITICAL vulnerabilities (policy violation)" + SHOULD_FAIL=true + fi + + if [ "${{ inputs.polaris-fail-on-high }}" == "true" ] && [ "$HIGH_COUNT" -gt 0 ]; then + echo "❌ Found $HIGH_COUNT HIGH vulnerabilities (policy violation)" + SHOULD_FAIL=true + fi + + if [ "$MEDIUM_COUNT" -gt 0 ]; then + echo "⚠️ Found $MEDIUM_COUNT MEDIUM vulnerabilities (not a policy violation but should be reviewed)" + SHOULD_FAIL=true + fi + + if [ "$SHOULD_FAIL" == "true" ]; then + exit 1 + else + echo "✅ No policy-violating vulnerabilities found" + fi diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index 77cba9f..c9aa6fa 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -29,6 +29,16 @@ name: Download SBOM from Insights and Convert to CSV on: workflow_call: inputs: + github-event-name: + description: 'GitHub event name (pass github.event_name from calling workflow for PR comment detection)' + required: false + type: string + default: '' + github-branch-name: + description: 'GitHub branch name (pass github.ref_name from calling workflow for branch-specific logic)' + required: false + type: string + default: '' version: description: 'Version of the project' required: true @@ -208,7 +218,7 @@ jobs: generate-blackduck-sbom: name: Blackduck SCA Scan (PURPLE) runs-on: ubuntu-latest - if: ${{ inputs.perform-blackduck-sca-scan == true }} + if: ${{ inputs.perform-blackduck-sca-scan == true }} steps: - name: 'Echo inputs' run: | @@ -218,6 +228,7 @@ jobs: echo "DETECT_PROJECT_GROUP_NAME: ${{ inputs.blackduck-project-group-name }}" echo "DETECT_PROJECT_NAME: ${{ inputs.blackduck-project-name }}" echo "DETECT_PROJECT_VERSION_NAME: ${{ inputs.version }}" + echo "GitHub branch: ${{ inputs.github-branch-name }}" - name: Checkout source uses: actions/checkout@v6 @@ -279,6 +290,11 @@ jobs: if [[ -n "${{ inputs.ruby-app-directory }}" ]]; then DETECT_ARGS="${DETECT_ARGS} --detect.source.path=${{ inputs.ruby-app-directory }}" fi + + if [[ "${{ inputs.github-event-name }}" == "pull_request" ]]; then + # RAPID scan for PRs - automatically compares against baseline from target branch + DETECT_ARGS="${DETECT_ARGS} --detect.blackduck.scan.mode=RAPID" + fi echo "DETECT_ARGS=${DETECT_ARGS}" >> $GITHUB_ENV echo "Constructed detect_args: ${DETECT_ARGS}" @@ -311,58 +327,96 @@ jobs: echo "FAILURE_SEVERITIES=${SEVERITIES}" >> $GITHUB_ENV echo "Enforcement policy: ${SEVERITIES}" - - name: BlackDuck SCA scan - id: blackduck-scan + # Full / INTELLIGENT scan — push to main, develop, release branches. + # Persists a baseline to the BlackDuck server so PR scans can diff against it. + - name: BlackDuck SCA Full Scan + id: blackduck-full-scan + if: ${{ inputs.github-event-name != 'pull_request' }} uses: blackduck-inc/black-duck-security-scan@v2.1.1 - continue-on-error: false # Allow pipeline to continue even with policy violations - env: + continue-on-error: false + env: GOPRIVATE: ${{ inputs.go-private-modules }} - DETECT_PROJECT_GROUP_NAME: ${{ inputs.blackduck-project-group-name}} #'Chef-Agents' # , Chef, Chef-Agents, Chef-Automate, Chef-Chef360, Chef-Habitat, Chef-Infrastructure-Server, Chef-Shared-Services + DETECT_PROJECT_GROUP_NAME: ${{ inputs.blackduck-project-group-name }} DETECT_PROJECT_NAME: ${{ inputs.blackduck-project-name }} - DETECT_PROJECT_VERSION_NAME: ${{ inputs.version }} # + DETECT_PROJECT_VERSION_NAME: ${{ inputs.version }} with: - blackducksca_url: ${{ secrets.BLACKDUCK_SBOM_URL }} # BLACKDUCK_URL, should be https://progresssoftware.app.blackduck.com/ + blackducksca_url: ${{ secrets.BLACKDUCK_SBOM_URL }} + blackducksca_token: ${{ secrets.BLACKDUCK_SCA_TOKEN }} + blackducksca_scan_failure_severities: ${{ env.FAILURE_SEVERITIES }} + blackducksca_scan_full: true + detect_args: ${{ env.DETECT_ARGS }} + + # RAPID scan — pull request events only. + # Diffs against the persisted baseline so only violations *new to this PR* are reported. + - name: BlackDuck SCA PR Scan + id: blackduck-pr-scan + if: ${{ inputs.github-event-name == 'pull_request' }} + uses: blackduck-inc/black-duck-security-scan@v2.1.1 + continue-on-error: false + env: + GOPRIVATE: ${{ inputs.go-private-modules }} + DETECT_PROJECT_GROUP_NAME: ${{ inputs.blackduck-project-group-name }} + DETECT_PROJECT_NAME: ${{ inputs.blackduck-project-name }} + DETECT_PROJECT_VERSION_NAME: ${{ inputs.version }} + DETECT_BLACKDUCK_RAPID_COMPARE_MODE: BOM_COMPARE + with: + blackducksca_url: ${{ secrets.BLACKDUCK_SBOM_URL }} # BLACKDUCK_URL, should be https://progresssoftware.app.blackduck.com/ blackducksca_token: ${{ secrets.BLACKDUCK_SCA_TOKEN }} # was BLACKDUCK_API_KEY blackducksca_scan_failure_severities: ${{ env.FAILURE_SEVERITIES }} - blackducksca_scan_full: true # Force INTELLIGENT scan mode for all branches (uploads results to server) + blackducksca_scan_full: false detect_args: ${{ env.DETECT_ARGS }} - # ignore python per https://documentation.blackduck.com/bundle/detect/page/packagemgrs/python.html + github_token: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }} + blackducksca_prComment_enabled: true - name: Check BlackDuck SCA results and report violations if: ${{ always() && (inputs.blackduck-fail-on-blocker == true || inputs.blackduck-fail-on-critical == true || inputs.blackduck-fail-on-major == true) }} run: | BRIDGE_LOG=".bridge/bridge.log" - + BLOCKER_COUNT=0 + CRITICAL_COUNT=0 + MAJOR_COUNT=0 + if [ ! -f "$BRIDGE_LOG" ]; then - echo "⚠️ Bridge log not found" + echo "⚠️ Bridge log not found - skipping violation count check" exit 0 fi - - SEVERITY_LINE=$(grep "Policy Severity counts:" "$BRIDGE_LOG" || true) - - if [ -z "$SEVERITY_LINE" ]; then - echo "⚠️ Policy Severity counts line not found in bridge.log" + + # Works for both RAPID and Full scans: + # - RAPID log has: "Critical and blocking policy violations" → "* Components: N", "* Security: N", etc. + # - Full log has: "Policy Severity counts:" → "N matches have a severity level of BLOCKER", etc. + extract() { grep "$1" "$BRIDGE_LOG" | awk '{print $NF}' | head -1; } + + if grep -q "Critical and blocking policy violations" "$BRIDGE_LOG"; then + # RAPID scan — map categories to severity levels + BLOCKER_COUNT=$(extract "\* Components:" || echo 0) + CRITICAL_COUNT=$(extract "\* Security:" || echo 0) + MAJOR_COUNT=$(extract "\* License:" || echo 0) + OTHER_COUNT=$(extract "\* Other:" || echo 0) + [ "${OTHER_COUNT:-0}" -gt 0 ] && CRITICAL_COUNT=$((CRITICAL_COUNT + OTHER_COUNT)) + elif grep -q "Policy Severity counts:" "$BRIDGE_LOG"; then + # Full scan + BLOCKER_COUNT=$(grep "severity level of BLOCKER" "$BRIDGE_LOG" | grep -oE '^[0-9]+' || echo 0) + CRITICAL_COUNT=$(grep "severity level of CRITICAL" "$BRIDGE_LOG" | grep -oE '^[0-9]+' || echo 0) + MAJOR_COUNT=$(grep "severity level of MAJOR" "$BRIDGE_LOG" | grep -oE '^[0-9]+' || echo 0) + else + echo "⚠️ No policy violation summary found in bridge.log" exit 0 fi - - BLOCKER_COUNT=$(echo "$SEVERITY_LINE" | grep -oE '[0-9]+ match(es)? ha(s|ve) a severity level of BLOCKER' | grep -oE '^[0-9]+' || echo "0") - CRITICAL_COUNT=$(echo "$SEVERITY_LINE" | grep -oE '[0-9]+ match(es)? ha(s|ve) a severity level of CRITICAL' | grep -oE '^[0-9]+' || echo "0") - MAJOR_COUNT=$(echo "$SEVERITY_LINE" | grep -oE '[0-9]+ match(es)? ha(s|ve) a severity level of MAJOR' | grep -oE '^[0-9]+' || echo "0") - + echo "" echo "============================================" echo "BlackDuck SCA Policy Violation Summary" echo "============================================" - echo "BLOCKER violations: $BLOCKER_COUNT" - echo "CRITICAL violations: $CRITICAL_COUNT" - echo "MAJOR violations: $MAJOR_COUNT" + echo "BLOCKER violations: ${BLOCKER_COUNT}" + echo "CRITICAL violations: ${CRITICAL_COUNT}" + echo "MAJOR violations: ${MAJOR_COUNT}" echo "============================================" - + VIOLATIONS="" - [ "${{ inputs.blackduck-fail-on-blocker }}" == "true" ] && [ "$BLOCKER_COUNT" -gt 0 ] && VIOLATIONS="${VIOLATIONS}$BLOCKER_COUNT BLOCKER, " - [ "${{ inputs.blackduck-fail-on-critical }}" == "true" ] && [ "$CRITICAL_COUNT" -gt 0 ] && VIOLATIONS="${VIOLATIONS}$CRITICAL_COUNT CRITICAL, " - [ "${{ inputs.blackduck-fail-on-major }}" == "true" ] && [ "$MAJOR_COUNT" -gt 0 ] && VIOLATIONS="${VIOLATIONS}$MAJOR_COUNT MAJOR, " - + [ "${{ inputs.blackduck-fail-on-blocker }}" == "true" ] && [ "${BLOCKER_COUNT}" -gt 0 ] && VIOLATIONS="${VIOLATIONS}${BLOCKER_COUNT} BLOCKER, " + [ "${{ inputs.blackduck-fail-on-critical }}" == "true" ] && [ "${CRITICAL_COUNT}" -gt 0 ] && VIOLATIONS="${VIOLATIONS}${CRITICAL_COUNT} CRITICAL, " + [ "${{ inputs.blackduck-fail-on-major }}" == "true" ] && [ "${MAJOR_COUNT}" -gt 0 ] && VIOLATIONS="${VIOLATIONS}${MAJOR_COUNT} MAJOR, " + if [ -n "$VIOLATIONS" ]; then echo "" echo "❌ Vulnerabilities Found: ${VIOLATIONS%, }" diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 882805e..018b940 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -44,11 +44,15 @@ jobs: # The first call to the action will invoke setup-trivy and install trivy - name: Generate Trivy Vulnerability Report (JSON) uses: aquasecurity/trivy-action@0.34.2 + env: + TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db:2 + TRIVY_DISABLE_VEX_NOTICE: true with: scan-type: "fs" output: trivy-report.json format: json # can be json, table, template, cyclonedx, sarif, table, spdx, spdx-json - scan-ref: . + scan-ref: '.' + # On a subsequent call to the action we know trivy is already installed so can skip this exit-code: 0 - name: Upload Vulnerability Scan Results @@ -62,11 +66,14 @@ jobs: - name: Generate Trivy Vulnerability Report (TABLE) uses: aquasecurity/trivy-action@0.34.2 + env: + TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db:2 + TRIVY_DISABLE_VEX_NOTICE: true with: scan-type: "fs" output: trivy-report.txt format: table - scan-ref: . + scan-ref: '.' skip-setup-trivy: true exit-code: 0 @@ -97,10 +104,13 @@ jobs: - name: Fail build on High/Critical Vulnerabilities if: ${{ inputs.trivy-fail-on-high || inputs.trivy-fail-on-critical }} uses: aquasecurity/trivy-action@0.34.2 + env: + TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db:2 + TRIVY_DISABLE_VEX_NOTICE: true with: scan-type: "fs" format: table - scan-ref: . + scan-ref: '.' severity: ${{ env.FAILURE_SEVERITIES }} ignore-unfixed: true exit-code: 1 diff --git a/.github/workflows/trufflehog.yml b/.github/workflows/trufflehog.yml index 520d3c1..6e9901d 100644 --- a/.github/workflows/trufflehog.yml +++ b/.github/workflows/trufflehog.yml @@ -3,8 +3,20 @@ name: Trufflehog secret scan on: + push: + pull_request: workflow_call: inputs: + github-event-name: + description: 'GitHub event name (pass github.event_name from calling workflow for PR comment detection)' + required: false + type: string + default: '' + github-branch-name: + description: 'GitHub branch name (pass github.ref_name from calling workflow for branch-specific logic)' + required: false + type: string + default: '' fail-trufflehog-on-secrets-found: description: 'Fail the pipeline if Trufflehog finds verified secrets' required: false @@ -20,34 +32,52 @@ jobs: with: fetch-depth: 0 - - name: TruffleHog secret scan - id: trufflehog-scan + - name: Setup git refs for PR scan + if: ${{ inputs.github-event-name == 'pull_request' }} + run: | + git fetch origin main + git branch main origin/main + + - name: Setup git refs for PR scan + if: ${{ inputs.github-event-name == 'pull_request' }} + run: | + git fetch origin main + git branch main origin/main + + - name: TruffleHog Full secret scan + id: trufflehog-full-scan + if: ${{ inputs.github-event-name != 'pull_request' }} uses: trufflesecurity/trufflehog@main with: path: ./ - extra_args: --only-verified continue-on-error: false - - name: Check results and fail if secrets found - if: ${{ always() && inputs.fail-trufflehog-on-secrets-found == true }} - run: | - # Parse the log output from the trufflehog action - LOG_OUTPUT="${{ steps.trufflehog-scan.outputs.stdout }}" - - # Extract verified_secrets count from JSON output - VERIFIED_COUNT=$(echo "$LOG_OUTPUT" | grep -oE '"verified_secrets":\s*[0-9]+' | grep -oE '[0-9]+' | tail -1 || echo "0") + - name: TruffleHog PR secret scan + id: trufflehog-pr-scan + if: ${{ inputs.github-event-name == 'pull_request' }} + uses: trufflesecurity/trufflehog@main + with: + path: ./ + base: main + head: HEAD + continue-on-error: false + + # - name: Check results and fail if secrets found + # if: ${{ always() && inputs.fail-trufflehog-on-secrets-found == true }} + # run: | + # # Check if TruffleHog step failed (which means secrets were found) + # FULL_SCAN_RESULT="${{ steps.trufflehog-full-scan.outcome }}" + # PR_SCAN_RESULT="${{ steps.trufflehog-pr-scan.outcome }}" - if [ "$VERIFIED_COUNT" -gt 0 ]; then - echo "" - echo "============================================" - echo "❌ Trufflehog Secret Scan Failed" - echo "============================================" - echo "Found $VERIFIED_COUNT verified secret(s)" - echo "============================================" - exit 1 - else - echo "✅ No verified secrets found" - fi + # if [ "$FULL_SCAN_RESULT" == "failure" ] || [ "$PR_SCAN_RESULT" == "failure" ]; then + # echo "" + # echo "============================================" + # echo "❌ Trufflehog Secret Scan Failed" + # echo "============================================" + # exit 1 + # else + # echo "✅ No secrets found" + # fi # --only-verified --fail --github-actions --results=verified,unknown --branch dev # TODO: use the GH_TOKEN --org=progress --token=ghp_xxxxx