docs: Windows signing is release-only, allowlisted, and gated by branch protection
Secret Scan / scan (push) Successful in 4s
Build App (Preview) / compute-version (pull_request) Successful in 3s
Secret Scan / scan (pull_request) Successful in 5s
Build App (Preview) / create-release (pull_request) Successful in 3s
Build App (Preview) / test (pull_request) Successful in 4m4s
Build App (Preview) / build-macos (pull_request) Successful in 3m5s
Build App (Preview) / build-windows (pull_request) Successful in 5m35s
Build App (Preview) / build-linux (pull_request) Successful in 10m8s
Build App (Preview) / prune-previews (pull_request) Successful in 2s
Secret Scan / scan (push) Successful in 4s
Build App (Preview) / compute-version (pull_request) Successful in 3s
Secret Scan / scan (pull_request) Successful in 5s
Build App (Preview) / create-release (pull_request) Successful in 3s
Build App (Preview) / test (pull_request) Successful in 4m4s
Build App (Preview) / build-macos (pull_request) Successful in 3m5s
Build App (Preview) / build-windows (pull_request) Successful in 5m35s
Build App (Preview) / build-linux (pull_request) Successful in 10m8s
Build App (Preview) / prune-previews (pull_request) Successful in 2s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -838,10 +838,15 @@ the bundle sets it, which is what lets the shell match the window to the entry.
|
|||||||
|
|
||||||
### Windows code signing
|
### Windows code signing
|
||||||
|
|
||||||
Every Windows build — releases (`build-app.yml`) and PR previews (`build-app-preview.yml`) — is
|
Windows **releases** (`build-app.yml`) are signed with **Azure Artifact Signing**: the app binary,
|
||||||
signed with **Azure Artifact Signing**: the app binary, the MSI, the NSIS installer and its
|
the MSI, the NSIS installer and its uninstaller. Three scripts do it, and "Verify signatures"
|
||||||
uninstaller. Three scripts do it, and "Verify signatures" fails the job if any `.exe`/`.msi` is
|
fails the job if any of them is unsigned or untimestamped, so an unsigned installer cannot ship
|
||||||
unsigned or untimestamped, so an unsigned installer cannot ship quietly.
|
quietly. **PR previews are deliberately not signed**, and `build-app-preview.yml` must not
|
||||||
|
reference the signing secrets. Two reasons: signing is metered (about 1000 signatures a month,
|
||||||
|
against roughly 50 preview builds a month), and a PR's workflow runs the PR's own code, so a
|
||||||
|
secret available there is available to whoever can push a branch. To exercise signing before a
|
||||||
|
merge, dispatch `build-app.yml` on the branch. Every publishing step there is gated on
|
||||||
|
`gitea.event_name == 'push'`, so a dispatch builds, signs and verifies without releasing.
|
||||||
|
|
||||||
- `scripts/windows-signing-setup.ps1` runs once per job. It downloads the signing client
|
- `scripts/windows-signing-setup.ps1` runs once per job. It downloads the signing client
|
||||||
(`Microsoft.ArtifactSigning.Client`) and a .NET runtime into `.code-signing/` in the workspace,
|
(`Microsoft.ArtifactSigning.Client`) and a .NET runtime into `.code-signing/` in the workspace,
|
||||||
@@ -852,10 +857,26 @@ unsigned or untimestamped, so an unsigned installer cannot ship quietly.
|
|||||||
- `scripts/windows-sign.ps1` is the sign command: `signtool sign /dlib` with SHA-256 and the
|
- `scripts/windows-sign.ps1` is the sign command: `signtool sign /dlib` with SHA-256 and the
|
||||||
Microsoft timestamp server, retried. Credentials never reach a command line — the dlib reads
|
Microsoft timestamp server, retried. Credentials never reach a command line — the dlib reads
|
||||||
`AZURE_TENANT_ID` / `AZURE_CLIENT_ID` / `AZURE_CLIENT_SECRET` from the environment.
|
`AZURE_TENANT_ID` / `AZURE_CLIENT_ID` / `AZURE_CLIENT_SECRET` from the environment.
|
||||||
|
**It signs only an allowlist of what ships**, about 4 signatures per release. Tauri also
|
||||||
|
presents build-time tools: the WiX extension DLLs, the NSIS plugins, and the app binary a
|
||||||
|
second time for the second bundle type. Signing those would roughly triple the metered count
|
||||||
|
for no user-visible benefit. If the app ever ships resource DLLs or sidecars, extend the
|
||||||
|
allowlist, or they will go out unsigned. Tauri reports a failed sign command only as
|
||||||
|
"failed to run powershell", so the script keeps a transcript (`.code-signing/sign-output.log`,
|
||||||
|
`signtool /debug` included), and the job prints it on failure.
|
||||||
- `scripts/windows-verify-signatures.ps1` checks `signtool verify /pa` plus a timestamp.
|
- `scripts/windows-verify-signatures.ps1` checks `signtool verify /pa` plus a timestamp.
|
||||||
|
|
||||||
Secrets (repository): the three `AZURE_*` above plus `ARTIFACT_SIGNING_ENDPOINT`,
|
Secrets (repository): the three `AZURE_*` above plus `ARTIFACT_SIGNING_ENDPOINT`,
|
||||||
`ARTIFACT_SIGNING_ACCOUNT_NAME`, `ARTIFACT_SIGNING_PROFILE_NAME`. Four things are load-bearing:
|
`ARTIFACT_SIGNING_ACCOUNT_NAME`, `ARTIFACT_SIGNING_PROFILE_NAME`. They are referenced only by the
|
||||||
|
two Windows steps of `build-app.yml` that need them ("Prepare code signing" and "Build Tauri
|
||||||
|
app"), never by the preview workflow, never echoed, and never on a command line. The repo is
|
||||||
|
public, so its Actions logs are too. Gitea masks the secret values, and the signing dlib's
|
||||||
|
`/debug` output carries no tokens (checked against its strings). Anyone who can push to this
|
||||||
|
repo can reach the secrets through a workflow file, so repo write access is the boundary.
|
||||||
|
`main` is branch-protected (no direct or force pushes; changes land by merging a PR), so a signed
|
||||||
|
release only ever comes from a merged, visible change. The
|
||||||
|
Azure side should hold the rest: an app registration with only the signer role on this one
|
||||||
|
certificate profile, and a client secret with an expiry. Four things are load-bearing:
|
||||||
|
|
||||||
- **`metadata.json` excludes every credential but `EnvironmentCredential`.** The dlib uses
|
- **`metadata.json` excludes every credential but `EnvironmentCredential`.** The dlib uses
|
||||||
`DefaultAzureCredential`, whose chain ends in `InteractiveBrowserCredential`; the runners run as
|
`DefaultAzureCredential`, whose chain ends in `InteractiveBrowserCredential`; the runners run as
|
||||||
|
|||||||
Reference in New Issue
Block a user