Files

151 lines
6.6 KiB
YAML

name: Trivy vulnerability scan
on:
push:
branches:
- main
paths:
- "**.tf"
pull_request:
paths:
- "**.tf"
workflow_dispatch:
schedule:
- cron: "0 4 * * *" # Nightly 4AM UTC
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true
defaults:
run:
# fail-fast using bash -eo pipefail. See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
shell: bash
env:
AWS_REGION: us-east-2
AWS_IAM_ROLE: arn:aws:iam::160035666661:role/github-actions-role
permissions:
contents: read
jobs:
discover:
# Only the nightly/manual runs scan extra branches; push/PR keep their
# single-ref behavior and skip this job.
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
outputs:
branches: ${{ steps.set.outputs.branches }}
steps:
- name: Determine branches to scan
id: set
env:
GH_TOKEN: ${{ github.token }}
run: |
# Resolve the most recent published (non-draft, non-prerelease) Fleet
# release tag, e.g. "fleet-v4.84.0", and strip the prefix so we end
# up with a bare semver like "4.84.0" for comparison.
latest=$(gh release view --repo "${{ github.repository }}" --json tagName -q .tagName)
latest_ver=${latest#fleet-v}
# List every branch on the repo and keep only those that strictly
# match the canonical RC naming pattern. The anchors filter out
# cherry-pick branches and "-suffix" variants (e.g.
# rc-minor-fleet-v4.65.0-cherry-helm), leaving just the real RCs.
# `|| true` keeps the step from failing when grep finds no matches.
candidates=$(gh api "repos/${{ github.repository }}/branches" --paginate --jq '.[].name' \
| grep -E '^rc-(minor|patch)-fleet-v[0-9]+\.[0-9]+\.[0-9]+$' || true)
# Keep only the RC branches whose version is strictly greater than
# the latest released version (i.e. an in-flight release). `sort -V`
# gives semver-aware ordering; if the branch's version sorts last
# and isn't equal to the released version, it's still active.
active=()
for b in $candidates; do
v=$(echo "$b" | sed -E 's/^rc-(minor|patch)-fleet-v//')
top=$(printf '%s\n%s\n' "$v" "$latest_ver" | sort -V | tail -n1)
if [ "$top" = "$v" ] && [ "$v" != "$latest_ver" ]; then
active+=("$b")
fi
done
# Emit a JSON array (e.g. ["main","rc-minor-fleet-v4.85.0"]) for
# the downstream matrix to consume via fromJSON.
branches=$(printf '%s\n' "main" "${active[@]}" | jq -Rnc '[inputs]')
echo "branches=$branches" >> "$GITHUB_OUTPUT"
echo "Will scan: $branches"
trivy:
needs: [discover]
# Run when discover succeeds (nightly/manual) or is skipped (push/PR).
if: always() && (needs.discover.result == 'success' || needs.discover.result == 'skipped')
permissions:
id-token: write # for aws-actions/configure-aws-credentials
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
name: Trivy sarif report
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# When discover is skipped, fall back to a single empty entry so we
# scan the triggering ref (push/PR default checkout behavior).
branch: ${{ fromJSON(needs.discover.outputs.branches || '[""]') }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ matrix.branch || github.ref }}
persist-credentials: false
- name: Capture checked-out commit SHA
id: sha
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
with:
role-to-assume: ${{env.AWS_IAM_ROLE}}
aws-region: ${{ env.AWS_REGION }}
- name: Run Trivy vulnerability scanner in repo mode
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
env:
TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db
TRIVY_JAVA_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-java-db
TRIVY_SECRET_CONFIG: "./security/code/trivy-secret.yaml"
with:
scan-type: "fs"
ignore-unfixed: false
# PR/push: only scan for secrets (block leaked credentials before merge).
# Schedule/manual: vuln + secret with SARIF upload to the Security tab
# for triage. CVEs are tracked nightly, not per-PR. Misconfig is
# intentionally excluded; revisit after the SOC 2 audit.
scanners: ${{ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && 'vuln,secret' || 'secret' }}
format: ${{ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && 'sarif' || 'table' }}
output: ${{ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && 'trivy-results.sarif' || '' }}
exit-code: ${{ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && '0' || '1' }}
severity: "CRITICAL,HIGH,MEDIUM,LOW"
trivyignores: "./security/code/.trivyignore"
# Test/dev keys and Sails placeholders are handled by trivy-secret.yaml.
# Production files use targeted path+regex allow-rules in trivy-secret.yaml.
- name: Upload Trivy scan results to GitHub Security tab
# Only upload on schedule/manual runs. PR/push uploads register
# instances on `refs/pull/*/merge` refs that are never re-scanned
# after the PR closes, leaving zombie alerts open forever.
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
uses: github/codeql-action/upload-sarif@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6
with:
sarif_file: "trivy-results.sarif"
category: trivy-${{ matrix.branch }}
ref: refs/heads/${{ matrix.branch }}
sha: ${{ steps.sha.outputs.sha }}
checkout_path: ${{ github.workspace }}