From 6973a17821d87e2d99f5c8b08468c6789f2c2493 Mon Sep 17 00:00:00 2001 From: Yasunobu <42543015+P4suta@users.noreply.github.com> Date: Wed, 21 Oct 2026 18:35:47 +0900 Subject: [PATCH 1/2] refactor(ci): use purpose-scoped Doppler credentials Add scoped OIDC delivery and a credential verification workflow. Refs: #52. --- .github/workflows/release-plz.yml | 27 ++++++++++-- .github/workflows/verify-doppler.yml | 66 ++++++++++++++++++++++++++++ docs/credential-delivery.md | 32 ++++++++++++++ 3 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/verify-doppler.yml create mode 100644 docs/credential-delivery.md diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml index 3ceb054..bcdf612 100644 --- a/.github/workflows/release-plz.yml +++ b/.github/workflows/release-plz.yml @@ -29,14 +29,35 @@ jobs: timeout-minutes: 30 environment: name: release - permissions: {} + permissions: + id-token: write steps: + - name: Fetch automation credentials + id: doppler_automation_0 + uses: dopplerhq/secrets-fetch-action@451892f16195f9ac360e1a5bcbf0b5fd0e957534 + with: + auth-method: oidc + doppler-identity-id: ${{ vars.DOPPLER_AUTOMATION_IDENTITY_ID }} + doppler-project: windows-spawn + doppler-config: automation + inject-env-vars: false + - name: Reject missing or unresolved automation credentials + if: >- + ${{ steps.doppler_automation_0.outcome == 'success' && ( + steps.doppler_automation_0.outputs.GITHUB_APP_CLIENT_ID == '' || + startsWith(steps.doppler_automation_0.outputs.GITHUB_APP_CLIENT_ID, '${') || + steps.doppler_automation_0.outputs.GITHUB_APP_PRIVATE_KEY == '' || + startsWith(steps.doppler_automation_0.outputs.GITHUB_APP_PRIVATE_KEY, '${') + ) }} + shell: bash + run: exit 1 + - name: Mint the release GitHub App token id: app-token uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: - client-id: ${{ vars.RELEASE_PLZ_APP_CLIENT_ID }} - private-key: ${{ secrets.RELEASE_PLZ_APP_PRIVATE_KEY }} + client-id: ${{ steps.doppler_automation_0.outputs.GITHUB_APP_CLIENT_ID }} + private-key: ${{ steps.doppler_automation_0.outputs.GITHUB_APP_PRIVATE_KEY }} permission-contents: write permission-pull-requests: write - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/.github/workflows/verify-doppler.yml b/.github/workflows/verify-doppler.yml new file mode 100644 index 0000000..f4ab677 --- /dev/null +++ b/.github/workflows/verify-doppler.yml @@ -0,0 +1,66 @@ +--- +name: Verify credential access + +'on': + workflow_dispatch: + +permissions: {} + +concurrency: + group: verify-credentials + cancel-in-progress: false + +jobs: + automation_0: + name: Verify automation access + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-24.04 + timeout-minutes: 5 + environment: release + permissions: + id-token: write + steps: + - name: Fetch automation credentials + id: credentials + uses: dopplerhq/secrets-fetch-action@451892f16195f9ac360e1a5bcbf0b5fd0e957534 + with: + auth-method: oidc + doppler-identity-id: ${{ vars.DOPPLER_AUTOMATION_IDENTITY_ID }} + doppler-project: windows-spawn + doppler-config: automation + inject-env-vars: false + - name: Reject missing or unresolved automation credentials + if: >- + ${{ steps.credentials.outcome == 'success' && ( + steps.credentials.outputs.GITHUB_APP_CLIENT_ID == '' || + startsWith(steps.credentials.outputs.GITHUB_APP_CLIENT_ID, '${') || + steps.credentials.outputs.GITHUB_APP_PRIVATE_KEY == '' || + startsWith(steps.credentials.outputs.GITHUB_APP_PRIVATE_KEY, '${') + ) }} + shell: bash + run: exit 1 + + - name: Verify a read-only repository token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 + with: + client-id: ${{ steps.credentials.outputs.GITHUB_APP_CLIENT_ID }} + private-key: ${{ steps.credentials.outputs.GITHUB_APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + permission-contents: read + + - name: Attempt unrelated credential access + id: forbidden + continue-on-error: true + uses: dopplerhq/secrets-fetch-action@451892f16195f9ac360e1a5bcbf0b5fd0e957534 + with: + auth-method: oidc + doppler-identity-id: ${{ vars.DOPPLER_AUTOMATION_IDENTITY_ID }} + doppler-project: aozora + doppler-config: automation + inject-env-vars: false + + - name: Reject unrelated credential access + if: ${{ always() && steps.forbidden.outcome != 'failure' }} + shell: bash + run: exit 1 diff --git a/docs/credential-delivery.md b/docs/credential-delivery.md new file mode 100644 index 0000000..77728d6 --- /dev/null +++ b/docs/credential-delivery.md @@ -0,0 +1,32 @@ +--- +doppler_automation_app: release_plz +--- + +# Credential delivery + +GitHub Actions exchanges its workload identity for a short-lived Doppler token at the credential-consuming step. +Each purpose has its own read-only service account, config, and environment variable. +Shared signing credentials stay in the canonical signing project and are referenced from the consumer config. +The shared credential still carries the signing provider's authority after retrieval. + +| Purpose | Doppler config | GitHub environment variable | Environments | +| --- | --- | --- | --- | +| automation | `windows-spawn.automation` | `DOPPLER_AUTOMATION_IDENTITY_ID` | release | + +The manual **Verify credential access** workflow checks required fields and rejects access to an unrelated config. +Automation also creates a repository-scoped App token with contents read access. +This workflow performs no signing, notarization, registry publication, release creation, or deployment. +Existing environment protections remain in force; their approval does not grant authority for another operation. + +After the reviewed workflows reach the default branch, run the credential-access check for that exact revision. +Keep the current credential delivery until every purpose and environment succeeds and the denial checks pass. +Confirm an explicit access-denied response for each negative check. +Keep the selected identity fixed for that scope check. +A workflow can exchange for every identity whose claims it satisfies; separate accounts alone don't isolate stages with matching claim policies. +Network, authentication, and runner failures leave that evidence inconclusive. +Then disconnect the corresponding Doppler-to-GitHub sync while preserving its target fields, check for remaining consumers, and remove the obsolete GitHub fields and legacy configs. +An expired or revoked registry key is a retirement candidate and must not be copied into a replacement config. +Use the registry's native trusted publisher where supported; an unconfigured publisher stays unavailable until its owner configures the exact binding. + +Store only field names, identity metadata, source revisions, and verification outcomes in issues and logs. +Keep credential values and native authentication state out of the repository. From 1785776d9ac96b7a44760953fcb17089464a2509 Mon Sep 17 00:00:00 2001 From: Yasunobu <42543015+P4suta@users.noreply.github.com> Date: Thu, 22 Oct 2026 04:33:14 +0900 Subject: [PATCH 2/2] fix(ci): register manual credential verification --- .github/workflows/verify-doppler.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/verify-doppler.yml b/.github/workflows/verify-doppler.yml index f4ab677..9992374 100644 --- a/.github/workflows/verify-doppler.yml +++ b/.github/workflows/verify-doppler.yml @@ -2,6 +2,10 @@ name: Verify credential access 'on': + push: + branches: [main] + paths: + - .github/workflows/verify-doppler.yml workflow_dispatch: permissions: {} @@ -13,7 +17,9 @@ concurrency: jobs: automation_0: name: Verify automation access - if: github.ref == 'refs/heads/main' + if: >- + github.event_name == 'workflow_dispatch' && + github.ref == 'refs/heads/main' runs-on: ubuntu-24.04 timeout-minutes: 5 environment: release @@ -41,7 +47,7 @@ jobs: run: exit 1 - name: Verify a read-only repository token - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 + uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0 with: client-id: ${{ steps.credentials.outputs.GITHUB_APP_CLIENT_ID }} private-key: ${{ steps.credentials.outputs.GITHUB_APP_PRIVATE_KEY }}