Repository navigation
feat(e2e): WIF shim and PR authorization gate (#1604) #1983
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| name: Check E2E Authorization | ||
| description: >- | ||
| Authorize PR-triggered e2e runs for trusted authors or a fresh ok-to-test label. | ||
| Removes stale ok-to-test labels and posts a sticky PR comment when unauthorized. | ||
|
|
||
| inputs: | ||
| pr_number: | ||
| description: Pull request number to authorize | ||
| required: true | ||
| repository: | ||
| description: Repository in owner/name form | ||
| required: true | ||
| docs_url: | ||
| description: Link to e2e testing documentation | ||
| required: false | ||
| default: https://github.com/fullsend-ai/fullsend/blob/main/docs/guides/dev/e2e-testing.md | ||
|
|
||
| outputs: | ||
| authorized: | ||
| description: Whether e2e tests may run for this PR | ||
| value: ${{ steps.check.outputs.authorized }} | ||
| reason: | ||
| description: Authorization outcome reason code | ||
| value: ${{ steps.check.outputs.reason }} | ||
| label_removed: | ||
| description: Whether a stale ok-to-test label was removed | ||
| value: ${{ steps.check.outputs.label_removed }} | ||
|
|
||
| runs: | ||
| using: composite | ||
| steps: | ||
| - name: Check authorization | ||
| id: check | ||
| shell: bash | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| PR_NUMBER: ${{ inputs.pr_number }} | ||
| REPOSITORY: ${{ inputs.repository }} | ||
| run: bash "${GITHUB_WORKSPACE}/scripts/check-e2e-authorization.sh" "${PR_NUMBER}" "${REPOSITORY}" | ||
|
|
||
| - name: Post gate comment | ||
| if: steps.check.outputs.authorized != 'true' | ||
| shell: bash | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
|
ifireball marked this conversation as resolved.
|
||
| REPOSITORY: ${{ inputs.repository }} | ||
| PR_NUMBER: ${{ inputs.pr_number }} | ||
| REASON: ${{ steps.check.outputs.reason }} | ||
| LABEL_REMOVED: ${{ steps.check.outputs.label_removed }} | ||
| DOCS_URL: ${{ inputs.docs_url }} | ||
| run: | | ||
| set -euo pipefail | ||
|
|
||
| marker='<!-- e2e-gate -->' | ||
| owner="${REPOSITORY%%/*}" | ||
| repo="${REPOSITORY#*/}" | ||
|
|
||
| body="${marker} | ||
| ## E2E tests did not run | ||
|
|
||
| " | ||
|
|
||
| case "${REASON}" in | ||
| stale_ok_to_test) | ||
| body+="The \`ok-to-test\` label was removed because new commits landed after it was applied. A maintainer must re-apply \`ok-to-test\` after reviewing the latest changes. | ||
| " | ||
| ;; | ||
| error) | ||
| body+="The authorization check failed (GitHub API error). Re-run the workflow when the API is available; if this persists, contact a maintainer. | ||
| " | ||
| ;; | ||
| *) | ||
| body+="E2E tests run automatically for org/repo **members and collaborators** on pull requests. | ||
|
|
||
| For other contributors, a maintainer must add the \`ok-to-test\` label **after** the latest push. | ||
| " | ||
| ;; | ||
| esac | ||
|
|
||
| if [[ "${LABEL_REMOVED}" == "true" ]]; then | ||
| body+=" | ||
| > **Note:** \`ok-to-test\` was cleared due to new commits. | ||
| " | ||
| fi | ||
|
|
||
| body+=" | ||
| See [E2E testing guide](${DOCS_URL}) for details." | ||
|
|
||
| existing_id="$(gh api "repos/${owner}/${repo}/issues/${PR_NUMBER}/comments" --paginate \ | ||
| --jq ".[] | select(.body | contains(\"${marker}\")) | .id" | head -n1 || true)" | ||
|
|
||
| if [[ -n "${existing_id}" ]]; then | ||
| jq -n --arg body "${body}" '{body: $body}' | \ | ||
| gh api -X PATCH "repos/${owner}/${repo}/issues/comments/${existing_id}" --input - >/dev/null | ||
| else | ||
| jq -n --arg body "${body}" '{body: $body}' | \ | ||
| gh api -X POST "repos/${owner}/${repo}/issues/${PR_NUMBER}/comments" --input - >/dev/null | ||
| fi | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,24 @@ | ||
| name: E2E Tests | ||
|
|
||
| permissions: | ||
| contents: read | ||
| id-token: write | ||
| # PRs invoke this workflow via e2e_shim.yml (workflow_call @ refs/heads/main). | ||
| # See docs/ADRs/0043-e2e-wif-shim-and-pr-authorization-gate.md | ||
|
|
||
| on: | ||
|
ifireball marked this conversation as resolved.
|
||
| workflow_call: | ||
| inputs: | ||
| pr_number: | ||
| description: Pull request number to authorize and test | ||
| required: true | ||
| type: number | ||
| head_sha: | ||
| description: PR head SHA to checkout for tests | ||
| required: true | ||
| type: string | ||
| pr_author: | ||
| description: PR author login (informational; auth uses API) | ||
| required: false | ||
| type: string | ||
| default: "" | ||
| push: | ||
| branches: [main] | ||
| paths: | ||
|
|
@@ -18,30 +32,58 @@ on: | |
| - 'internal/sentencetoken/english.json' | ||
| - 'Makefile' | ||
| - '.github/workflows/e2e.yml' | ||
| pull_request: | ||
| paths: | ||
| - '**/*.go' | ||
| - 'go.mod' | ||
| - 'go.sum' | ||
| - 'e2e/**' | ||
| - 'internal/scaffold/fullsend-repo/**' | ||
| - 'internal/security/hooks/**' | ||
| - 'internal/dispatch/gcf/mintsrc/**' | ||
| - 'internal/sentencetoken/english.json' | ||
| - 'Makefile' | ||
| - '.github/workflows/e2e.yml' | ||
| - '.github/workflows/e2e_shim.yml' | ||
| - '.github/actions/check-e2e-authorization/**' | ||
| - 'scripts/check-e2e-authorization.sh' | ||
| - 'scripts/check-e2e-authorization_test.sh' | ||
| workflow_dispatch: | ||
|
|
||
| permissions: {} | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.ref }} | ||
| group: e2e-${{ github.event_name == 'workflow_call' && inputs.pr_number || github.ref }} | ||
| cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} | ||
|
|
||
| jobs: | ||
| gate: | ||
| if: github.event_name == 'workflow_call' | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
| outputs: | ||
| authorized: ${{ steps.auth.outputs.authorized }} | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| ref: refs/heads/main | ||
|
|
||
| - name: Check PR authorization | ||
| id: auth | ||
| uses: ./.github/actions/check-e2e-authorization | ||
| with: | ||
| pr_number: ${{ inputs.pr_number }} | ||
| repository: ${{ github.repository }} | ||
|
|
||
| e2e: | ||
| needs: gate | ||
| if: >- | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This branch predates 6aef818, which added After rebasing, it'd be worth thinking through how that fits with the shim. The shim's
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I started working on this with the wrong assumptions and ended up with a solution that is far more comlex and cumbersome then what we actually needed. Opened PR #2106 to redo this with fresh approach |
||
| !cancelled() && | ||
| (github.event_name != 'workflow_call' || needs.gate.result == 'skipped' || needs.gate.outputs.authorized == 'true') | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 30 | ||
| permissions: | ||
| contents: read | ||
| id-token: write | ||
| env: | ||
| # Shared e2e GCP defaults when repository secrets are unset (see ADR 0043). | ||
| E2E_GCP_PROJECT_ID: ${{ secrets.E2E_GCP_PROJECT_ID != '' && secrets.E2E_GCP_PROJECT_ID || 'it-gcp-konflux-e2e-fullsend' }} | ||
| E2E_GCP_SERVICE_ACCOUNT: ${{ secrets.E2E_GCP_SERVICE_ACCOUNT != '' && secrets.E2E_GCP_SERVICE_ACCOUNT || 'fullsend-e2e@it-gcp-konflux-e2e-fullsend.iam.gserviceaccount.com' }} | ||
| E2E_GCP_WIF_PROVIDER: ${{ secrets.E2E_GCP_WIF_PROVIDER != '' && secrets.E2E_GCP_WIF_PROVIDER || 'projects/208332380190/locations/global/workloadIdentityPools/fullsend-e2e-pool/providers/github-oidc' }} | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ github.event_name == 'workflow_call' && inputs.head_sha || github.sha }} | ||
|
|
||
| - uses: actions/setup-go@v5 | ||
| with: | ||
|
|
@@ -73,10 +115,11 @@ jobs: | |
|
|
||
| - name: Authenticate to GCP | ||
| if: steps.secrets-check.outputs.available == 'true' | ||
| uses: google-github-actions/auth@v2 | ||
| uses: google-github-actions/auth@v3 | ||
| with: | ||
| workload_identity_provider: ${{ secrets.E2E_GCP_WIF_PROVIDER }} | ||
| service_account: ${{ secrets.E2E_GCP_SERVICE_ACCOUNT }} | ||
| workload_identity_provider: ${{ env.E2E_GCP_WIF_PROVIDER }} | ||
| service_account: ${{ env.E2E_GCP_SERVICE_ACCOUNT }} | ||
| project_id: ${{ env.E2E_GCP_PROJECT_ID }} | ||
|
|
||
| - name: Run e2e tests | ||
| if: steps.secrets-check.outputs.available == 'true' | ||
|
|
@@ -86,13 +129,12 @@ jobs: | |
| E2E_GITHUB_PASSWORD: ${{ secrets.E2E_GITHUB_PASSWORD }} | ||
| E2E_GITHUB_TOTP_SECRET: ${{ secrets.E2E_GITHUB_TOTP_SECRET }} | ||
| E2E_MINT_URL: ${{ secrets.E2E_MINT_URL }} | ||
| E2E_GCP_PROJECT_ID: ${{ secrets.E2E_GCP_PROJECT_ID }} | ||
|
|
||
| - name: Upload debug screenshots | ||
| if: always() && steps.secrets-check.outputs.available == 'true' | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: e2e-screenshots | ||
| name: e2e-screenshots-${{ github.event_name == 'workflow_call' && inputs.pr_number || github.run_id }} | ||
| path: ${{ runner.temp }}/e2e-screenshots/ | ||
| if-no-files-found: ignore | ||
| retention-days: 5 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| name: E2E Shim | ||
|
|
||
| # Lightweight PR shim: calls trusted e2e.yml@main without checking out PR code. | ||
| # See docs/ADRs/0043-e2e-wif-shim-and-pr-authorization-gate.md | ||
| # | ||
| # Security: pull_request_target runs the BASE branch version of this workflow, | ||
| # preventing PRs from modifying it to exfiltrate credentials. This shim never | ||
| # checks out PR code, so it is not vulnerable to "pwn request" attacks. | ||
|
|
||
| on: | ||
| pull_request_target: | ||
| types: [opened, synchronize, reopened, labeled] | ||
| paths: | ||
| - '**/*.go' | ||
| - 'go.mod' | ||
| - 'go.sum' | ||
| - 'e2e/**' | ||
| - 'internal/scaffold/fullsend-repo/**' | ||
| - 'internal/security/hooks/**' | ||
| - 'internal/dispatch/gcf/mintsrc/**' | ||
| - 'internal/sentencetoken/english.json' | ||
| - 'Makefile' | ||
| - '.github/workflows/e2e.yml' | ||
| - '.github/workflows/e2e_shim.yml' | ||
| - '.github/actions/check-e2e-authorization/**' | ||
| - 'scripts/check-e2e-authorization.sh' | ||
| - 'scripts/check-e2e-authorization_test.sh' | ||
|
ifireball marked this conversation as resolved.
|
||
|
|
||
| concurrency: | ||
| group: e2e-${{ github.event.pull_request.number }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| call-e2e: | ||
| if: >- | ||
| github.event.action != 'labeled' || | ||
| github.event.label.name == 'ok-to-test' | ||
| permissions: | ||
| contents: read | ||
|
ifireball marked this conversation as resolved.
ifireball marked this conversation as resolved.
|
||
| id-token: write | ||
| pull-requests: write | ||
| uses: fullsend-ai/fullsend/.github/workflows/e2e.yml@refs/heads/main | ||
| with: | ||
| pr_number: ${{ github.event.pull_request.number }} | ||
| head_sha: ${{ github.event.pull_request.head.sha }} | ||
| pr_author: ${{ github.event.pull_request.user.login }} | ||
| secrets: inherit | ||
Uh oh!
There was an error while loading. Please reload this page.