mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 04:32:32 +00:00
60a41e9778
The code-signing identity trusts only environment:release now, so build-agent release, build-msi sign-loader and release, and publish-scripts publish run in it. Every job that federates to Azure carries an environment; the ref is no longer a trusted subject. Help texts of the storage and endpoint helpers follow: the local build publishes nothing since D-354. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
142 lines
6.1 KiB
YAML
142 lines
6.1 KiB
YAML
name: Publish customer scripts
|
|
|
|
# Keeps the scripts behind download.autopilotmonitor.com/agent/ current WITHOUT waiting
|
|
# for an agent release. Before this workflow existed, the script blobs rotated only on a
|
|
# publish_as_stable run of build-agent.yml, so a bootstrap fix sat unpublished until the
|
|
# next agent cutover -- while the WDP bootstrap MSI downloads the published copy on every
|
|
# enrolling device.
|
|
#
|
|
# Same publish code as the release path (.github/scripts/Publish-BootstrapScripts.ps1);
|
|
# this workflow only supplies a second trigger for it.
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- 'scripts/Bootstrap/**'
|
|
- 'scripts/CustomerSetup/**'
|
|
- '.github/scripts/Publish-BootstrapScripts.ps1'
|
|
workflow_dispatch:
|
|
inputs:
|
|
dry_run:
|
|
description: 'Run every check and report drift, upload nothing'
|
|
required: true
|
|
type: boolean
|
|
default: false
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
# Shared with build-agent.yml: a script publish and an agent release both touch
|
|
# version.json, and the bootstrapVersion read-modify-write must not interleave with the
|
|
# release writing its agent fields. Never cancel -- a half-published set is worse than a queue.
|
|
concurrency:
|
|
group: agent-blob-publish
|
|
cancel-in-progress: false
|
|
|
|
# Authenticode for the customer scripts. Same identity and same repository variables as
|
|
# build-agent.yml -- the loader on every device pins this publisher, so the two must not
|
|
# drift apart. The publisher takes every host, the timestamp server included, from
|
|
# .github/scripts/Get-Endpoints.ps1.
|
|
env:
|
|
CODE_SIGN_CLIENT_ID: ${{ vars.CODE_SIGN_CLIENT_ID }}
|
|
CODE_SIGN_CERTIFICATE_NAME: ${{ vars.CODE_SIGN_CERTIFICATE_NAME }}
|
|
CODE_SIGN_KEY_VAULT_URL: ${{ vars.CODE_SIGN_KEY_VAULT_URL }}
|
|
CODE_SIGN_EXPECTED_SUBJECT: ${{ vars.CODE_SIGN_EXPECTED_SUBJECT }}
|
|
|
|
jobs:
|
|
# $/ is the self-repository form: this repository at the running commit, never a
|
|
# path some earlier step could have written to the workspace.
|
|
gates:
|
|
uses: $/.github/workflows/bootstrap-script-gates.yml
|
|
|
|
publish:
|
|
name: Publish to the download alias
|
|
needs: gates
|
|
runs-on: windows-latest
|
|
# Both identities trust only the `release` environment, which admits main only.
|
|
environment: release
|
|
# Job-scoped: only the publish job federates to Azure, the gates job never holds an
|
|
# OIDC token it does not need.
|
|
permissions:
|
|
contents: read
|
|
id-token: write # OIDC federated login for the storage writes (secret-less)
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Check code-signing configuration
|
|
shell: pwsh
|
|
run: |
|
|
foreach ($name in 'CODE_SIGN_CLIENT_ID', 'CODE_SIGN_CERTIFICATE_NAME', 'CODE_SIGN_KEY_VAULT_URL') {
|
|
if ([string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($name))) {
|
|
throw "Repository variable $name is not set. Published customer scripts must be signed."
|
|
}
|
|
}
|
|
|
|
# First, and only to fetch the Key Vault token: azure/login sets the default az
|
|
# account and the storage login below has to be the one that stays.
|
|
- name: Login to Azure (code-signing identity)
|
|
uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0
|
|
with:
|
|
client-id: ${{ env.CODE_SIGN_CLIENT_ID }}
|
|
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
|
allow-no-subscriptions: true
|
|
|
|
- name: Acquire Key Vault token for signing
|
|
shell: pwsh
|
|
run: |
|
|
$ErrorActionPreference = 'Stop'
|
|
$token = az account get-access-token --scope https://vault.azure.net/.default --query accessToken --output tsv
|
|
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($token)) {
|
|
throw "Could not acquire a Key Vault token for the code-signing identity."
|
|
}
|
|
Write-Host "::add-mask::$token"
|
|
"CODE_SIGN_AKV_ACCESS_TOKEN=$token" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
|
|
|
# Authorizes the eu blob writes and the AdminConfiguration merge. The gates job above
|
|
# runs the same publisher with -DryRun and deliberately without a login -- a dry run
|
|
# must never need Azure credentials.
|
|
- name: Login to Azure (OIDC)
|
|
uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0
|
|
with:
|
|
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
|
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
|
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
|
|
|
# Version pinned so a new or compromised NuGet release cannot flow into signing
|
|
# unreviewed; roll-forward because the tool targets an older runtime than the SDK
|
|
# preinstalled on the runner.
|
|
- name: Install AzureSignTool
|
|
env:
|
|
DOTNET_ROLL_FORWARD: LatestMajor
|
|
run: dotnet tool install --no-cache --global AzureSignTool --version 7.0.1
|
|
|
|
- name: Publish bootstrap + customer scripts
|
|
shell: pwsh
|
|
env:
|
|
DOTNET_ROLL_FORWARD: LatestMajor
|
|
run: |
|
|
$dryRun = '${{ inputs.dry_run }}' -eq 'true'
|
|
./.github/scripts/Publish-BootstrapScripts.ps1 `
|
|
-LegacySasToken '${{ secrets.AZURE_BLOB_AGENT_LEGACY_SAS_TOKEN }}' `
|
|
-SigningToken $env:CODE_SIGN_AKV_ACCESS_TOKEN `
|
|
-KeyVaultUrl $env:CODE_SIGN_KEY_VAULT_URL `
|
|
-CertificateName $env:CODE_SIGN_CERTIFICATE_NAME `
|
|
-ExpectedPublisher $env:CODE_SIGN_EXPECTED_SUBJECT `
|
|
-SignedOutputDir "$env:RUNNER_TEMP/signed-scripts" `
|
|
-DryRun:$dryRun
|
|
|
|
# The exact bytes that were (or, on a dry run, would have been) uploaded. A dry-run
|
|
# dispatch is therefore the way to inspect a signed script locally before it reaches
|
|
# a single customer. Kept on failure too: a failed alias verification is precisely
|
|
# when someone wants to look at the file.
|
|
- name: Upload signed scripts
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: signed-scripts
|
|
path: ${{ runner.temp }}/signed-scripts
|
|
if-no-files-found: ignore
|