diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml index c8e3e0a..780fef6 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@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.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..9992374 --- /dev/null +++ b/.github/workflows/verify-doppler.yml @@ -0,0 +1,72 @@ +--- +name: Verify credential access + +'on': + push: + branches: [main] + paths: + - .github/workflows/verify-doppler.yml + workflow_dispatch: + +permissions: {} + +concurrency: + group: verify-credentials + cancel-in-progress: false + +jobs: + automation_0: + name: Verify automation access + if: >- + github.event_name == 'workflow_dispatch' && + 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@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0 + 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.