mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
f5efadfd07
A tag is a mutable pointer owned by the action's publisher. These jobs are not ordinary CI: they hold the code-signing Key Vault token (D-223), the federated storage credentials (D-222), and combine-rules.yml holds a contents:write token that pushes to main. Whoever could repoint v7 would run code with all of that. azure/login and Azure/functions-action were already pinned; the actions/* and github/codeql-action references were not, on the usual argument that GitHub owns them and shares the runner's trust boundary. That argument does not survive the fact that they run in the same jobs as the signing identity. All seven are now pinned by SHA with the version in a trailing comment -- the comment is what keeps the line reviewable, and Dependabot rewrites both together. action-pins.yml is the guard: pins nobody enforces rot, and no review reliably catches one @v4 added to a 200-line workflow. It lives in its own workflow because ci.yml deliberately ignores changes to the other workflow files, so a job placed there would stay silent for exactly the commits it must inspect. Like CI it reports rather than blocks -- main is unprotected; making it binding would mean adding it as a required status check, which is a branch-protection decision. Verified both directions: 49 references pass against the repo, and a fixture with @v7, @main and a bare SHA fails with three annotations while the local reusable workflow refs are skipped. Two floating tool versions closed while here. wix went from the range 5.* to 5.0.2 -- it writes the bytes of the MSI customers install, where AzureSignTool only signs them. ajv was the worse one: unpinned, and npm runs lifecycle scripts at install, so a hijacked release would have executed with the push token before a single rule was validated; now exact versions with --ignore-scripts. Line endings: sed -i rewrote the workflow files as LF. Git normalises on commit, so the content diff is unaffected and indexnow-notify.yml carries no change at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
135 lines
5.8 KiB
YAML
135 lines
5.8 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
|
|
id-token: write # OIDC federated login for the storage writes (secret-less)
|
|
|
|
# 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.
|
|
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 }}
|
|
CODE_SIGN_TIMESTAMP_URL: http://timestamp.acs.microsoft.com
|
|
|
|
jobs:
|
|
gates:
|
|
uses: ./.github/workflows/bootstrap-script-gates.yml
|
|
|
|
publish:
|
|
name: Publish to the download alias
|
|
needs: gates
|
|
runs-on: windows-latest
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
|
|
- 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. See D-223 --
|
|
# both federated credentials are bound to the ref, so this job must never carry a
|
|
# GitHub `environment:`.
|
|
- name: Login to Azure (code-signing identity)
|
|
uses: azure/login@7ddb5af1ef8758cf1353cf3b42f940aee27ba21c # v3
|
|
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@7ddb5af1ef8758cf1353cf3b42f940aee27ba21c # v3
|
|
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 `
|
|
-TimestampUrl $env:CODE_SIGN_TIMESTAMP_URL `
|
|
-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
|
|
with:
|
|
name: signed-scripts
|
|
path: ${{ runner.temp }}/signed-scripts
|
|
if-no-files-found: ignore
|