Trivy Periodic Image Scan #17
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 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'] | |
| }); |