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
28 changes: 25 additions & 3 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,46 @@ jobs:
timeout-minutes: 10
environment: release-please
permissions:
id-token: write
actions: write
contents: write
pull-requests: write
env:
HAS_APP: ${{ secrets.RELEASE_PLEASE_CLIENT_ID != '' && secrets.RELEASE_PLEASE_PRIVATE_KEY != '' }}
HAS_APP: ${{ vars.DOPPLER_AUTOMATION_IDENTITY_ID != '' }}
steps:
- name: Require GitHub App configuration for manual runs
if: ${{ github.event_name == 'workflow_dispatch' && env.HAS_APP != 'true' }}
run: |
echo "::error::release-please GitHub App credentials are not configured"
exit 1
- name: Fetch automation credentials
id: doppler_automation_1
if: "env.HAS_APP == 'true'"
uses: dopplerhq/secrets-fetch-action@451892f16195f9ac360e1a5bcbf0b5fd0e957534
with:
auth-method: oidc
doppler-identity-id: ${{ vars.DOPPLER_AUTOMATION_IDENTITY_ID }}
doppler-project: snaply
doppler-config: automation
inject-env-vars: false
- name: Reject missing or unresolved automation credentials
if: >-
${{ steps.doppler_automation_1.outcome == 'success' && (
steps.doppler_automation_1.outputs.GITHUB_APP_CLIENT_ID == '' ||
startsWith(steps.doppler_automation_1.outputs.GITHUB_APP_CLIENT_ID, '${') ||
steps.doppler_automation_1.outputs.GITHUB_APP_PRIVATE_KEY == '' ||
startsWith(steps.doppler_automation_1.outputs.GITHUB_APP_PRIVATE_KEY, '${')
) }}
shell: bash
run: exit 1

