diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5fa535305ec..818671ca858 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -10,6 +10,10 @@ first-time-pr/** @open-telemetry/shared-workflows-approvers @maryliag .github/workflows/first-time-pr.yml @open-telemetry/shared-workflows-approvers @maryliag +# OSSF Scorecard +scorecard/** @open-telemetry/shared-workflows-approvers @trask +.github/workflows/scorecard.yml @open-telemetry/shared-workflows-approvers @trask + # Pull request dashboard pull-request-dashboard/** @open-telemetry/shared-workflows-approvers @trask .github/scripts/pull-request-dashboard/** @open-telemetry/shared-workflows-approvers @trask diff --git a/.github/workflows/README.md b/.github/workflows/README.md index c9bfe946e6e..ec8bed03367 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -6,7 +6,7 @@ The YAML files here fall into three categories: - **Reusable workflows** called by other repos via `uses:` (for example [`zizmor.yml`](./zizmor.yml)). The consumer-facing README lives in a companion docs folder at the repo root (for example [`zizmor/`](../../zizmor/)). - **Centrally-executed workflows** that run from this repo against opted-in target repositories. Each ships one or more flat YAML files (grouped by filename prefix, e.g. `pull-request-dashboard*.yml`) plus a companion docs folder under root (for example [`pull-request-dashboard/`](../../pull-request-dashboard/)). Supporting scripts and the `repositories.json` opt-in list live under [`../scripts//`](../scripts/). -- **Repo-internal workflows** that only run for this repository (for example [`codeql.yml`](./codeql.yml) and [`scorecard.yml`](./scorecard.yml)). These are not shared and have no companion docs folder. +- **Repo-internal workflows** that only run for this repository (for example [`codeql.yml`](./codeql.yml)). These are not shared and have no companion docs folder. ``` / # root folder, shared workflows only diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 5bdb7ec81c4..6ee4e977d4f 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -1,6 +1,8 @@ +# Reusable workflow — see /scorecard/README.md for consumer documentation. name: OSSF Scorecard on: + workflow_call: push: branches: - main @@ -8,18 +10,24 @@ on: - cron: '25 4 * * 4' # weekly at 04:25 (UTC) on Thursday workflow_dispatch: -permissions: read-all # zizmor: ignore[excessive-permissions] — needed to read repo metadata for Scorecard checks +permissions: {} + +concurrency: + # github.workflow here would be the caller's name, not this workflow's. + group: shared-workflow-scorecard-${{ github.ref }} + cancel-in-progress: true jobs: + # Scorecard restricts what this job may contain while publish_results is true: + # every step must be a `uses:` of an allow-listed action, and no other job may + # request id-token. Filtering therefore happens in the upload job below. + # https://github.com/ossf/scorecard-action#workflow-restrictions analysis: name: OSSF Scorecard Analysis runs-on: ubuntu-latest - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true permissions: - security-events: write # Needed for Code scanning upload - id-token: write # Needed for GitHub OIDC token if publish_results is true + contents: read # for actions/checkout + id-token: write # Needed for GitHub OIDC token since publish_results is true steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -31,8 +39,7 @@ jobs: results_format: sarif publish_results: true - # Upload the results as artifacts (optional). Commenting out will disable - # uploads of run results in SARIF format to the repository Actions tab. + # Also hands the results to the upload job below. # https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts - name: "Upload artifact" uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -41,9 +48,42 @@ jobs: path: results.sarif retention-days: 5 - # Upload the results to GitHub's code scanning dashboard (optional). - # Commenting out will disable upload of results to your repo's Code Scanning dashboard + upload: + name: Upload to code scanning + needs: analysis + runs-on: ubuntu-latest + permissions: + contents: read # for actions/checkout, so that upload-sarif can fingerprint results + security-events: write # Needed for Code scanning upload + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: SARIF file + + - name: Keep only the checks that report findings + env: + # Deliberately narrow. See the issue for what is left out and why. + # https://github.com/open-telemetry/sig-security/issues/309 + INCLUDED_CHECKS: | + BinaryArtifactsID + DangerousWorkflowID + PinnedDependenciesID + TokenPermissionsID + run: | + set -euo pipefail + # The rule definitions stay in place so that code scanning closes the + # alerts already raised for them rather than leaving them stale. + jq --arg included "$INCLUDED_CHECKS" ' + ($included | split("\n") | map(select(length > 0))) as $keep + | .runs |= map(.results = ((.results // []) | map(select((.ruleId // "") | IN($keep[]))))) + ' results.sarif > filtered.sarif + echo "kept $(jq '[.runs[] | (.results // [])[]] | length' filtered.sarif) of $(jq '[.runs[] | (.results // [])[]] | length' results.sarif) results" + - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 with: - sarif_file: results.sarif + sarif_file: filtered.sarif diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 90f06b41f38..7cf0d004f9f 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -19,16 +19,17 @@ on: - cron: '45 9 * * 5' # weekly at 09:45 (UTC) on Friday workflow_dispatch: -permissions: - contents: read +permissions: {} + +concurrency: + # github.workflow here would be the caller's name, not this workflow's. + group: shared-workflow-zizmor-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: zizmor: name: Zizmor Security Analysis runs-on: ubuntu-latest - concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true permissions: contents: read security-events: write # for zizmor to upload SARIF results diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7e00dd00510..5b25687c13f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,7 @@ Shared workflows in this repo come in two shapes. Pick the one that fits your us - **Reusable workflow** — a workflow that other repos call directly via [`uses:`](https://docs.github.com/en/actions/using-workflows/reusing-workflows). The workflow runs in the *calling* repository's context. Example: [`zizmor.yml`](./.github/workflows/zizmor.yml). - **Centrally-executed workflow** — a workflow that runs *from this repo* against an opted-in list of target repositories. The workflow runs in this repository's context and accesses target repos via the GitHub API. Example: [`pull-request-dashboard/`](./pull-request-dashboard/) plus `pull-request-dashboard*.yml`. -> Workflows that this repo runs only for its own checks (such as [`codeql.yml`](./.github/workflows/codeql.yml) and [`scorecard.yml`](./.github/workflows/scorecard.yml)) are **not** shared. They have no companion docs folder. +> Workflows that this repo runs only for its own checks (such as [`codeql.yml`](./.github/workflows/codeql.yml)) are **not** shared. They have no companion docs folder. ### Layout diff --git a/README.md b/README.md index ea134b91779..480fb8be292 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for how to propose a new shared workf | Name | Description | How to use | | ---- | ----------- | ---------- | | [First-time contributor](./first-time-pr/) | Reusable workflow that welcomes first-time contributors on `pull_request_target: opened`: applies a label and posts a customizable welcome comment. | Call via `uses:` from your repo's `pull_request_target` workflow. See the [First-time contributor README](./first-time-pr/README.md) for the snippet. | +| [OSSF Scorecard](./scorecard/) | Reusable workflow that runs [OpenSSF Scorecard](https://github.com/ossf/scorecard), publishes the full result for the badge, and uploads a subset of its checks to code scanning. | Call via `uses:` from your repo's workflow. See the [OSSF Scorecard README](./scorecard/README.md) for the snippet. | | [Pull Request Dashboard](./pull-request-dashboard/) | Centrally-executed workflow that builds a per-repository pull request triage dashboard (issue body, status, Slack notifications) for opted-in repositories. | Add your repository to [`repositories.json`](./.github/scripts/pull-request-dashboard/repositories.json) and follow the setup in the [workflow's README](./pull-request-dashboard/README.md). | | [Survey on merged PR](./survey-on-merged-pr/) | Reusable workflow that posts a survey link to a merged PR when the author is a new contributor. | Call via `uses:` from your repo's `pull_request_target: closed` workflow. See the [Survey on merged PR README](./survey-on-merged-pr/README.md) for the snippet. | | [Workflow failure issue](./workflow-failure-issue/) | Reusable workflow that tracks a workflow's pass/fail state by opening, commenting on, and closing a GitHub issue in the calling repository — useful for scheduled workflows whose failure notifications otherwise reach only a single user. | Call via `uses:` from a final `if: always()` job in the workflow you want to monitor. See the [Workflow failure issue README](./workflow-failure-issue/README.md) for the snippet. | diff --git a/scorecard/README.md b/scorecard/README.md new file mode 100644 index 00000000000..af51935dfa1 --- /dev/null +++ b/scorecard/README.md @@ -0,0 +1,41 @@ +# OSSF Scorecard + +Reusable GitHub Actions workflow that runs [OpenSSF Scorecard](https://github.com/ossf/scorecard) against the calling repository, publishes the full result to [api.scorecard.dev](https://api.scorecard.dev) (which is what backs the Scorecard badge), and uploads a subset of its checks to the repository's code scanning dashboard. + +## How to use + +Replace your repository's inline Scorecard workflow (typically `.github/workflows/scorecard.yml`) with: + +```yaml +name: OSSF Scorecard + +on: + push: + branches: [main] + schedule: + - cron: '25 4 * * 4' + workflow_dispatch: + +permissions: {} + +jobs: + scorecard: + permissions: + contents: read # for actions/checkout + id-token: write # for Scorecard to publish results + security-events: write # for the SARIF upload to code scanning + uses: open-telemetry/shared-workflows/.github/workflows/scorecard.yml@ +``` + +Pin `` to a commit SHA or release tag in this repository. No inputs or secrets are required. + +## What gets filtered + +Only these checks are uploaded to code scanning: + +- `BinaryArtifactsID` +- `DangerousWorkflowID` +- `PinnedDependenciesID` +- `TokenPermissionsID` + +The full result is still published, so the badge and the public `api.scorecard.dev` entry are unaffected.