ci: sign only what ships, stop signing previews, log signing output
Secret Scan / scan (push) Successful in 5s
Build App (Preview) / compute-version (pull_request) Successful in 4s
Secret Scan / scan (pull_request) Successful in 4s
Build App (Preview) / create-release (pull_request) Successful in 2s
Build App (Preview) / test (pull_request) Successful in 5m25s
Build App (Preview) / build-macos (pull_request) Successful in 4m15s
Build App (Preview) / build-windows (pull_request) Successful in 6m11s
Build App (Preview) / build-linux (pull_request) Successful in 14m11s
Build App (Preview) / prune-previews (pull_request) Successful in 5s
Secret Scan / scan (push) Successful in 5s
Build App (Preview) / compute-version (pull_request) Successful in 4s
Secret Scan / scan (pull_request) Successful in 4s
Build App (Preview) / create-release (pull_request) Successful in 2s
Build App (Preview) / test (pull_request) Successful in 5m25s
Build App (Preview) / build-macos (pull_request) Successful in 4m15s
Build App (Preview) / build-windows (pull_request) Successful in 6m11s
Build App (Preview) / build-linux (pull_request) Successful in 14m11s
Build App (Preview) / prune-previews (pull_request) Successful in 5s
Artifact Signing is metered at about 1000 signatures a month, and there were 67 Windows builds last month. - windows-sign.ps1 signs only what reaches users: the app binary, the MSI, the NSIS installer and the uninstaller. It skips the WiX extension DLLs and NSIS plugins that Tauri also offers, and any file that is already validly signed. That is 4 signatures per release. - Previews are no longer signed, so the preview workflow no longer references the signing secrets. A PR's workflow runs the PR's own code. The inline TAURI_CONFIG there, which the v2 CLI never read, becomes a real `--config`. - Tauri reports a failed sign command only as "failed to run powershell". The script now keeps a transcript, signtool /debug included, and the release job prints it on failure. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+78
-37
@@ -1,7 +1,18 @@
|
||||
# windows-sign.ps1 <file> - sign one file with Azure Artifact Signing.
|
||||
#
|
||||
# Tauri's bundle.windows.signCommand, set up by windows-signing-setup.ps1.
|
||||
# Tauri calls it once per file it signs and fails the build on a non-zero exit.
|
||||
# Tauri calls it once per file it wants signed and fails the build on a
|
||||
# non-zero exit - but shows none of this script's output when it does, so
|
||||
# everything is also appended to $TRIPLE_C_SIGN_OUTPUT, which the workflow
|
||||
# prints if the job fails.
|
||||
#
|
||||
# Only what ships is signed. Tauri also offers build-time tools - the WiX
|
||||
# extension DLLs candle/light load, the NSIS plugins makensis embeds - and
|
||||
# each signature is metered (about 1000 a month), so those are skipped. The
|
||||
# allowlist below is the whole of what reaches users: the app binary, the MSI,
|
||||
# the NSIS installer, and the uninstaller makensis writes to the job's temp
|
||||
# directory. A file that already carries a valid signature is skipped too:
|
||||
# Tauri presents the app binary once per bundle type.
|
||||
#
|
||||
# This may run as 32-bit PowerShell: the NSIS uninstaller is signed from inside
|
||||
# makensis, which is 32-bit and resolves `powershell` to the SysWOW64 copy. So
|
||||
@@ -15,47 +26,77 @@
|
||||
param([Parameter(Mandatory = $true)][string]$Path)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$utf8 = New-Object System.Text.UTF8Encoding $false
|
||||
|
||||
foreach ($name in 'TRIPLE_C_SIGNTOOL', 'TRIPLE_C_SIGN_DLIB', 'TRIPLE_C_SIGN_METADATA', 'TRIPLE_C_SIGN_TIMESTAMP',
|
||||
'AZURE_TENANT_ID', 'AZURE_CLIENT_ID', 'AZURE_CLIENT_SECRET') {
|
||||
if (-not [Environment]::GetEnvironmentVariable($name)) {
|
||||
throw "$name is not set - run windows-signing-setup.ps1 first and pass the AZURE_* secrets to this step"
|
||||
function Write-Log([string]$Text) {
|
||||
Write-Host $Text
|
||||
if ($env:TRIPLE_C_SIGN_OUTPUT) {
|
||||
try { [IO.File]::AppendAllText($env:TRIPLE_C_SIGN_OUTPUT, "$Text`n", $utf8) } catch { }
|
||||
}
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $Path)) { throw "No such file to sign: $Path" }
|
||||
|
||||
# /d names the product in the UAC prompt, which for an MSI would otherwise show
|
||||
# a temporary file name. The timestamp is what keeps the signature valid after
|
||||
# the short-lived Artifact Signing certificate expires, so it is not optional.
|
||||
$arguments = @(
|
||||
'sign', '/v',
|
||||
'/fd', 'SHA256',
|
||||
'/tr', $env:TRIPLE_C_SIGN_TIMESTAMP, '/td', 'SHA256',
|
||||
'/d', 'Triple-C',
|
||||
'/dlib', $env:TRIPLE_C_SIGN_DLIB,
|
||||
'/dmdf', $env:TRIPLE_C_SIGN_METADATA,
|
||||
$Path
|
||||
)
|
||||
|
||||
# Timestamp servers and the signing endpoint both fail transiently now and
|
||||
# then; a retry is cheaper than a failed three-platform release.
|
||||
#
|
||||
# Stop is relaxed around the call: Tauri captures this script's output, and
|
||||
# PowerShell 5.1 turns a native command's stderr into error records when its
|
||||
# own streams are redirected - under Stop, signtool's first warning would kill
|
||||
# the script before its exit code is read.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
for ($attempt = 1; $attempt -le 3; $attempt++) {
|
||||
& $env:TRIPLE_C_SIGNTOOL @arguments 2>&1 | ForEach-Object { "$_" }
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
# The evidence "Verify signatures" needs for files it cannot see
|
||||
# afterwards - the NSIS uninstaller is embedded in the installer.
|
||||
if ($env:TRIPLE_C_SIGN_LOG) {
|
||||
[IO.File]::AppendAllText($env:TRIPLE_C_SIGN_LOG, "$Path`n", (New-Object System.Text.UTF8Encoding $false))
|
||||
try {
|
||||
foreach ($name in 'TRIPLE_C_SIGNTOOL', 'TRIPLE_C_SIGN_DLIB', 'TRIPLE_C_SIGN_METADATA', 'TRIPLE_C_SIGN_TIMESTAMP',
|
||||
'TRIPLE_C_SIGN_TMP', 'TRIPLE_C_SIGN_LOG', 'AZURE_TENANT_ID', 'AZURE_CLIENT_ID', 'AZURE_CLIENT_SECRET') {
|
||||
if (-not [Environment]::GetEnvironmentVariable($name)) {
|
||||
throw "$name is not set - run windows-signing-setup.ps1 first and pass the AZURE_* secrets to this step"
|
||||
}
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $Path)) { throw "No such file to sign: $Path" }
|
||||
$full = (Resolve-Path -LiteralPath $Path).ProviderPath
|
||||
Write-Log "== $full"
|
||||
|
||||
$ships = ($full -match '\\target\\release\\[^\\]+\.exe$') -or
|
||||
($full -match '\\target\\release\\bundle\\(msi|nsis)\\[^\\]+\.(msi|exe)$') -or
|
||||
$full.StartsWith($env:TRIPLE_C_SIGN_TMP.TrimEnd('\') + '\', [StringComparison]::OrdinalIgnoreCase)
|
||||
if (-not $ships) {
|
||||
Write-Log 'skipped: build-time file, not shipped'
|
||||
exit 0
|
||||
}
|
||||
Write-Host "signtool exited $LASTEXITCODE signing $Path (attempt $attempt of 3)"
|
||||
if ($attempt -lt 3) { Start-Sleep -Seconds (10 * $attempt) }
|
||||
|
||||
$existing = Get-AuthenticodeSignature -LiteralPath $full
|
||||
if ($existing.Status -eq 'Valid' -and $existing.TimeStamperCertificate) {
|
||||
Write-Log "skipped: already signed by $($existing.SignerCertificate.Subject)"
|
||||
[IO.File]::AppendAllText($env:TRIPLE_C_SIGN_LOG, "$full`n", $utf8)
|
||||
exit 0
|
||||
}
|
||||
|
||||
# /d names the product in the UAC prompt, which for an MSI would otherwise
|
||||
# show a temporary file name. The timestamp is what keeps the signature
|
||||
# valid after the short-lived Artifact Signing certificate expires, so it
|
||||
# is not optional. /debug makes the dlib say why it failed, into the log.
|
||||
$arguments = @(
|
||||
'sign', '/v', '/debug',
|
||||
'/fd', 'SHA256',
|
||||
'/tr', $env:TRIPLE_C_SIGN_TIMESTAMP, '/td', 'SHA256',
|
||||
'/d', 'Triple-C',
|
||||
'/dlib', $env:TRIPLE_C_SIGN_DLIB,
|
||||
'/dmdf', $env:TRIPLE_C_SIGN_METADATA,
|
||||
$full
|
||||
)
|
||||
|
||||
# Stop is relaxed around the call: Tauri captures this script's output,
|
||||
# and PowerShell 5.1 turns a native command's stderr into error records
|
||||
# when its own streams are redirected - under Stop, signtool's first
|
||||
# warning would kill the script before its exit code is read. Timestamp
|
||||
# servers and the signing endpoint fail transiently now and then, hence
|
||||
# the retries.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
for ($attempt = 1; $attempt -le 3; $attempt++) {
|
||||
& $env:TRIPLE_C_SIGNTOOL @arguments 2>&1 | ForEach-Object { Write-Log "$_" }
|
||||
$code = $LASTEXITCODE
|
||||
if ($code -eq 0) {
|
||||
# The evidence "Verify signatures" needs for the file it cannot see
|
||||
# afterwards - the NSIS uninstaller is embedded in the installer.
|
||||
[IO.File]::AppendAllText($env:TRIPLE_C_SIGN_LOG, "$full`n", $utf8)
|
||||
Write-Log 'signed'
|
||||
exit 0
|
||||
}
|
||||
Write-Log "signtool exited $code (attempt $attempt of 3)"
|
||||
if ($attempt -lt 3) { Start-Sleep -Seconds (10 * $attempt) }
|
||||
}
|
||||
exit 1
|
||||
} catch {
|
||||
Write-Log "windows-sign.ps1 failed: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
exit 1
|
||||
|
||||
@@ -145,6 +145,7 @@ $tauriConfigPath = Join-Path $root 'tauri.signing.conf.json'
|
||||
$tmpDir = Join-Path $root 'tmp'
|
||||
New-Item -ItemType Directory -Path $tmpDir | Out-Null
|
||||
$signLog = Join-Path $root 'signed.log'
|
||||
$signOutput = Join-Path $root 'sign-output.log'
|
||||
|
||||
# $GITHUB_ENV is KEY=VALUE lines. Written without a BOM: PowerShell 5.1's
|
||||
# utf8 encoding adds one, which would corrupt the first key.
|
||||
@@ -157,6 +158,7 @@ $lines = @(
|
||||
"DOTNET_ROOT_X64=$dotnetDir"
|
||||
"TRIPLE_C_TAURI_SIGN_CONFIG=$tauriConfigPath"
|
||||
"TRIPLE_C_SIGN_LOG=$signLog"
|
||||
"TRIPLE_C_SIGN_OUTPUT=$signOutput"
|
||||
"TRIPLE_C_SIGN_TMP=$tmpDir"
|
||||
"TEMP=$tmpDir"
|
||||
"TMP=$tmpDir"
|
||||
|
||||
Reference in New Issue
Block a user