- name: Mint GitHub App token
id: app-token
if: env.HAS_APP == 'true'
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ secrets.RELEASE_PLEASE_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_PLEASE_PRIVATE_KEY }}
client-id: ${{ steps.doppler_automation_1.outputs.GITHUB_APP_CLIENT_ID }}
private-key: ${{ steps.doppler_automation_1.outputs.GITHUB_APP_PRIVATE_KEY }}
- name: Update release pull request
id: release
if: env.HAS_APP == 'true'
Expand Down
67 changes: 58 additions & 9 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,10 @@ jobs:
timeout-minutes: 25
environment: release
permissions:
id-token: write
contents: read
env:
HAVE_SIGNING: ${{ secrets.ES_USERNAME != '' && secrets.ES_PASSWORD != '' && secrets.CREDENTIAL_ID != '' && secrets.ES_TOTP_SECRET != '' }}
HAVE_SIGNING: ${{ vars.DOPPLER_SIGN_WINDOWS_IDENTITY_ID != '' }}
SIGNER_SUBJECT_CONTAINS: ${{ vars.SIGNER_SUBJECT_CONTAINS }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -154,26 +155,74 @@ jobs:
if ([string]::IsNullOrWhiteSpace($env:SIGNER_SUBJECT_CONTAINS)) {
throw "SIGNER_SUBJECT_CONTAINS is required."
}
- name: Fetch sign_windows credentials
id: doppler_sign_windows_5
uses: dopplerhq/secrets-fetch-action@451892f16195f9ac360e1a5bcbf0b5fd0e957534
with:
auth-method: oidc
doppler-identity-id: ${{ vars.DOPPLER_SIGN_WINDOWS_IDENTITY_ID }}
doppler-project: snaply
doppler-config: sign_windows
inject-env-vars: false
- name: Reject missing or unresolved sign_windows credentials
if: >-
${{ steps.doppler_sign_windows_5.outcome == 'success' && (
steps.doppler_sign_windows_5.outputs.SSLDOTCOM_CREDENTIAL_ID == '' ||
startsWith(steps.doppler_sign_windows_5.outputs.SSLDOTCOM_CREDENTIAL_ID, '${') ||
steps.doppler_sign_windows_5.outputs.SSLDOTCOM_PASSWORD == '' ||
startsWith(steps.doppler_sign_windows_5.outputs.SSLDOTCOM_PASSWORD, '${') ||
steps.doppler_sign_windows_5.outputs.SSLDOTCOM_TOTP_SECRET == '' ||
startsWith(steps.doppler_sign_windows_5.outputs.SSLDOTCOM_TOTP_SECRET, '${') ||
steps.doppler_sign_windows_5.outputs.SSLDOTCOM_USERNAME == '' ||
startsWith(steps.doppler_sign_windows_5.outputs.SSLDOTCOM_USERNAME, '${')
) }}
shell: bash
run: exit 1

- name: Sign portable first-party binaries
uses: SSLcom/esigner-codesign@cf5f6c1d38ad10f47e3ed9aca873f429b1a8d85b # v1.3.2
with:
command: batch_sign
username: ${{ secrets.ES_USERNAME }}
password: ${{ secrets.ES_PASSWORD }}
credential_id: ${{ secrets.CREDENTIAL_ID }}
totp_secret: ${{ secrets.ES_TOTP_SECRET }}
username: ${{ steps.doppler_sign_windows_5.outputs.SSLDOTCOM_USERNAME }}
password: ${{ steps.doppler_sign_windows_5.outputs.SSLDOTCOM_PASSWORD }}
credential_id: ${{ steps.doppler_sign_windows_5.outputs.SSLDOTCOM_CREDENTIAL_ID }}
totp_secret: ${{ steps.doppler_sign_windows_5.outputs.SSLDOTCOM_TOTP_SECRET }}
dir_path: ${{ github.workspace }}\build\release\sign-stage\portable
output_path: ${{ github.workspace }}\build\release\signed\portable
environment_name: PROD
malware_block: "true"
- name: Fetch sign_windows credentials
id: doppler_sign_windows_6
uses: dopplerhq/secrets-fetch-action@451892f16195f9ac360e1a5bcbf0b5fd0e957534
with:
auth-method: oidc
doppler-identity-id: ${{ vars.DOPPLER_SIGN_WINDOWS_IDENTITY_ID }}
doppler-project: snaply
doppler-config: sign_windows
inject-env-vars: false
- name: Reject missing or unresolved sign_windows credentials
if: >-
${{ steps.doppler_sign_windows_6.outcome == 'success' && (
steps.doppler_sign_windows_6.outputs.SSLDOTCOM_CREDENTIAL_ID == '' ||
startsWith(steps.doppler_sign_windows_6.outputs.SSLDOTCOM_CREDENTIAL_ID, '${') ||
steps.doppler_sign_windows_6.outputs.SSLDOTCOM_PASSWORD == '' ||
startsWith(steps.doppler_sign_windows_6.outputs.SSLDOTCOM_PASSWORD, '${') ||
steps.doppler_sign_windows_6.outputs.SSLDOTCOM_TOTP_SECRET == '' ||
startsWith(steps.doppler_sign_windows_6.outputs.SSLDOTCOM_TOTP_SECRET, '${') ||
steps.doppler_sign_windows_6.outputs.SSLDOTCOM_USERNAME == '' ||
startsWith(steps.doppler_sign_windows_6.outputs.SSLDOTCOM_USERNAME, '${')
) }}
shell: bash
run: exit 1

- name: Sign MSIX bundle
uses: SSLcom/esigner-codesign@cf5f6c1d38ad10f47e3ed9aca873f429b1a8d85b # v1.3.2
with:
command: batch_sign
username: ${{ secrets.ES_USERNAME }}
password: ${{ secrets.ES_PASSWORD }}
credential_id: ${{ secrets.CREDENTIAL_ID }}
totp_secret: ${{ secrets.ES_TOTP_SECRET }}
username: ${{ steps.doppler_sign_windows_6.outputs.SSLDOTCOM_USERNAME }}
password: ${{ steps.doppler_sign_windows_6.outputs.SSLDOTCOM_PASSWORD }}
credential_id: ${{ steps.doppler_sign_windows_6.outputs.SSLDOTCOM_CREDENTIAL_ID }}
totp_secret: ${{ steps.doppler_sign_windows_6.outputs.SSLDOTCOM_TOTP_SECRET }}
dir_path: ${{ github.workspace }}\build\release\sign-stage\msix
output_path: ${{ github.workspace }}\build\release\signed\msix
environment_name: PROD
Expand Down
115 changes: 115 additions & 0 deletions .github/workflows/verify-doppler.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
name: Verify credential access

'on':
workflow_dispatch:

permissions: {}

concurrency:
group: verify-credentials
cancel-in-progress: false

jobs:
sign_windows_0:
name: Verify sign_windows access
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
timeout-minutes: 5
environment: release
permissions:
id-token: write
steps:
- name: Fetch sign_windows credentials
id: credentials
uses: dopplerhq/secrets-fetch-action@451892f16195f9ac360e1a5bcbf0b5fd0e957534
with:
auth-method: oidc
doppler-identity-id: ${{ vars.DOPPLER_SIGN_WINDOWS_IDENTITY_ID }}
doppler-project: snaply
doppler-config: sign_windows
inject-env-vars: false
- name: Reject missing or unresolved sign_windows credentials
if: >-
${{ steps.credentials.outcome == 'success' && (
steps.credentials.outputs.SSLDOTCOM_CREDENTIAL_ID == '' ||
startsWith(steps.credentials.outputs.SSLDOTCOM_CREDENTIAL_ID, '${') ||
steps.credentials.outputs.SSLDOTCOM_PASSWORD == '' ||
startsWith(steps.credentials.outputs.SSLDOTCOM_PASSWORD, '${') ||
steps.credentials.outputs.SSLDOTCOM_TOTP_SECRET == '' ||
startsWith(steps.credentials.outputs.SSLDOTCOM_TOTP_SECRET, '${') ||
steps.credentials.outputs.SSLDOTCOM_USERNAME == '' ||
startsWith(steps.credentials.outputs.SSLDOTCOM_USERNAME, '${')
) }}
shell: bash
run: exit 1

- 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_SIGN_WINDOWS_IDENTITY_ID }}
doppler-project: snaply
doppler-config: automation
inject-env-vars: false

- name: Reject unrelated credential access
if: ${{ always() && steps.forbidden.outcome != 'failure' }}
shell: bash
run: exit 1

automation_0:
name: Verify automation access
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
timeout-minutes: 5
environment: release-please
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: snaply
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: snaply
doppler-config: sign_windows
inject-env-vars: false

- name: Reject unrelated credential access
if: ${{ always() && steps.forbidden.outcome != 'failure' }}
shell: bash
run: exit 1
33 changes: 33 additions & 0 deletions docs/credential-delivery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
doppler_automation_app: release_please
---

# 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 |
| --- | --- | --- | --- |
| sign_windows | `snaply.sign_windows` | `DOPPLER_SIGN_WINDOWS_IDENTITY_ID` | release |
| automation | `snaply.automation` | `DOPPLER_AUTOMATION_IDENTITY_ID` | release-please |

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