diff --git a/.gitea/workflows/run-sec-scan.yaml b/.gitea/workflows/run-sec-scan.yaml new file mode 100644 index 0000000..40de3c9 --- /dev/null +++ b/.gitea/workflows/run-sec-scan.yaml @@ -0,0 +1,75 @@ +name: Run Sec Scan + +on: + workflow_call: + inputs: + ecosystems: + description: "Comma-separated ecosystems for setup-osv-db (see sec-actions/setup-osv-db)." + required: false + type: string + default: "github-actions,npm,go,docker" + cache-bucket-hours: + description: "Cache key time bucket (hours) for setup-osv-db." + required: false + type: number + default: 24 + osv-scanner-image: + description: "Container image for OSV-Scanner (run with hardened docker options)." + required: false + type: string + default: "ghcr.io/google/osv-scanner:latest" + outputs: + merged-sarif-artifact: + description: "Artifact name containing the SARIF file (download this artifact; file inside is merged-sarif.json)." + value: ${{ jobs.osv-scan.outputs.merged-sarif-artifact }} + merged-sarif-path: + description: "Path to the SARIF file inside the artifact (merged-sarif.json)." + value: ${{ jobs.osv-scan.outputs.merged-sarif-path }} + +jobs: + osv-scan: + runs-on: ubuntu-latest + outputs: + merged-sarif-artifact: ${{ steps.meta.outputs.merged-sarif-artifact }} + merged-sarif-path: ${{ steps.meta.outputs.merged-sarif-path }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup offline OSV DB + id: setup-db + uses: https://gitea.t000-n.de/t.behrendt/sec-actions/setup-osv-db@main + with: + ecosystems: ${{ inputs.ecosystems }} + cache-bucket-hours: ${{ inputs.cache-bucket-hours }} + + - name: Run OSV-Scanner in Docker (offline, no network, read-only) + id: scan + run: | + set -euo pipefail + SARIF_HOST_DIR="${RUNNER_TEMP}/osv-sarif-out" + mkdir -p "${SARIF_HOST_DIR}" + CACHE="${{ steps.setup-db.outputs.cache-dir }}" + IMAGE="${{ inputs.osv-scanner-image }}" + docker run --rm \ + --network none \ + --read-only \ + --tmpfs /tmp:rw,noexec,nosuid,size=256m \ + --cap-drop ALL \ + --security-opt no-new-privileges \ + -v "${GITHUB_WORKSPACE}:/work:ro" \ + -v "${CACHE}:/osv-db:ro" \ + -v "${SARIF_HOST_DIR}:/out" \ + -e OSV_SCANNER_LOCAL_DB_CACHE_DIRECTORY=/osv-db \ + "${IMAGE}" \ + scan source --offline-vulnerabilities --format sarif --output /out/merged-sarif.json -r /work + + - name: Artifact metadata + id: meta + run: | + echo "merged-sarif-artifact=merged-sarif" >> "$GITHUB_OUTPUT" + echo "merged-sarif-path=merged-sarif.json" >> "$GITHUB_OUTPUT" + + - uses: https://github.com/ChristopherHX/gitea-upload-artifact@v4 + with: + name: merged-sarif + path: ${{ runner.temp }}/osv-sarif-out/merged-sarif.json diff --git a/.gitea/workflows/run-trivy-scan.yaml b/.gitea/workflows/run-trivy-scan.yaml deleted file mode 100644 index bf8587b..0000000 --- a/.gitea/workflows/run-trivy-scan.yaml +++ /dev/null @@ -1,192 +0,0 @@ -name: Run Trivy Scan - -on: - workflow_call: - inputs: - scan-config: - description: "Run Trivy config scan (repository root)." - required: false - type: boolean - default: false - scan-images: - description: "Run Trivy image scan on images from image-scan-files." - required: false - type: boolean - default: false - image-scan-files: - description: "YAML list of files to extract container images from. Used when scan-images is true." - required: false - type: string - default: "" - trivy-server-url: - description: "Optional Trivy server URL for image scan." - required: false - type: string - default: "" - scan-fs: - description: "Run Trivy file system scan on the repository root." - required: false - type: boolean - default: false - outputs: - merged-sarif-artifact: - description: "Artifact name containing the merged SARIF file (download this artifact; file inside is merged-sarif.json)." - value: ${{ jobs.merge.outputs.merged-sarif-artifact }} - merged-sarif-path: - description: "Path to the merged SARIF file inside the artifact (merged-sarif.json)." - value: ${{ jobs.merge.outputs.merged-sarif-path }} - -jobs: - config-scan: - if: inputs.scan-config - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: https://gitea.t000-n.de/t.behrendt/trivy-actions/setup-trivy@a6508d695d7bb6137f14372392d5c312c98225cf # 1.4.7 - - uses: https://gitea.t000-n.de/t.behrendt/trivy-actions/setup-db@a6508d695d7bb6137f14372392d5c312c98225cf # 1.4.7 - - run: | - trivy config --cache-dir "$TRIVY_CACHE_DIR" --exit-code 0 --format sarif --output config-sarif.json . - env: - TRIVY_CACHE_DIR: ${{ runner.temp }}/trivy - - uses: https://github.com/ChristopherHX/gitea-upload-artifact@v4 - with: - name: config-sarif - path: config-sarif.json - - image-scan: - if: inputs.scan-images - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: https://gitea.t000-n.de/t.behrendt/trivy-actions/setup-trivy@a6508d695d7bb6137f14372392d5c312c98225cf # 1.4.7 - - uses: https://gitea.t000-n.de/t.behrendt/trivy-actions/setup-db@a6508d695d7bb6137f14372392d5c312c98225cf # 1.4.7 - - name: Get images from files - id: get-images - uses: https://gitea.t000-n.de/t.behrendt/trivy-actions/get-images-from-files@a6508d695d7bb6137f14372392d5c312c98225cf # 1.4.7 - with: - files: ${{ inputs.image-scan-files }} - - name: Pull images - if: steps.get-images.outputs.images != '[]' - run: | - set -e - images='${{ steps.get-images.outputs.images }}' - for img in $(echo "$images" | jq -r '.[]'); do - docker pull "$img" - done - - name: Scan images - id: scan - if: steps.get-images.outputs.images != '[]' - run: | - set -e - images='${{ steps.get-images.outputs.images }}' - count=$(echo "$images" | jq 'length') - if [ "$count" -eq 0 ]; then - echo "count=0" >> "$GITHUB_OUTPUT" - exit 0 - fi - i=0 - server="${{ inputs.trivy-server-url }}" - for img in $(echo "$images" | jq -r '.[]'); do - args=(image --cache-dir "$TRIVY_CACHE_DIR" --exit-code 0 --scanners vuln --format sarif --output "sarif-image-${i}.json" "$img") - [ -n "$server" ] && args+=(--server "$server") - trivy "${args[@]}" - i=$((i + 1)) - done - { - echo "files<> $GITHUB_OUTPUT - echo "count=$i" >> $GITHUB_OUTPUT - env: - TRIVY_CACHE_DIR: ${{ runner.temp }}/trivy - - name: Merge image SARIF files - if: steps.get-images.outputs.images != '[]' && steps.scan.outputs.count != '0' - uses: https://gitea.t000-n.de/t.behrendt/trivy-actions/merge-sarif-files@a6508d695d7bb6137f14372392d5c312c98225cf # 1.4.7 - with: - files: ${{ steps.scan.outputs.files }} - output-file: image-sarif.json - - name: Ensure image SARIF exists (no images case) - if: steps.get-images.outputs.images == '[]' || steps.scan.outputs.count == '0' - run: | - echo '{"version":"2.1.0","$schema":"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json","runs":[]}' > image-sarif.json - - uses: https://github.com/ChristopherHX/gitea-upload-artifact@v4 - with: - name: image-sarif - path: image-sarif.json - - fs-scan: - if: inputs.scan-fs - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: https://gitea.t000-n.de/t.behrendt/trivy-actions/setup-trivy@a6508d695d7bb6137f14372392d5c312c98225cf # 1.4.7 - - uses: https://gitea.t000-n.de/t.behrendt/trivy-actions/setup-db@a6508d695d7bb6137f14372392d5c312c98225cf # 1.4.7 - - run: | - server="${{ inputs.trivy-server-url }}" - args=(fs --cache-dir "$TRIVY_CACHE_DIR" --exit-code 0 --format sarif --output fs-sarif.json --scanners vuln .) - [ -n "$server" ] && args+=(--server "$server") - trivy "${args[@]}" - env: - TRIVY_CACHE_DIR: ${{ runner.temp }}/trivy - - uses: https://github.com/ChristopherHX/gitea-upload-artifact@v4 - with: - name: fs-sarif - path: fs-sarif.json - - merge: - runs-on: ubuntu-latest - needs: [config-scan, image-scan, fs-scan] - if: always() && (needs.config-scan.result == 'success' || needs.config-scan.result == 'skipped') && (needs.image-scan.result == 'success' || needs.image-scan.result == 'skipped') && (inputs.scan-config || inputs.scan-images) - outputs: - merged-sarif-artifact: ${{ steps.outputs.outputs.merged-sarif-artifact }} - merged-sarif-path: ${{ steps.outputs.outputs.merged-sarif-path }} - steps: - - name: Require at least one scan - if: ${{ !inputs.scan-config && !inputs.scan-images }} - run: | - echo "Enable scan-config and/or scan-images (and provide image-scan-files for image scan)." - exit 1 - - name: Download config SARIF - if: inputs.scan-config - uses: https://github.com/ChristopherHX/gitea-download-artifact@v4 - with: - name: config-sarif - path: config-sarif-artifact - - name: Download image SARIF - if: inputs.scan-images - uses: https://github.com/ChristopherHX/gitea-download-artifact@v4 - with: - name: image-sarif - path: image-sarif-artifact - - name: Build merge file list - id: file-list - run: | - files="" - if [ "${{ inputs.scan-config }}" = "true" ] && [ -f config-sarif-artifact/config-sarif.json ]; then - files="- config-sarif-artifact/config-sarif.json" - fi - if [ "${{ inputs.scan-images }}" = "true" ] && [ -f image-sarif-artifact/image-sarif.json ]; then - [ -n "$files" ] && files="$files"$'\n'" - image-sarif-artifact/image-sarif.json" || files="- image-sarif-artifact/image-sarif.json" - fi - if [ "${{ inputs.scan-fs }}" = "true" ] && [ -f fs-sarif-artifact/fs-sarif.json ]; then - [ -n "$files" ] && files="$files"$'\n'" - fs-sarif-artifact/fs-sarif.json" || files="- fs-sarif-artifact/fs-sarif.json" - fi - echo "files<> "$GITHUB_OUTPUT" - echo "$files" >> "$GITHUB_OUTPUT" - echo "EOF" >> "$GITHUB_OUTPUT" - - name: Merge SARIF files - id: merge-step - uses: https://gitea.t000-n.de/t.behrendt/trivy-actions/merge-sarif-files@a6508d695d7bb6137f14372392d5c312c98225cf # 1.4.7 - with: - files: ${{ steps.file-list.outputs.files }} - output-file: merged-sarif.json - - name: Set outputs - id: outputs - run: | - echo "merged-sarif-artifact=merged-sarif" >> "$GITHUB_OUTPUT" - echo "merged-sarif-path=merged-sarif.json" >> "$GITHUB_OUTPUT" - - uses: https://github.com/ChristopherHX/gitea-upload-artifact@v4 - with: - name: merged-sarif - path: merged-sarif.json diff --git a/README.md b/README.md index 5e1a67d..b15b977 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,16 @@ -# Trivy Workflows +# Sec Workflows -## Run Trivy Scan (reusable workflow) +## Run Sec Scan (reusable workflow) -Reusable workflow that optionally runs Trivy config and/or image scan, merges the SARIF results, and exposes the merged file as an artifact. It does **not** upload to TAS; callers download the artifact and use it (e.g. with `tas-upload-sarif`). +Reusable workflow that restores the [OSV-Scanner](https://google.github.io/osv-scanner/) offline vulnerability database via **`setup-osv-db`**, runs **`osv-scanner scan source`** with **`--offline-vulnerabilities`**, and publishes a single SARIF artifact. It does **not** upload to TAS; callers download the artifact and use it (e.g. with `tas-upload-sarif`). -**Workflow file:** [../.gitea/workflows/run-trivy-scan.yaml](../.gitea/workflows/run-trivy-scan.yaml) +Scanning uses [Google OSV](https://osv.dev/) data (not Trivy). The scanner runs **only** inside Docker with **no container network**, a **read-only** root filesystem (plus a small **`tmpfs`** for `/tmp`), **all capabilities dropped**, and **`no-new-privileges`**. The workspace and the local OSV DB are bind-mounted **read-only**; SARIF is written to a dedicated host directory mounted read-write at `/out` in the container. + +**Workflow file:** [.gitea/workflows/run-sec-scan.yaml](.gitea/workflows/run-sec-scan.yaml) + +### Offline DB (`setup-osv-db`) + +The workflow uses the **`setup-osv-db`** action from the **`sec-actions`** repository (replacing the former `trivy-actions` / `setup-trivy` flow). That action prepares `OSV_SCANNER_LOCAL_DB_CACHE_DIRECTORY` for use with `--offline-vulnerabilities` (see [OSV-Scanner offline mode](https://google.github.io/osv-scanner/usage/offline-mode/)). ## Usage @@ -12,82 +18,69 @@ Reusable workflow that optionally runs Trivy config and/or image scan, merges th ```yaml jobs: - trivy: - uses: ./.gitea/workflows/run-trivy-scan.yaml + sec: + uses: ./.gitea/workflows/run-sec-scan.yaml with: - scan-config: true - scan-images: true - image-scan-files: | - - k8s/31_deployment_redis.yaml - - k8s/32_deployment_gitea.yaml - - dockers/runner/compose.yaml - trivy-server-url: ${{ vars.TRIVY_SERVER_URL }} + ecosystems: PyPI,npm,Go + cache-bucket-hours: 6 use-sarif: - needs: trivy + needs: sec runs-on: ubuntu-latest steps: - - name: Download merged SARIF + - name: Download SARIF uses: https://github.com/ChristopherHX/gitea-download-artifact@v4 with: - name: ${{ needs.trivy.outputs.merged-sarif-artifact }} + name: ${{ needs.sec.outputs.merged-sarif-artifact }} path: sarif - # Path to file: sarif/${{ needs.trivy.outputs.merged-sarif-path }} + # Path to file: sarif/${{ needs.sec.outputs.merged-sarif-path }} # - uses: .../tas-upload-sarif@... # with: - # sarif-file: sarif/${{ needs.trivy.outputs.merged-sarif-path }} + # sarif-file: sarif/${{ needs.sec.outputs.merged-sarif-path }} ``` ### Call from another repository -Use the **full workflow path** including `.gitea/workflows/` and the filename. Gitea does not accept a bare repo path like `.../trivy-actions/run-trivy-scan@ref`. +Use the **full workflow path** including `.gitea/workflows/` and the filename. Gitea does not accept a bare repo path like `.../sec-actions/run-sec-scan@ref`. **With absolute URL:** + ```yaml jobs: - trivy: - uses: https://gitea.t000-n.de/t.behrendt/trivy-actions/.gitea/workflows/run-trivy-scan.yaml@1.4.5 + sec: + uses: https://gitea.t000-n.de/t.behrendt/sec-workflows/.gitea/workflows/run-sec-scan.yaml@1.0.0 with: - ref: 1.4.5 # use same ref as in 'uses' so actions load from trivy-actions - scan-config: true - scan-images: true - image-scan-files: | - - k8s/31_deployment_redis.yaml - trivy-server-url: ${{ vars.TRIVY_SERVER_URL }} + ecosystems: github-actions,npm,go,Alpine ``` **With owner/repo path** (same server as the caller): + ```yaml jobs: - trivy: - uses: t.behrendt/trivy-actions/.gitea/workflows/run-trivy-scan.yaml@1.4.5 + sec: + uses: t.behrendt/sec-workflows/.gitea/workflows/run-sec-scan.yaml@1.0.0 with: - ref: 1.4.5 # use same ref as in 'uses' - scan-config: true - scan-images: true - image-scan-files: | - - k8s/31_deployment_redis.yaml + ecosystems: github-actions,npm,go,docker ``` -When calling from another repo, pass **`ref`** with the same ref as in the workflow path (e.g. `@1.4.5` or commit SHA). The workflow checks out trivy-actions at that ref into `trivy-actions/` and then uses the actions from that checkout (e.g. `./trivy-actions/setup-trivy`). +Pin the same tag or commit in `uses:` that you intend to run. Reusable actions referenced **inside** this workflow (for example `sec-actions/setup-osv-db`) are pinned in the workflow file; update that repo reference when you release new **sec-actions** versions. ### Inputs -| Input | Type | Default | Description | -| ------------------- | ------- | ------- | ----------- | -| `scan-config` | boolean | false | Run Trivy config scan on the repository root. | -| `scan-images` | boolean | false | Run Trivy image scan on images from `image-scan-files`. | -| `image-scan-files` | string | "" | YAML list of files to extract container images from. | -| `trivy-server-url` | string | "" | Optional Trivy server URL for image scan. | -| `ref` | string | "main" | Git ref to checkout (branch, tag, or SHA); use the same ref as in the workflow path when calling from another repo. | -| `repository-url` | string | "https://gitea.t000-n.de/t.behrendt/trivy-actions.git" | Clone URL for trivy-actions; for private repos use a URL with token or SSH. | - -At least one of `scan-config` or `scan-images` must be true. If `scan-images` is true, set `image-scan-files`. +| Input | Type | Default | Description | +| --------------------- | ------ | ----------------------------------- | --------------------------------------------------------------------------- | +| `ecosystems` | string | `github-actions,npm,go,docker` | Passed to **`setup-osv-db`** (`docker` maps to **Linux** in that action). | +| `cache-bucket-hours` | number | `24` | Passed to **`setup-osv-db`** for `actions/cache` key bucketing. | +| `osv-scanner-image` | string | `ghcr.io/google/osv-scanner:latest` | Image for the hardened **`docker run`** (offline scan; no network in run). Pin a digest or tag for reproducibility. | ### Outputs -| Output | Description | -| ------------------------ | ----------- | -| `merged-sarif-artifact` | Artifact name to pass to `download-artifact` (e.g. `merged-sarif`). | -| `merged-sarif-path` | Path to the file inside that artifact (e.g. `merged-sarif.json`). | +| Output | Description | +| ----------------------- | ------------------------------------------------------------------- | +| `merged-sarif-artifact` | Artifact name to pass to `download-artifact` (e.g. `merged-sarif`). | +| `merged-sarif-path` | Path to the file inside that artifact (e.g. `merged-sarif.json`). | -After downloading the artifact, the merged SARIF file is at `/${{ needs..outputs.merged-sarif-path }}`. +After downloading the artifact, the SARIF file is at `/${{ needs..outputs.merged-sarif-path }}`. + +## Migration from Trivy + +Earlier revisions used **Trivy** (`setup-trivy`, `setup-db` from **trivy-actions**) for config, filesystem, and image scans. This workflow now targets **OSV-Scanner** source scans with an **offline OSV database** only. Replacing IaC or container-image vulnerability semantics may require additional tooling outside this repository.