diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 421e22d..fd877af 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,56 +7,71 @@ on: pull_request: workflow_dispatch: -permissions: - contents: read - pull-requests: read # for golangci/golangci-lint-action to fetch pull requests +permissions: {} jobs: pre-commit: runs-on: ubuntu-latest + permissions: + contents: read # for actions/checkout to fetch code steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - - uses: actions/setup-go@v6 + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 with: go-version: 'stable' check-latest: true - - uses: pre-commit/action@v3.0.1 + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 actionlint: runs-on: ubuntu-latest + permissions: + contents: read # for actions/checkout to fetch code + checks: write # for reviewdog github-pr-check reporter to report findings steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - - uses: reviewdog/action-actionlint@v1 + - uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # v1 golangci-lint: runs-on: ubuntu-latest + permissions: + contents: read # for actions/checkout to fetch code steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - - uses: actions/setup-go@v6 + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 with: go-version: 'stable' check-latest: true - - uses: golangci/golangci-lint-action@v9.2.1 + - uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0 with: version: latest args: --verbose test: runs-on: ubuntu-latest + permissions: + contents: read # for actions/checkout to fetch code steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - - uses: actions/setup-go@v6 + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 with: go-version: 'stable' check-latest: true diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index d402abc..b13b6ae 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -1,13 +1,25 @@ --- name: E2E Tests +# Trigger design (intentional — do not "fix" by running e2e on PR events): +# +# The e2e matrix is slow, so it must not run on every push to a PR. It runs +# once per PR, via manual workflow_dispatch by a maintainer, when the PR is +# ready to merge. +# +# To gate merging on that manual run, every PR event sets the "e2e" commit +# status to pending (set-pending job), and the dispatched run reports the +# result back (report-status job) so the merge check can pass. +# +# pull_request_target is only used because posting a commit status requires +# a write-capable GITHUB_TOKEN, also for fork PRs. Since that token is +# privileged, the set-pending job must never check out or execute PR code. + on: workflow_dispatch: pull_request_target: -permissions: - contents: read - statuses: write +permissions: {} jobs: @@ -15,19 +27,25 @@ jobs: if: github.event_name == 'pull_request_target' runs-on: ubuntu-latest name: Set pending e2e status + permissions: + statuses: write # for gh api to set the commit status steps: - name: Set pending e2e status run: | - gh api "repos/${{ github.repository }}/statuses/${{ github.event.pull_request.head.sha }}" \ + gh api "repos/${REPO}/statuses/${HEAD_SHA}" \ -f state="pending" \ -f context="e2e" \ -f description="E2E tests must be triggered manually via workflow_dispatch" env: GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} e2e: if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest + permissions: + contents: read # for actions/checkout to fetch code timeout-minutes: ${{ matrix.timeout || 5 }} strategy: @@ -64,9 +82,11 @@ jobs: steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - - uses: actions/setup-go@v6 + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 with: go-version: 'stable' check-latest: true @@ -82,6 +102,8 @@ jobs: needs: [e2e] runs-on: ubuntu-latest name: Report e2e status + permissions: + statuses: write # for gh api to set the commit status steps: - name: Report e2e status run: | diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index f31abac..fabd5b1 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -5,6 +5,8 @@ on: push: branches: ['main'] +permissions: {} + jobs: publish: runs-on: ubuntu-latest @@ -15,22 +17,23 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: fetch-depth: 0 fetch-tags: true + persist-credentials: false - name: Setup Go - uses: actions/setup-go@v6 + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 with: go-version: 'stable' check-latest: true - name: Install Cosign - uses: sigstore/cosign-installer@v4.1.2 + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 - name: Prepare Ko - uses: ko-build/setup-ko@v0.9 + uses: ko-build/setup-ko@61b4d1d396f5b2e7d6bb6fefdce3dc38d1a13445 # v0.10 - name: Compute build metadata env: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 16b91a5..d3532fb 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -5,6 +5,8 @@ on: push: tags: ['v*'] +permissions: {} + jobs: release: runs-on: ubuntu-latest @@ -15,22 +17,23 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: fetch-depth: 0 fetch-tags: true + persist-credentials: false - name: Setup Go - uses: actions/setup-go@v6 + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 with: go-version: 'stable' check-latest: true - name: Install Cosign - uses: sigstore/cosign-installer@v4.1.2 + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 - name: Prepare Ko - uses: ko-build/setup-ko@v0.9 + uses: ko-build/setup-ko@61b4d1d396f5b2e7d6bb6fefdce3dc38d1a13445 # v0.10 - name: Compute build metadata env: diff --git a/docs/testing.md b/docs/testing.md index 9b73865..e0cbc74 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -10,6 +10,25 @@ Tests use `httptest.NewServer` for real local HTTP servers — no mocks for the - `newTestProxyWithRetries(t, mirrors, retries)` — same, but also sets `retryBaseDelay = 0` so retry tests run instantly. - `newTestApp(pp)` — builds an Echo app with the same middleware stack as production (`RequestID` → error handler → `Recover` → `Cache` → `ForwardProxy`). +## E2E Tests in CI + +E2e tests (`.github/workflows/e2e.yaml`) run a full distro container matrix and are +expensive, so they don't run on every push. They run once per pull request, triggered +manually by a maintainer before merging: + +```bash +gh workflow run e2e.yaml --ref +``` + +or via the Actions UI ("E2E Tests" → "Run workflow"). + +Merge gating: each PR event sets a pending `e2e` commit status, and the dispatched run +reports the result back as the `e2e` status used as a merge check. + +Security constraint of the implementation: the status-setting job runs under +`pull_request_target` with a privileged token and therefore never checks out or +executes PR code. + ## External Tests Tests in `proxy_test.go` that hit httpbin.org are skipped by default. Enable with: