Files
Unlucky-Launcher/.github/workflows/signing-status.yml
T
AskaEth bc1b09b776 fix(packaging): 播报 workflow 的种子只在 main 注入 + --key 用法错回 2 + 指针修正(M255 r1 P2-1/2/3)
P2-1(安全):`signing-status.yml` 原先把生产签名种子注入到 `pull_request` 与任意 ref 的
`workflow_dispatch` 运行里,而 CI 跑的是那个 ref 自己的代码 ⇒ 任何能推分支的人都能把种子搬走
(这把种子能给全量已装客户端签可信清单)。改成:
  - 去掉 `pull_request` 触发;
  - 注入行加 ref 门:`github.ref == 'refs/heads/main' && secrets.… || ''`;
  - 非 main 用 `ULL_SIGNING_KEY_WITHHELD` 说明原因,于是 ② 读「未判定(未注入)」而不是「没配」。
runbook §2 步② 加硬顺序:先合这笔、再录 Secret。

P2-2:`--key` 读不了(不存在 / 是目录 / 非 UTF-8)曾在 main 的 try 之外抛 traceback + rc=1,
与「未就绪」同码;现在统一 rc=2 + 一行说明,并补用例 `key_unreadable`。

P2-3:4 处文案指针修正(`signing-ready.yml` → `signing-status.yml`;§3 → §2 步③;§1–§3 →
§2 步①–③;§4 → §2 步⑤ / §5)。新增两条机检:`section_refs`(输出的 `§N` 必须是 runbook 真实
章节号)与 `signing_ready_wiring_selftest.py`(文档里的 `packaging/…`/`crates/…`/`.github/…`
路径必须真存在),后者另带三处变异重放:去掉 ref 门 / 加回 pull_request / 改坏文档路径 ⇒ 必须红。

顺带修掉 r1 复审指出的两处小问题:`--release-assets` 里只有 `manifest.json.sig` 时的子串假读数
(先剥 `.sig` 再判清单)、③ 那个从不参与判定的 bool 保持原样(无行为影响)。

selftest 拆成两份(400 行红线):判据本体 + 接线/文案。guards.yml 仍 399 行零改动。
2026-11-11 20:17:04 +08:00

