Move to more secure split trivy workflow based on labels, not comments (#12592)

Signed-off-by: Derek Nola <derek.nola@suse.com>
This commit is contained in:
Derek Nola 2025-07-08 09:12:44 -07:00 committed by GitHub
parent 7ab7865530
commit c3a4c8df9a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 197 additions and 104 deletions

129
.github/workflows/trivy-scan.yml vendored Normal file
View file

@ -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"

68
.github/workflows/trivy-trigger.yml vendored Normal file
View file

@ -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

View file

@ -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:"