ci: require Azure Trusted Signing vars before a downloadable Windows build

batch-sign-binaries.mjs and electron-builder.config.cjs::windowsSigning
treat a missing AZURE_SIGN_* set as "sign nothing, warn" — right for a
fork or a local build, but a release-signing lane with the vars
unprovisioned would have published UNSIGNED installers with only a
console.warn in the log. The macOS leg already refuses to build without
CSC/Apple credentials under the publishing gate; the Windows legs now
do the same for the Azure vars, before any payload is built.
This commit is contained in:
ethernet
2026-09-21 18:49:18 -04:00
parent b8c64260d8
commit 241a9ddc8e
2 changed files with 55 additions and 0 deletions

View File

@@ -497,6 +497,30 @@ jobs:
payload-signatures-v1-${{ runner.os }}-${{ matrix.target.label }}-
payload-signatures-v1-${{ runner.os }}-
# Downloadable artifacts (commit builds, candidates, published tags,
# channels) must be Authenticode-signed. Without the Azure Trusted
# Signing vars the build only warns and ships UNSIGNED binaries, which
# is right for a fork or a local build and wrong for a release.
- name: Require Azure signing when publishing
if: inputs.upload_release == true || inputs.release-phase == 'candidate' || inputs.build_commit != '' || inputs.channel != ''
shell: bash
env:
AZURE_SIGN_ENDPOINT: ${{ vars.AZURE_SIGN_ENDPOINT }}
AZURE_SIGN_ACCOUNT: ${{ vars.AZURE_SIGN_ACCOUNT }}
AZURE_SIGN_PROFILE: ${{ vars.AZURE_SIGN_PROFILE }}
AZURE_SIGN_PUBLISHER: ${{ vars.AZURE_SIGN_PUBLISHER }}
AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID }}
run: |
missing=()
for name in AZURE_SIGN_ENDPOINT AZURE_SIGN_ACCOUNT AZURE_SIGN_PROFILE AZURE_SIGN_PUBLISHER AZURE_CLIENT_ID AZURE_TENANT_ID; do
[ -z "${!name}" ] && missing+=("$name")
done
if [ ${#missing[@]} -gt 0 ]; then
echo "::error::required Azure Trusted Signing vars are missing from release-signing: ${missing[*]}"
exit 1
fi
- name: Azure login (OIDC)
if: vars.AZURE_CLIENT_ID != ''
uses: azure/login@f5d393ae46f8fde4be8b75f32e3fc50e654ad0ca # v3.0.1

View File

@@ -223,3 +223,34 @@ def test_malformed_tag_is_refused_before_any_git_work(tmp_path: Path):
"not a release tag" in proc.stdout + proc.stderr
or "does not match pyproject.toml version" in proc.stdout + proc.stderr
)
def _require_step(job: str, name_prefix: str) -> dict:
steps = _workflow()["jobs"][job]["steps"]
(step,) = [s for s in steps if isinstance(s, dict) and s.get("name", "").startswith(name_prefix)]
return step
@pytest.mark.skipif(shutil.which("bash") is None, reason="needs bash")
def test_downloadable_windows_builds_refuse_to_ship_unsigned(tmp_path: Path):
"""The Windows signer only warns without AZURE_SIGN_*; every lane whose
artifacts are downloadable must therefore fail before building, under
the same gate the macOS leg uses for its signing credentials."""
step = _require_step("build-win32-release", "Require Azure signing")
assert step["if"] == _require_step("build-darwin-release", "Require signing credentials")["if"]
assert "build-commit" not in step["if"] and "release-phase == 'candidate'" in step["if"]
names = list(step["env"])
assert {"AZURE_SIGN_ENDPOINT", "AZURE_SIGN_ACCOUNT", "AZURE_SIGN_PROFILE", "AZURE_CLIENT_ID"} <= set(names)
def run(**values: str) -> subprocess.CompletedProcess:
env = {"PATH": os.environ["PATH"], "RUNNER_TEMP": str(tmp_path)}
env.update({name: "" for name in names})
env.update(values)
return subprocess.run(["bash", "-euo", "pipefail", "-c", step["run"]], env=env,
capture_output=True, text=True, timeout=60)
proc = run()
assert proc.returncode != 0 and "::error::" in proc.stdout and "AZURE_SIGN_ENDPOINT" in proc.stdout
assert run(**{name: "x" for name in names}).returncode == 0
partial = run(**{name: "x" for name in names if name != "AZURE_CLIENT_ID"})
assert partial.returncode != 0 and "AZURE_CLIENT_ID" in partial.stdout