Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions .github/workflows/release-plz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
72 changes: 72 additions & 0 deletions .github/workflows/verify-doppler.yml
Original file line number Diff line number Diff line change
@@ -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
32 changes: 32 additions & 0 deletions docs/credential-delivery.md
Original file line number Diff line number Diff line change
@@ -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.
Loading