Skip to content

Trivy Periodic Image Scan #9

Trivy Periodic Image Scan

Trivy Periodic Image Scan #9

---
#
# This workflow scans the latest published container image
# for new vulnerabilities daily, publishing findings to
# the GitHub Security tab. If vulnerabilities are found,
# it bumps the patch version and triggers a rebuild.
#
name: Trivy Periodic Image Scan
on:
schedule:
- cron: "0 0 * * *" # run daily
workflow_dispatch: {}
jobs:
get-image-reference:
runs-on: ubuntu-latest
steps:
- name: Convert repo name to lower case
id: to_lower_case
run: |
# While GitHub repos can be mixed case,
# Docker images can only be lower case
repo_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')
echo "repo_name=$repo_name" >> $GITHUB_OUTPUT
- name: Find current version
id: find_version
uses: mathieudutour/github-tag-action@a22cf08638b34d5badda920f9daf6e72c477b07b # v6.2
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
dry_run: true # setting to 'true' means no new version is created
outputs:
image_repo: ghcr.io/${{ steps.to_lower_case.outputs.repo_name }}
image_tag: ${{ steps.find_version.outputs.previous_tag }}
permissions:
contents: read
periodic-scan:
needs: get-image-reference
uses: "./.github/workflows/trivy.yml"
with:
SOURCE_TYPE: image
IMAGE_NAME: ${{ needs.get-image-reference.outputs.image_repo }}:${{ needs.get-image-reference.outputs.image_tag }}
EXIT_CODE: 1
permissions:
contents: read
deployments: write
security-events: write
# If scan failed, compute next version (dry run) and attempt a rebuild.
# The tag is only created after a successful rebuild to prevent
# infinite bump loops when a CVE fix requires a major version upgrade
# that a simple rebuild cannot provide.
compute-next-version:
needs: periodic-scan
runs-on: ubuntu-latest
if: ${{ !cancelled() && needs.periodic-scan.outputs.trivy_conclusion == 'failure' }}
steps:
- name: Compute next version (dry run — no tag created)
id: tag_version
uses: mathieudutour/github-tag-action@a22cf08638b34d5badda920f9daf6e72c477b07b # v6.2
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
dry_run: true
- name: Parse new version
id: parsed
uses: booxmedialtd/ws-action-parse-semver@7784200024d6b3fc01253e617ec0168daf603de3 # v1.4.7
with:
input_string: ${{ steps.tag_version.outputs.new_version }}
outputs:
new_tag: ${{ steps.tag_version.outputs.new_tag }}
new_version: ${{ steps.tag_version.outputs.new_version }}
new_major_minor: ${{ steps.parsed.outputs.major }}.${{ steps.parsed.outputs.minor }}
permissions:
contents: read
update-image:
needs: [get-image-reference, periodic-scan, compute-next-version]
if: ${{ !cancelled() && needs.periodic-scan.outputs.trivy_conclusion == 'failure' && needs.compute-next-version.result == 'success' }}
uses: "./.github/workflows/docker_build.yml"
with:
REF_TO_CHECKOUT: ${{ needs.get-image-reference.outputs.image_tag }}
IMAGE_REFERENCES: "${{ needs.get-image-reference.outputs.image_repo }}:${{ needs.compute-next-version.outputs.new_tag }},${{ needs.get-image-reference.outputs.image_repo }}:${{ needs.compute-next-version.outputs.new_major_minor }}"
permissions:
contents: read
deployments: write
security-events: write
packages: write
actions: read # Required by docker_build.yml's nested trivy-scan job
# Only create the git tag after the rebuilt image passes Trivy and is pushed
create-tag:
needs: [periodic-scan, update-image]
runs-on: ubuntu-latest
if: ${{ !cancelled() && needs.periodic-scan.outputs.trivy_conclusion == 'failure' && needs.update-image.result == 'success' }}
steps:
- name: Bump version and push tag
uses: mathieudutour/github-tag-action@a22cf08638b34d5badda920f9daf6e72c477b07b # v6.2
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
permissions:
contents: write
# If the rebuild still has vulnerabilities, open a GitHub issue for manual triage
# instead of looping endlessly
alert-on-failure:
needs: [periodic-scan, update-image]
runs-on: ubuntu-latest
if: ${{ !cancelled() && needs.periodic-scan.outputs.trivy_conclusion == 'failure' && needs.update-image.result == 'failure' }}
permissions:
issues: write
steps:
- name: Check for existing open issue
id: check_issue
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'trivy,security',
per_page: 1
});
return issues.data.length > 0 ? 'true' : 'false';
result-encoding: string
- name: Create GitHub issue for unresolved vulnerabilities
if: steps.check_issue.outputs.result == 'false'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Trivy: unresolved container vulnerabilities after rebuild',
body: [
'## Summary',
'',
'The daily Trivy periodic scan found Critical/High vulnerabilities in the latest published Docker image.',
'An automated rebuild was attempted but the rebuilt image **still has vulnerabilities**,',
'indicating the fix requires a manual dependency update rather than a base image refresh.',
'',
'## Next steps',
'',
`- Review findings in the [Security tab](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/security/code-scanning)`,
'- Update the affected dependencies to a version that includes the fix',
'- Or add the CVE ID(s) to a `.trivyignore` file if the risk is accepted',
'',
'## Details',
'',
`- **Workflow run:** ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
`- **Triggered at:** ${new Date().toISOString()}`,
].join('\n'),
labels: ['trivy', 'security']
});