122 lines
8.3 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Signing status(播报,不是门禁)
# 第一句就是口径:**这是播报,不是门禁。它红了不代表不能合,它绿了也不代表签名就绪。**
# 就绪度的结论只在 job summary 的三行字里(`packaging/check_signing_ready.py --line` 写的),
# 不在 job 的颜色里。**本 workflow 不应被设为 required check**(GitHub 侧分支保护里不要勾它)。
#
# 为什么要有它(M255):M254(CLAIMAUDIT)查出 `crates/core/src/update/trust.rs` 的 `PRODUCTION`
# 表在 `PINNED-KEYS` 标记之间是空的,而空表一律 `KeyringEmpty` 拒装 ⇒ 今天任何正式二进制都验不过任何
# 清单(fail-closed,用户端表现为「更新永远失败」)。工具链早就齐了,缺的是**可见性**:这三行读数让
# 「今天差什么」在每次运行里都摆在台面上,而不是等发版时才发现。
#
# 口径四条(由 `packaging/tests/signing_ready_wiring_selftest.py` 的接线判据机器校验,不是靠注释自律):
# ① 就绪度那一步抓 `rc=$?` 之后**恒 `exit 0`** —— 未就绪用 `::warning::` 播报,不用 error 级注解
# (error 会让人以为构建挂了,与播报定位直接冲突)。真正的红留在 `release-publish.yml` 的签名步:
# 那里今天必然红是**对的**,因为签不出来的产物本来就不能发。
# ② 文件名/名字都不带 guards、check 字样:它不是守卫,是一条状态播报。
# ③ 本文件**刻意不声明 `workflow_call:`**:没有这个触发器,别的 workflow 就没法 `uses:` 它 ——
# 「不能被接进 dev 线变成必需检查」是一条机制,不是一句嘱咐。接线自检另外扫全部 workflow,
# 任何 `uses: …/signing-status.yml` 出现即红(防的是有人先加 workflow_call 再接进来)。
# ④ **生产签名种子只在 `refs/heads/main` 上注入**,且**没有 `pull_request` 触发**(M255 r1 P2-1)。
# 这条是本文件唯一的**安全**口径,别为了「PR 上也能看读数」把它放开:
# - 仓库内分支的 PR / 任意分支的 dispatch **拿得到**仓库 Secret(只有 fork / Dependabot 拿不到);
# - 而这把种子能给**全量已安装客户端**签出被信任的清单(自更新链 = 用户端 RCE),
# 既有设计把它锁在 tag ref 后面(`release.yml` 的 `if: … github.ref_type == 'tag'`)。
# - 播报**不需要**在 PR 上跑:PR 改了 `check_signing_ready.py` 时,CI 会在那个 PR 自己的代码上执行它,
# 若同时注入种子,等于把种子交给任何能推分支的人。
# 门由两行 env 表达(同一个条件、同一个地方可审):钥给 main,非 main 则给 `ULL_SIGNING_KEY_WITHHELD`
# 一个原因串 —— 于是 ② 读成「未判定」而不是「没配」(把策略说成缺配是一次假读数)。
#
# 判据本体与 0/1/2 的语义:`packaging/check_signing_ready.py` 的文件头;
# 人类要做的那几步:`packaging/docs/signing-runbook.md`。
on:
push:
branches: [main]
# ⚠ 刻意**没有** `pull_request`(见口径④)。要读 PR 的签名就绪度,就跑 push 到分支或对着 main dispatch。
workflow_dispatch:
permissions:
contents: read
jobs:
# 尺子自测:它红 = **判据脚本坏了**(不是「签名没就绪」)。两者措辞在 summary 里分开写。
# 这一步照常会红,因为「尺子坏了」本来就该有人看见 —— 但它不阻断任何东西:本 workflow 独立、
# 非必需,也不在 `dev-guards.yml` 的 `uses:` 里。
selftest:
name: 判据自测(三行/退出码/变异重放/接线)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 判据本体(三行 / 0/1/2 / 两向退出码变异 / --key 用法错 / 指针章节)
run: python3 packaging/tests/signing_ready_selftest.py
# 接线单独一份(400 行红线):播报不是门禁、**生产种子只在 main 注入**、文档指针真的存在,
# 以及「去掉 ref 门 / 加回 pull_request / 改坏文档路径 ⇒ 必须红」三处变异重放。
- name: 接线(ref 门 / 不被别的 workflow 复用 / 无 error 级注解 / 文档路径存在性 + 三处变异)
run: python3 packaging/tests/signing_ready_wiring_selftest.py
- name: 真跑三种情形(今天的状态 / 假 PinnedKey 副本 / 用法错)
run: |
set -uo pipefail
work="$RUNNER_TEMP/signing-ready"
mkdir -p "$work"
# 结论只打印,不判定:这里要的是「三种情形各自的退出码与三行读数长什么样」,
# 判据在上一跑的 selftest 里。所有 `|| true` 只为让这一步本身不变红。
run_and_show() { # run_and_show <说明> <命令...>
note="$1"; shift
echo "── $note"
python3 packaging/check_signing_ready.py "$@" 2>&1 || true
echo
}
run_and_show "今天的状态(真实 trust.rs:表空 ⇒ 未就绪)"
# 副本夹一条假 PinnedKey(**不碰真实 trust.rs**):造出「就绪」的正路长什么样。
python3 - "$work" <<'PY'
import sys
from pathlib import Path
trust = Path("crates/core/src/update/trust.rs").read_text(encoding="utf-8")
begin = trust.index(">>> PINNED-KEYS >>>") + len(">>> PINNED-KEYS >>>")
end = trust.index("<<< PINNED-KEYS <<<")
fake = ' PinnedKey { key_id: "demo-not-a-real-key", public_key_hex: "' + "ab" * 32 + '" },\n'
Path(sys.argv[1], "trust-with-fake-key.rs").write_text(
trust[:begin] + "\n" + fake + trust[end:], encoding="utf-8"
)
PY
run_and_show "副本夹一条假 PinnedKey(②仍缺钥 ⇒ 仍未就绪,但 ① 会变就绪)" \
--trust "$work/trust-with-fake-key.rs"
run_and_show "用法错(不存在的 trust.rs ⇒ 2)" --trust "$work/nope/trust.rs" || true
# 播报:只把三行搬进 job summary,不看颜色。
status:
name: 签名就绪度(播报)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# ② 要读「环境里有没有钥」,所以把 Secret 传进来 —— **只在 main ref 上**(口径④)。
# 门是下面这两行 env(同一个条件),不是 `if:`:这样非 main 的运行照样跑完三行读数,
# 只是 ② 读成「未判定」,而不是整个 job 被跳过后一片空白。
# ⚠ 这两行是安全边界,接线自检钉着它们(去掉 ref 门 / 加回 pull_request 都会当场红)。
- name: 三行就绪度(脚本给 0/1/2;本步恒 exit 0)
env:
ULL_MANIFEST_SIGNING_KEY: ${{ github.ref == 'refs/heads/main' && secrets.ULL_MANIFEST_SIGNING_KEY || '' }}
ULL_SIGNING_KEY_WITHHELD: ${{ github.ref == 'refs/heads/main' && '' || '非 main ref(按设计不注入 Secret;要读这一行请对 main 跑)' }}
run: |
set -uo pipefail
set +e
python3 packaging/check_signing_ready.py --line > "$RUNNER_TEMP/signing-ready.txt" 2>&1
rc=$?
set -e
cat "$RUNNER_TEMP/signing-ready.txt"
case "$rc" in
0) echo "::notice::签名就绪:信任表非空,且环境里的钥与它同源。这是一条播报,不是门禁。" ;;
1) echo "::warning::签名未就绪(或 ② 未判定):该做什么见 packaging/docs/signing-runbook.md §2 的下单清单。" ;;
*) echo "::warning::就绪度脚本用不了(rc=$rc,用法错):这是尺子的问题,不是签名状态 —— 看上面的 stderr。" ;;
esac
{
echo
echo "- 本 workflow 是**播报**,不是门禁:**不应被设为 required**,也不阻断任何其它 job。"
echo "- 颜色 ≠ 结论:绿色只说明脚本跑完了;结论只看上面三行(尤其 ①「信任表」)。"
echo "- **Secret 只在 \`refs/heads/main\` 上注入**(生产签名种子锁在 main / tag 后面);非 main ref 的 ② 显示「未判定」而不是「没配」。"
echo "- 真门禁在哪:\`release-publish.yml\` 的签名步(\`--check-pinned-keyring\` / \`--sign\` / \`--verify-pinned\`)。"
} >> "$GITHUB_STEP_SUMMARY"
exit 0