diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml new file mode 100644 index 00000000000..7af4d7f4b0c --- /dev/null +++ b/.github/workflows/trivy-scan.yml @@ -0,0 +1,129 @@ +name: Trivy Scan Result + +on: + workflow_run: + workflows: ["Trivy Scan Trigger"] + types: + - completed + +permissions: + contents: read + +jobs: + trivy_scan: + if: github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + permissions: + contents: write # Required to checkout the PR's head SHA. + outputs: + pr_number: ${{ steps.pr_context.outputs.pr_number }} + + steps: + # For some reason with workflow_run.id, download-artifact does not work. + # Github Docs explicity provide an example of using github-script to download artifacts. + - name: 'Download artifact' + uses: actions/github-script@v7 + with: + script: | + let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.payload.workflow_run.id, + }); + let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => { + return artifact.name == "pr-context-for-scan" + })[0]; + let download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip', + }); + const fs = require('fs'); + fs.writeFileSync('pr-context-for-scan.zip', Buffer.from(download.data)); + + - name: 'Unzip artifact to pr-context' + run: unzip pr-context-for-scan.zip -d pr-context + + - name: Setup PR context + id: pr_context + run: | + pr_number=$(cat pr-context/pr_number) + echo "pr_number=$pr_number" >> $GITHUB_OUTPUT + + - name: Load K3s Image + run: docker load -i pr-context/k3s.tar + + - name: Download Rancher's VEX Hub report + run: curl -fsSO https://raw.githubusercontent.com/rancher/vexhub/refs/heads/main/reports/rancher.openvex.json + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@0.31.0 + with: + image-ref: 'rancher/k3s:latest' + format: 'table' + severity: "HIGH,CRITICAL" + output: "trivy-report.txt" + env: + TRIVY_VEX: rancher.openvex.json + TRIVY_SHOW_SUPPRESSED: true + + - name: Upload Trivy Report + uses: actions/upload-artifact@v4 + with: + name: trivy-report + path: trivy-report.txt + retention-days: 2 + if-no-files-found: error + + report_results: + needs: trivy_scan + if: always() # Run even if the scan fails. + runs-on: ubuntu-latest + permissions: + pull-requests: write # Required to post comments. + + steps: + - name: Download Trivy Report artifact + uses: actions/download-artifact@v4 + if: needs.trivy_scan.result == 'success' + with: + name: trivy-report + path: . + + - name: Add Trivy Report to PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + SCAN_RESULT: ${{ needs.trivy_scan.result }} + PR_NUMBER: ${{ needs.trivy_scan.outputs.pr_number }} + run: | + if [[ "$SCAN_RESULT" == "failure" ]]; then + gh issue comment $PR_NUMBER -b ":x: Trivy scan action failed, check logs :x:" + exit 0 + fi + + if [ -s trivy-report.txt ] && [ -n "$(grep -v '^\s*$' trivy-report.txt)" ]; then + echo '```' | cat - trivy-report.txt > temp && mv temp trivy-report.txt + echo '```' >> trivy-report.txt + gh issue comment $PR_NUMBER -F trivy-report.txt + else + echo ':star2: No High or Critical CVEs Found :star2:' > trivy-report.txt + gh issue comment $PR_NUMBER -F trivy-report.txt + fi + + remove_label: + if: always() # Run even if the scan fails. + needs: trivy_scan + runs-on: ubuntu-latest + permissions: + pull-requests: write # Required to remove labels from the PR. + + steps: + - name: Remove 'scan-with-trivy' label + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + PR_NUMBER: ${{ needs.trivy_scan.outputs.pr_number }} + run: | + gh pr edit $PR_NUMBER --remove-label "scan-with-trivy" diff --git a/.github/workflows/trivy-trigger.yml b/.github/workflows/trivy-trigger.yml new file mode 100644 index 00000000000..0628bba6461 --- /dev/null +++ b/.github/workflows/trivy-trigger.yml @@ -0,0 +1,68 @@ +name: Trivy Scan Trigger + +# This workflow is triggered when a pull request is labeled with 'scan-with-trivy'. +# This can only be initiated by a user who is a member of the k3s-io organization and has write permissions. +# It isolates the built of k3s within a unprivileged enviroment. +# The follow up unprivileged workflow will then use the artifact created here to run the scan +# and report the results back to the PR. + +on: + pull_request: + types: [labeled] + +permissions: + contents: read + +jobs: + trigger-scan: + if: github.event.label.name == 'scan-with-trivy' + runs-on: ubuntu-latest + steps: + - name: Verify actor is a member of k3s-io organization and has write permissions + uses: actions/github-script@v7 + with: + script: | + const org = 'k3s-io'; + const actor = context.actor; + const { repo, owner } = context.repo; + + try { + const result = await github.rest.orgs.checkMembershipForUser({ + org, + username: actor, + }); + } catch (error) { + core.setFailed(`User ${actor} is not an public member of the ${org} organization`); + } + + const { data: { permission } } = await github.rest.repos.getCollaboratorPermissionLevel({ + owner, + repo, + username: actor + }); + + if (permission !== 'admin' && permission !== 'write') { + core.setFailed(`User @${actor} does not have write permission. Scan can only be triggered by repository collaborators with write access.`); + } + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Build And Save K3s Image + run: | + make local-image + make tag-image-latest + docker save -o k3s.tar rancher/k3s:latest + + - name: Create PR context artifact + run: | + mkdir -p pr-context + echo "${{ github.event.pull_request.number }}" > pr-context/pr_number + mv k3s.tar pr-context/k3s.tar + + - name: Upload PR context artifact + uses: actions/upload-artifact@v4 + with: + name: pr-context-for-scan + path: pr-context/ + retention-days: 1 diff --git a/.github/workflows/trivy.yaml b/.github/workflows/trivy.yaml deleted file mode 100644 index 69caacb26fd..00000000000 --- a/.github/workflows/trivy.yaml +++ /dev/null @@ -1,104 +0,0 @@ -name: PR Comment Triggered Trivy Scan - -on: - issue_comment: - types: [created] - -permissions: - contents: read - -jobs: - trivy_scan: - if: github.event.issue.pull_request && github.event.comment.body == '/trivy' - runs-on: ubuntu-latest - permissions: - pull-requests: read - steps: - - name: Check if comment author is a member of k3s-dev team - uses: actions/github-script@v7 - with: - # Catch 404 errors if user is not a member of the organization - # 302 is expected as the GHA is not a member of the organization - # Users must be set their membership to public for this to work - # https://github.com/orgs/k3s-io/people - script: | - const org = context.repo.owner; - const username = context.payload.comment.user.login; - try { - const result = await github.rest.orgs.checkMembershipForUser({ - org, - username, - }); - } catch (error) { - core.setFailed(`User ${username} is not an public member of the ${org} organization`); - } - - name: Checkout PR code - uses: actions/checkout@v4 - with: - ref: refs/pull/${{ github.event.issue.number }}/head - - - name: Build K3s Image - run: | - make local-image - make tag-image-latest - - - name: Download Rancher's VEX Hub report - run: curl -fsSO https://raw.githubusercontent.com/rancher/vexhub/refs/heads/main/reports/rancher.openvex.json - - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@0.31.0 - with: - image-ref: 'rancher/k3s:latest' - format: 'table' - severity: "HIGH,CRITICAL" - output: "trivy-report.txt" - env: - TRIVY_VEX: rancher.openvex.json - TRIVY_SHOW_SUPPRESSED: true - - - name: Upload Trivy Report - uses: actions/upload-artifact@v4 - with: - name: trivy-report - path: trivy-report.txt - retention-days: 2 - if-no-files-found: error - - trivy_report: - needs: trivy_scan - runs-on: ubuntu-latest - permissions: - pull-requests: write - env: - GH_TOKEN: ${{ github.token }} - GH_REPO: ${{ github.repository }} - steps: - - name: Download Trivy Report - uses: actions/download-artifact@v4 - with: - name: trivy-report - - - name: Add Trivy Report to PR - run: | - if [ -s trivy-report.txt ] && [ -n "$(grep -v '^\s*$' trivy-report.txt)" ]; then - echo '```' | cat - trivy-report.txt > temp && mv temp trivy-report.txt - echo '```' >> trivy-report.txt - gh issue comment ${{ github.event.issue.number }} -F trivy-report.txt - else - echo ':star2: No High or Critical CVEs Found :star2:' > trivy-report.txt - gh issue comment ${{ github.event.issue.number }} -F trivy-report.txt - fi - - trivy_failure: - needs: trivy_scan - runs-on: ubuntu-latest - if: always() && needs.trivy_scan.result == 'failure' - permissions: - pull-requests: write - env: - GH_TOKEN: ${{ github.token }} - GH_REPO: ${{ github.repository }} - steps: - - name: Report Failure - run: | - gh issue comment ${{ github.event.issue.number }} -b ":x: Trivy scan action failed, check logs :x:"