-
Notifications
You must be signed in to change notification settings - Fork 0
ROX-30730: add scan-image-vulnerabilities action #72
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
Open
tommartensen
wants to merge
29
commits into
main
Choose a base branch
from
tm/ROX-image-vuln-check
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
a1ae34d
ROX-30730: add action for iamge-vulnerability-check
tommartensen ffefc4f
remove superfluous step ID
tommartensen 61ef929
Revert "remove superfluous step ID"
tommartensen 6bb87b8
prepend only quay.io, require repository to be the passed in parameter
tommartensen 9bbec44
quote bash command
tommartensen 7d11292
refactor: markdown table, only fail if fixable CVEs
tommartensen bbbfed2
make script executable
tommartensen 904332d
cleanups
tommartensen 48015ea
update documentation
tommartensen c9fe987
update summary
tommartensen 85a652f
add title prefix
tommartensen 4c9379e
fix typo
tommartensen d6f9927
fix formatting
tommartensen 7025a2a
handle no vulnerabilities found
tommartensen 09af701
add message explaining status
tommartensen a6584fd
re-format
tommartensen 6e4b676
add comments for the csv->markdown conversion
tommartensen 75c2a6d
touches
tommartensen 92b555b
update README
tommartensen 05a2a6e
prefer 'scan' or 'check' terminology
tommartensen e6f53b0
addressing reviewer feedback
tommartensen 02bdcb2
update script description
tommartensen eb950c6
update README to address review comments
tommartensen 380f2f4
extract vuln table header from jq
tommartensen 01751fb
put fixable findings first in table, then unfixable; fix counting of …
tommartensen 7334b3b
s/vulnerabilities/findings
tommartensen b7c19e5
refactor fixable findings assertion to common function
tommartensen 303fbdb
apply code suggestions
tommartensen 35cf37d
resolve are_blocking_vulns_present hint
tommartensen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| # Scan Image Vulnerabilities | ||
|
|
||
| Scan a container image in Quay.io for vulnerabilities using `roxctl image scan` and fail if fixable critical or important vulnerabilities are found. | ||
|
|
||
| This action waits for an image to be available in Quay.io, scans it using roxctl, and generates a detailed vulnerability report in the GitHub step summary, while making the raw report available as JSON in the workspace as `scan-result.json`. | ||
|
|
||
| ## Required permissions | ||
|
|
||
| ```yaml | ||
| permissions: | ||
| # Needed for stackrox/central-login to create the JWT token. | ||
| id-token: write | ||
| ``` | ||
|
|
||
| ## All options | ||
|
|
||
| | Input | Description | Required | Default | | ||
| | ----------------------------------------- | -------------------------------------------------- | -------- | --------- | | ||
| | [image](#image) | Image name (without registry prefix) | Yes | | | ||
| | [version](#version) | Image version tag | Yes | | | ||
| | [wait-limit](#wait-limit) | Maximum time to wait for image (seconds) | No | `"7200"` | | ||
| | [summary-prefix](#summary-prefix) | Title prefix for the GitHub step summary | Yes | | | ||
| | [quay-bearer-token](#quay-bearer-token) | Quay.io bearer token for wait-for-image | Yes | | | ||
| | [central-url](#central-url) | ACS Central URL | Yes | | | ||
|
|
||
| ### Detailed options | ||
|
|
||
| #### image | ||
|
|
||
| Image name without the registry prefix. The action will automatically prepend `quay.io/` to construct the full image reference. | ||
|
|
||
| Example: `"rhacs-eng/main"` | ||
|
|
||
| #### version | ||
|
|
||
| Image version tag to scan. | ||
|
|
||
| Example: `"3.76.1"` | ||
|
|
||
| #### wait-limit | ||
|
|
||
| Maximum time in seconds to wait for the image to be available on Quay.io before failing. | ||
|
|
||
| Default: `"7200"` (2 hours) | ||
|
|
||
| #### summary-prefix | ||
|
|
||
| Prefix for the vulnerability report in the GitHub step summary. Use this to help users of the action classify images into groups when multiple matrix scans are performed in a workflow. | ||
|
|
||
| Example: `"Upstream Image Scan Results"` | ||
|
|
||
| #### quay-bearer-token | ||
|
|
||
| Bearer token for authenticating with the Quay.io API. This is required by the wait-for-image action to check if the image is available. | ||
|
|
||
| #### central-url | ||
|
|
||
| URL of the ACS/RHACS Central instance to use for scanning. | ||
|
|
||
| Example: `"https://central.example.com"` | ||
|
|
||
| ## Usage | ||
|
|
||
| The action integrates with the [stackrox/central-login](https://github.com/stackrox/central-login) action, which uses OIDC login for authentication of the `roxctl` CLI. | ||
| The ACS Central needs to be configured to allow exchanging tokens from GitHub Actions workflow runs. | ||
|
|
||
| Additionally, an image integration for Quay.io must be configured in the ACS Central so that it can pull images requested for scanning. | ||
|
|
||
| ```yaml | ||
| name: Scan image for vulnerabilities | ||
|
|
||
| jobs: | ||
| scan: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| id-token: write # Required for ACS Central OIDC login | ||
| steps: | ||
| - uses: stackrox/actions/release/scan-image-vulnerabilities@v1 | ||
| with: | ||
| image: rhacs-eng/main | ||
| version: 3.76.1 | ||
| summary-prefix: "Main Image Scan" | ||
| quay-bearer-token: ${{ secrets.QUAY_BEARER_TOKEN }} | ||
| central-url: https://central.example.com | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| name: "Scan Image Vulnerabilities" | ||
| description: "Scan an image for vulnerabilities and fail if fixable critical or important ones are found" | ||
|
|
||
| inputs: | ||
| image: | ||
| description: "Image name (without registry prefix)" | ||
| required: true | ||
| version: | ||
| description: "Image version tag" | ||
| required: true | ||
| wait-limit: | ||
| description: "Maximum time to wait for image (seconds)" | ||
| required: false | ||
| default: "7200" | ||
| summary-prefix: | ||
| description: "Title prefix for the GitHub step summary" | ||
| required: true | ||
| quay-bearer-token: | ||
| description: "Quay.io bearer token for wait-for-image" | ||
| required: true | ||
| central-url: | ||
| description: "ACS Central URL" | ||
| required: true | ||
|
|
||
| runs: | ||
| using: composite | ||
| steps: | ||
| - name: "Wait for image to be built: quay.io/${{ inputs.image }}:${{ inputs.version }}" | ||
| uses: stackrox/actions/release/wait-for-image@v1 | ||
msugakov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| with: | ||
| token: ${{ inputs.quay-bearer-token }} | ||
| image: ${{ inputs.image }}:${{ inputs.version }} | ||
| limit: ${{ inputs.wait-limit }} | ||
|
|
||
| - name: Central login | ||
| uses: stackrox/central-login@v1 | ||
| with: | ||
| endpoint: ${{ inputs.central-url }} | ||
|
|
||
| - name: Install roxctl | ||
| uses: stackrox/roxctl-installer-action@v1 | ||
| with: | ||
| central-endpoint: ${{ inputs.central-url }} | ||
| central-token: ${{ env.ROX_API_TOKEN }} | ||
|
|
||
| - name: Scan image for fixable vulnerabilities | ||
| shell: bash | ||
| run: | | ||
| set -uo pipefail | ||
| "${{ github.action_path }}/../../common/common.sh" \ | ||
| "${{ github.action_path }}/scan-image-vulnerabilities.sh" \ | ||
| "quay.io/${{ inputs.image }}:${{ inputs.version }}" \ | ||
| "${{ inputs.summary-prefix }}" | ||
122 changes: 122 additions & 0 deletions
122
release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| #!/usr/bin/env bash | ||
| # | ||
| # Scans an image for vulnerabilities and prints a summary of the findings. | ||
| # For the purpose of this script, a finding is an image component in a version that is affected by a CVE. | ||
| # There may be multiple findings for the same image component in a version, if the component has multiple CVEs. | ||
| # | ||
| # Local run: | ||
| # | ||
| # test/local-env.sh release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh <image> <summary-prefix> | ||
| # | ||
| set -euo pipefail | ||
|
|
||
| function main() { | ||
| local IMAGE="${1:-}" | ||
| local SUMMARY_PREFIX="${2:-}" | ||
| local result_path="scan-result.json" | ||
|
|
||
| check_not_empty \ | ||
| IMAGE \ | ||
| SUMMARY_PREFIX | ||
|
|
||
| scan_image "$IMAGE" "$result_path" | ||
|
|
||
| # Count the total and fixable number of findings for each severity. | ||
| # Use associative arrays to store counts by severity. | ||
| # Arrays are used via nameref parameters in print_summary function. | ||
| # shellcheck disable=SC2034 | ||
| declare -A total_counts | ||
| # shellcheck disable=SC2034 | ||
| declare -A fixable_counts | ||
|
|
||
| # shellcheck disable=SC2034 | ||
| for severity in CRITICAL IMPORTANT MODERATE LOW; do | ||
| total_counts[$severity]="$(count_total_findings "$severity" "$result_path")" | ||
| fixable_counts[$severity]="$(count_fixable_findings "$severity" "$result_path")" | ||
| done | ||
|
|
||
| gh_summary "### $SUMMARY_PREFIX $IMAGE" | ||
| print_summary total_counts fixable_counts | ||
|
|
||
| # Print the findings table in a collapsible section. | ||
| # For the table to render correctly, we need to add a newline after the summary. | ||
| gh_summary "<details><summary>Click to expand details</summary>\n" | ||
| gh_summary "$(print_details_table "$result_path")" | ||
| gh_summary "</details>" | ||
|
|
||
| # Fail the build if any fixable findings of relevant severity are present. | ||
| if are_blocking_vulns_present fixable_counts; then | ||
| exit 1 | ||
| fi | ||
| } | ||
|
|
||
| # Scans the image for vulnerabilities. | ||
| function scan_image() { | ||
| local image="$1" | ||
| local result_path="$2" | ||
| roxctl image scan --output=json --force \ | ||
| --image="${image}" | tee "$result_path" | ||
msugakov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| gh_output result-path "$result_path" | ||
| } | ||
|
|
||
| # Counts the number of findings for a given severity. | ||
| function count_total_findings() { | ||
| local severity="$1" | ||
| local result_path="$2" | ||
| jq "[.result.vulnerabilities // [] | .[] | select(.cveSeverity == \"$severity\")] | length" "$result_path" | ||
| } | ||
|
|
||
| # Counts the number of fixable findings for a given severity. | ||
| function count_fixable_findings() { | ||
| local severity="$1" | ||
| local result_path="$2" | ||
| jq "[.result.vulnerabilities // [] | .[] | select(.cveSeverity == \"$severity\" and .componentFixedVersion != \"\")] | length" "$result_path" | ||
tommartensen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| # Prints the vulnerability status and an overview table of the findings counts. | ||
| function print_summary() { | ||
| local -n total_counts_ref=$1 | ||
| local -n fixable_counts_ref=$2 | ||
|
|
||
| if are_blocking_vulns_present fixable_counts_ref; then | ||
| local message="Found fixable critical or important vulnerabilities." | ||
|
|
||
| gh_log "error" "$message See the step summary for details." | ||
| gh_summary "Status: ❌" | ||
| gh_summary "> $message" | ||
msugakov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| else | ||
| gh_summary "Status: ✅" | ||
| gh_summary "> No fixable critical or important vulnerabilities found." | ||
| fi | ||
|
|
||
| gh_summary "" | ||
| gh_summary "| Severity | Total | Fixable |" | ||
| gh_summary "| --- | --- | --- |" | ||
| for severity in CRITICAL IMPORTANT MODERATE LOW; do | ||
| gh_summary "| $severity | ${total_counts_ref[$severity]} | ${fixable_counts_ref[$severity]} |" | ||
| done | ||
| } | ||
|
|
||
| # Checks if any fixable findings of relevant severity are present. | ||
| function are_blocking_vulns_present() { | ||
| local -n fixable_counts_map="$1" | ||
| (( fixable_counts_map[CRITICAL] > 0 || fixable_counts_map[IMPORTANT] > 0 )) | ||
| } | ||
|
|
||
| # Prints a markdown table of the findings, sorted by severity with fixable findings first. | ||
| # Each row contains left, right and column separators added by jq's join function. | ||
| function print_details_table() { | ||
| local result_path="$1" | ||
| echo "| COMPONENT | VERSION | CVE | SEVERITY | FIXED_VERSION | LINK |" | ||
| echo "| --- | --- | --- | --- | --- | --- |" | ||
| jq -r ' | ||
| .result.vulnerabilities // [] | ||
| | sort_by([ | ||
| (if ((.componentFixedVersion // "") != "") then 0 else 1 end), | ||
| ({"CRITICAL":0,"IMPORTANT":1,"MODERATE":2,"LOW":3}[.cveSeverity] // 4) | ||
| ]) | ||
| | (.[] | [.componentName // "", .componentVersion // "", .cveId // "", .cveSeverity // "", .componentFixedVersion // "", .cveInfo // ""] | "| " + join(" | ") + " |") | ||
| ' "$result_path" | ||
| } | ||
|
|
||
| main "$@" | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.