diff --git a/.github/sts/slack-config.sts.yaml b/.github/sts/slack-config.sts.yaml index c67fe67..f93f42b 100644 --- a/.github/sts/slack-config.sts.yaml +++ b/.github/sts/slack-config.sts.yaml @@ -13,5 +13,8 @@ subject: - repo:tempoxyz@211589300/tip.bot@1232143975:environment:preview - repo:tempoxyz@211589300/tip.bot@1232143975:environment:production +claim_pattern: + job_workflow_ref: tempoxyz/tip\.bot/\.github/workflows/(preview_deploy|preview_destroy|preview_sweep|production)\.yml@refs/heads/main + permissions: secrets: write diff --git a/.github/sts/workflow-member.sts.yaml b/.github/sts/workflow-member.sts.yaml new file mode 100644 index 0000000..7f13839 --- /dev/null +++ b/.github/sts/workflow-member.sts.yaml @@ -0,0 +1,12 @@ +# Allows secret-bearing workflows loaded from main to check whether the actor +# who ultimately triggered the run is a current tempoxyz organization member. +# The token grants only organization membership visibility. +subject: + - repo:tempoxyz@211589300/tip.bot@1232143975:pull_request + - repo:tempoxyz@211589300/tip.bot@1232143975:ref:refs/heads/main + +claim_pattern: + job_workflow_ref: tempoxyz/tip\.bot/\.github/workflows/(preview|preview_destroy|preview_sweep|production)\.yml@refs/heads/main + +permissions: + members: read diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 8bddef6..1cbbad2 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -7,12 +7,6 @@ on: default: false required: false type: boolean - workflow_dispatch: - inputs: - skip_workers: - default: false - required: false - type: boolean permissions: contents: read diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml new file mode 100644 index 0000000..b230280 --- /dev/null +++ b/.github/workflows/preview.yml @@ -0,0 +1,69 @@ +name: Preview + +on: # zizmor: ignore[dangerous-triggers] Loads from main and authorizes the triggering actor before checking out PR code. + workflow_run: + workflows: [Pull Request] + types: [completed] + +permissions: {} + +jobs: + authorize: + name: Authorize + if: github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + outputs: + pr_number: ${{ steps.pr.outputs.number }} + permissions: + contents: read + id-token: write + pull-requests: read + steps: + - name: Mint organization membership token + id: member_token + uses: tempoxyz/gh-actions/actions/github-sts@dd1014bf8244fded7a501e701cafab240a4f9f0e # main + with: + policy: workflow-member + + - name: Check triggering actor organization membership + env: + ACTOR: ${{ github.run_attempt != '1' && github.triggering_actor || github.event.workflow_run.triggering_actor.login }} + GH_TOKEN: ${{ steps.member_token.outputs.token }} + run: gh api --silent "orgs/tempoxyz/members/${ACTOR}" + + - name: Resolve pull request + id: pr + env: + GH_TOKEN: ${{ github.token }} + HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + run: | + NUMBER=$(gh api "repos/${{ github.repository }}/commits/${HEAD_SHA}/pulls" \ + --jq 'map(select(.state == "open" and .draft == false)) | first | .number // empty') + if [ -z "$NUMBER" ]; then + echo "No open, ready pull request found for ${HEAD_SHA}." + exit 1 + fi + echo "number=$NUMBER" >> "$GITHUB_OUTPUT" + + preview: + name: Preview + needs: authorize + permissions: + contents: read + id-token: write + issues: write + pull-requests: write + uses: ./.github/workflows/preview_deploy.yml + with: + pr_number: ${{ needs.authorize.outputs.pr_number }} + sha: ${{ github.event.workflow_run.head_sha }} + twitter_bot_handle: ${{ vars.TWITTER_BOT_HANDLE || 'tipbotgg' }} + secrets: + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + FEE_PAYER_PRIVATE_KEY_MAINNET: ${{ secrets.FEE_PAYER_PRIVATE_KEY_MAINNET }} + FEE_PAYER_PRIVATE_KEY_TESTNET: ${{ secrets.FEE_PAYER_PRIVATE_KEY_TESTNET }} + SECRET_KEY: ${{ secrets.SECRET_KEY }} + SLACK_CONFIG_ACCESS_TOKEN: ${{ secrets.SLACK_CONFIG_ACCESS_TOKEN }} + SLACK_CONFIG_REFRESH_TOKEN: ${{ secrets.SLACK_CONFIG_REFRESH_TOKEN }} + TEMPO_API_KEY: ${{ secrets.TEMPO_API_KEY }} diff --git a/.github/workflows/preview_destroy.yml b/.github/workflows/preview_destroy.yml index e1368b2..4b69dc0 100644 --- a/.github/workflows/preview_destroy.yml +++ b/.github/workflows/preview_destroy.yml @@ -1,18 +1,14 @@ name: Preview Destroy -on: - pull_request: +on: # zizmor: ignore[dangerous-triggers] Runs main's workflow and checkout; PR code is never checked out. + pull_request_target: types: [closed, converted_to_draft] concurrency: group: slack-config-ci cancel-in-progress: false -permissions: - contents: read - id-token: write - issues: write - pull-requests: write +permissions: {} env: PREVIEW_APEX: pr${{ github.event.pull_request.number }} @@ -21,11 +17,35 @@ env: WRANGLER_SEND_METRICS: false jobs: + authorize: + name: Authorize + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - name: Mint organization membership token + id: member_token + uses: tempoxyz/gh-actions/actions/github-sts@dd1014bf8244fded7a501e701cafab240a4f9f0e # main + with: + policy: workflow-member + + - name: Check triggering actor organization membership + env: + ACTOR: ${{ github.triggering_actor }} + GH_TOKEN: ${{ steps.member_token.outputs.token }} + run: gh api --silent "orgs/tempoxyz/members/${ACTOR}" + destroy: name: Destroy - if: contains(fromJSON('["COLLABORATOR", "MEMBER", "OWNER"]'), github.event.pull_request.author_association) + needs: authorize runs-on: ubuntu-latest environment: preview + permissions: + contents: read + id-token: write + issues: write + pull-requests: write steps: - name: Read preview state id: state diff --git a/.github/workflows/preview_sweep.yml b/.github/workflows/preview_sweep.yml index 3b851d0..c30a8e4 100644 --- a/.github/workflows/preview_sweep.yml +++ b/.github/workflows/preview_sweep.yml @@ -1,28 +1,52 @@ name: Preview Sweep on: + repository_dispatch: + types: [preview_sweep] schedule: - cron: '0 0 * * *' # Daily at midnight UTC - workflow_dispatch: concurrency: group: slack-config-ci cancel-in-progress: false -permissions: - contents: read - id-token: write - issues: write - pull-requests: write +permissions: {} env: WRANGLER_SEND_METRICS: false jobs: + authorize: + name: Authorize + if: github.event_name == 'repository_dispatch' + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - name: Mint organization membership token + id: member_token + uses: tempoxyz/gh-actions/actions/github-sts@dd1014bf8244fded7a501e701cafab240a4f9f0e # main + with: + policy: workflow-member + + - name: Check triggering actor organization membership + env: + ACTOR: ${{ github.triggering_actor }} + GH_TOKEN: ${{ steps.member_token.outputs.token }} + run: gh api --silent "orgs/tempoxyz/members/${ACTOR}" + sweep: name: Sweep Orphaned Previews + if: always() && (github.event_name == 'schedule' || needs.authorize.result == 'success') + needs: authorize runs-on: ubuntu-latest environment: preview + permissions: + contents: read + id-token: write + issues: write + pull-requests: write steps: - name: Checkout uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 8325be7..874db13 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -10,8 +10,28 @@ env: WRANGLER_SEND_METRICS: false jobs: + authorize: + name: Authorize + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - name: Mint organization membership token + id: member_token + uses: tempoxyz/gh-actions/actions/github-sts@dd1014bf8244fded7a501e701cafab240a4f9f0e # main + with: + policy: workflow-member + + - name: Check triggering actor organization membership + env: + ACTOR: ${{ github.triggering_actor }} + GH_TOKEN: ${{ steps.member_token.outputs.token }} + run: gh api --silent "orgs/tempoxyz/members/${ACTOR}" + check: name: Check + needs: authorize permissions: contents: read uses: ./.github/workflows/check.yml diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index e9546f6..c4dd82a 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -18,27 +18,3 @@ jobs: check: name: Check uses: ./.github/workflows/check.yml - - preview: - name: Preview - if: github.event.pull_request.draft == false && contains(fromJSON('["COLLABORATOR", "MEMBER", "OWNER"]'), github.event.pull_request.author_association) - needs: check - permissions: - contents: read - id-token: write - issues: write - pull-requests: write - uses: ./.github/workflows/preview_deploy.yml - with: - pr_number: ${{ github.event.pull_request.number }} - sha: ${{ github.event.pull_request.head.sha }} - twitter_bot_handle: ${{ vars.TWITTER_BOT_HANDLE || 'tipbotgg' }} - secrets: - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - FEE_PAYER_PRIVATE_KEY_MAINNET: ${{ secrets.FEE_PAYER_PRIVATE_KEY_MAINNET }} - FEE_PAYER_PRIVATE_KEY_TESTNET: ${{ secrets.FEE_PAYER_PRIVATE_KEY_TESTNET }} - SECRET_KEY: ${{ secrets.SECRET_KEY }} - SLACK_CONFIG_ACCESS_TOKEN: ${{ secrets.SLACK_CONFIG_ACCESS_TOKEN }} - SLACK_CONFIG_REFRESH_TOKEN: ${{ secrets.SLACK_CONFIG_REFRESH_TOKEN }} - TEMPO_API_KEY: ${{ secrets.TEMPO_API_KEY }} diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index afaa946..85fd919 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -5,7 +5,6 @@ on: branches: ['**'] push: branches: [main] - workflow_dispatch: permissions: {}