From fb912a33e30b80c64169c477e9a49579e93f9176 Mon Sep 17 00:00:00 2001 From: Daniela Plascencia Date: Tue, 8 Oct 2024 20:28:43 +0200 Subject: [PATCH] feat: add automatic vulnerability reports Add the option to report vulnerabilities automatically via Github issues. Fixes #69 --- ...-scan-test-report-issue-publish-rock.yaml} | 22 +++++- ...-modified-and-build-scan-test-publish.yaml | 18 ++++- .../workflows/report-vulnerability-in-gh.yaml | 79 +++++++++++++++++++ .github/workflows/scan-rock.yaml | 58 ++++++++++++-- 4 files changed, 167 insertions(+), 10 deletions(-) rename .github/workflows/{build-scan-test-publish-rock.yaml => build-scan-test-report-issue-publish-rock.yaml} (74%) create mode 100644 .github/workflows/report-vulnerability-in-gh.yaml diff --git a/.github/workflows/build-scan-test-publish-rock.yaml b/.github/workflows/build-scan-test-report-issue-publish-rock.yaml similarity index 74% rename from .github/workflows/build-scan-test-publish-rock.yaml rename to .github/workflows/build-scan-test-report-issue-publish-rock.yaml index 7822f03..98dc91c 100644 --- a/.github/workflows/build-scan-test-publish-rock.yaml +++ b/.github/workflows/build-scan-test-report-issue-publish-rock.yaml @@ -3,6 +3,11 @@ name: Build, scan, and test ROCK on: workflow_call: inputs: + report-vulnerabilities: + description: "Whether to report security vulnerabilities through Github issues." + required: false + default: false + type: boolean rock-dir: description: "Path to rock directory, i.e. directory containing rockcraft.yaml" required: true @@ -27,8 +32,12 @@ on: default: "" required: false type: string + severity: + description: "Comma separated list of severities of vulnerabilities to scanned for and displayed" + required: false + type: string + default: "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL" jobs: - build: uses: ./.github/workflows/build-rock.yaml with: @@ -43,6 +52,17 @@ jobs: rock-artifact: ${{ needs.build.outputs.rock-artifact}} rock-reference: ${{ needs.build.outputs.rock-reference }} rock-filename: ${{ needs.build.outputs.rock-filename }} + report-vulnerabilities: ${{ inputs.report-vulnerabilities }} + severity: ${{ inputs.severity }} + + report-vulnerability: + needs: [scan, build] + uses: ./.github/workflows/report-vulnerability-in-gh.yaml + secrets: inherit + if: ${{ always() && (needs.scan.result == 'failure' && needs.build.result == 'success') }} + with: + issue-title: 'Vulnerabilities found for' + rock-reference: ${{ needs.build.outputs.rock-reference }} sanity-tests: needs: build diff --git a/.github/workflows/get-rocks-modified-and-build-scan-test-publish.yaml b/.github/workflows/get-rocks-modified-and-build-scan-test-publish.yaml index 0587af6..6d04137 100644 --- a/.github/workflows/get-rocks-modified-and-build-scan-test-publish.yaml +++ b/.github/workflows/get-rocks-modified-and-build-scan-test-publish.yaml @@ -2,7 +2,15 @@ name: Get ROCKs modified and build-scan-test-publish them on: workflow_call: + secrets: + GH_TOKEN: + required: true inputs: + report-vulnerabilities: + description: "Whether to report security vulnerabilities through Github issues." + required: false + default: false + type: boolean rockcraft-channel: description: "Rockcraft channel e.g. latest/stable" required: false @@ -23,6 +31,12 @@ on: default: "" required: false type: string + severity: + description: "Comma separated list of severities of vulnerabilities to scanned for and displayed" + required: false + type: string + default: "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL" + jobs: get-rock-paths-modified: @@ -35,11 +49,13 @@ jobs: fail-fast: false matrix: rock-dir: ${{ fromJson(needs.get-rock-paths-modified.outputs.paths) }} - uses: ./.github/workflows/build-scan-test-publish-rock.yaml + uses: ./.github/workflows/build-scan-test-report-issue-publish-rock.yaml secrets: inherit with: + report-vulnerabilities: ${{ inputs.report-vulnerabilities }} rock-dir: ${{ matrix.rock-dir }} microk8s-channel: ${{ inputs.microk8s-channel }} juju-channel: ${{ inputs.juju-channel }} rockcraft-channel: ${{ inputs.rockcraft-channel }} python-version: ${{ inputs.python-version }} + severity: ${{ inputs.severity }} diff --git a/.github/workflows/report-vulnerability-in-gh.yaml b/.github/workflows/report-vulnerability-in-gh.yaml new file mode 100644 index 0000000..fdd7aa2 --- /dev/null +++ b/.github/workflows/report-vulnerability-in-gh.yaml @@ -0,0 +1,79 @@ +name: Report vulnerability issues via Github +on: + workflow_call: + inputs: + issue-title: + description: The title of the issue to be created/edited + required: true + type: string + issue-labels: + description: A comma separated list of labels + required: false + type: string + default: "bug" + rock-reference: + description: "Rock reference to be used in order to copy the image to docker cache. + It consists of :." + required: true + type: string + +jobs: + report-vulns: + name: Create or edit issues for reporting vulnerabilities + runs-on: ubuntu-22.04 + steps: + - name: Install tools + run: | + sudo snap install gh + sudo snap install jq + + - name: Generate image name + id: image-name + run: | + IMAGE_NAME=$(echo ${{ inputs.rock-reference }} | sed 's/\:/-/g') + echo "image-name=${IMAGE_NAME}" >> "$GITHUB_OUTPUT" + + - name: Get issue number if exists + id: get-issue-number + run: | + set -xeu + export GH_TOKEN=${{ secrets.GH_TOKEN }} + EXPECTED_TITLE=$(echo "${{ inputs.issue-title }} ${{ inputs.rock-reference }}") + ISSUE_NUMBER=$(gh issue list --repo $GITHUB_REPOSITORY --limit 500 --json "number,title" | jq -r --arg expected_title "$EXPECTED_TITLE" '.[] | select(.title == $expected_title) | .number') + echo "issue-number=$ISSUE_NUMBER" >> $GITHUB_OUTPUT + + - name: Download report + uses: actions/download-artifact@v4 + with: + name: trivy-report-${{ steps.image-name.outputs.image-name }} + + - name: Issue body + id: issue-body + run: | + set -xeu + EXPECTED_TITLE=$(echo "${{ inputs.issue-title }} ${{ inputs.rock-reference }}") + echo "## $EXPECTED_TITLE" > issue.md + echo "" >> issue.md + echo "\`\`\`" >> issue.md + cat trivy-report-${{ steps.image-name.outputs.image-name }}.txt >> issue.md + echo "\`\`\`" >> issue.md + echo "" >> issue.md + echo -e "\nDetails: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> issue.md + echo "issue-body-file=issue.md" >> "$GITHUB_OUTPUT" + + - name: Report failures via Github issue + run: | + export GH_TOKEN=${{ secrets.GH_TOKEN }} + EXPECTED_TITLE=$(echo "${{ inputs.issue-title }} ${{ inputs.rock-reference }}") + if [ -z ${{ steps.get-issue-number.outputs.issue-number }} ]; then + echo "---- Creating issue ----" + gh issue create --repo $GITHUB_REPOSITORY \ + --title "$EXPECTED_TITLE" \ + --label "${{ inputs.issue-labels }}" \ + --body-file "${{ steps.issue-body.outputs.issue-body-file }}" + else + echo "---- Editing issue ${{ steps.get-issue-number.outputs.issue-number }}----" + gh issue edit --repo $GITHUB_REPOSITORY ${{ steps.get-issue-number.outputs.issue-number }} \ + --title "$EXPECTED_TITLE" \ + --body-file "${{ steps.issue-body.outputs.issue-body-file }}" + fi diff --git a/.github/workflows/scan-rock.yaml b/.github/workflows/scan-rock.yaml index 52001a1..169875a 100644 --- a/.github/workflows/scan-rock.yaml +++ b/.github/workflows/scan-rock.yaml @@ -3,6 +3,11 @@ name: Scan on: workflow_call: inputs: + report-vulnerabilities: + description: "Whether to report security vulnerabilities through Github issues." + required: false + default: false + type: boolean rock-artifact: description: "Name of the artifact from which the ROCK will be downloaded." required: true @@ -16,10 +21,19 @@ on: description: "Filename of the .rock file" required: true type: string + severity: + description: "Comma separated list of severities of vulnerabilities to scanned for and displayed" + required: false + type: string + default: "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL" jobs: scan: name: Scan ${{ inputs.rock-reference }} runs-on: ubuntu-22.04 + outputs: + image-name: ${{ steps.image-name.outputs.image-name }} + strategy: + fail-fast: false steps: # Ideally we'd use self-hosted runners, but this effort is still not stable. # This action will remove unused software (dotnet, haskell, android libs, codeql, @@ -38,7 +52,7 @@ jobs: - name: Install Rockcraft run: sudo snap install rockcraft --classic --edge - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: ${{ inputs.rock-artifact }} @@ -48,22 +62,50 @@ jobs: sudo rockcraft.skopeo --insecure-policy copy oci-archive:${{ inputs.rock-filename }} docker-daemon:rock:tag echo "image=rock:tag" >> "$GITHUB_OUTPUT" + - name: Set up inputs for scan + id: set-up-inputs + run: | + echo "exit-code=1" >> "$GITHUB_OUTPUT" + if ${{ inputs.report-vulnerabilities == 'false' }}; then + echo "exit-code=0" >> "$GITHUB_OUTPUT" + fi + + - name: Generate image name + id: image-name + run: | + IMAGE_NAME=$(echo ${{ inputs.rock-reference }} | sed 's/\:/-/g') + echo "image-name=${IMAGE_NAME}" >> "$GITHUB_OUTPUT" + - name: Scan for vulnerabilities id: scan - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@0.25.0 + # Workaround for https://github.com/aquasecurity/trivy-action/issues/389 + env: + TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db:2 + TRIVY_JAVA_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-java-db:1 with: scan-type: 'image' image-ref: '${{ steps.rock_in_docker.outputs.image }}' - format: 'json' - output: 'trivy-report-${{ inputs.rock-artifact }}.json' + format: 'table' + output: 'trivy-report-${{ steps.image-name.outputs.image-name }}.txt' ignore-unfixed: true timeout: '50m0s' + exit-code: ${{ steps.set-up-inputs.outputs.exit-code }} + severity: ${{ inputs.severity }} + # NOTE: pebble is flagged with a HIGH vuln because of golang.org/x/crypto + # CVE-2021-43565, CVE-2022-27191 + skip-files: '/bin/pebble,/usr/bin/pebble,usr/bin/pebble,bin/pebble' - name: Print vulnerabilities report - run: cat trivy-report-${{ inputs.rock-artifact }}.json + # The report should be printed regardless of the success of the previous step + if: success() || failure() + run: cat trivy-report-${{ steps.image-name.outputs.image-name }}.txt - name: Upload Trivy reports - uses: actions/upload-artifact@v3 + # The report should be uploaded regardless of the success of the previous steps + if: success() || failure() + uses: actions/upload-artifact@v4 with: - name: trivy-report-${{ inputs.rock-artifact }} - path: trivy-report-${{ inputs.rock-artifact }}.json + compression-level: 0 + name: trivy-report-${{ steps.image-name.outputs.image-name }} + path: trivy-report-${{ steps.image-name.outputs.image-name }}.txt