feat: bind each node to the binary it runs at its first handshake; refuse changes that are not in a CI-signed manifest [CI]
ci/woodpecker/push/main-amd64-build Pipeline failed

This commit is contained in:
justme committed 2026-10-08 05:39:13 +08:00
1 parent 117d34a2af
commit 4841f7b15b
40 files changed
+1011 -21

No files matched your search

+43 -6
View File
@@ -122,11 +122,11 @@ fetch_offer() {
cp "$OFFER_MANIFEST" "$WORK/feed.manifest"; cp "$OFFER_SIG" "$WORK/feed.sig"
fi
fetch_mailbox
# 信箱里没有东西,而仓库里的清单有问题(不是「已经装过」):留下仓库的那份,让后面照常报出验证失败的原因,而不是悄悄当作没有更新
case "$feed_error" in
""|"stale manifest"*) ;;
*) if [ ! -s "$OFFER_MANIFEST" ]; then cp "$WORK/feed.manifest" "$OFFER_MANIFEST"; cp "$WORK/feed.sig" "$OFFER_SIG"; fi ;;
esac
# 信箱里没有东西,而仓库里的清单没通过(验证失败,或者就是已经装的这一份):留下仓库的那份,让后面照常报出原因,
# 而不是悄悄当作没有更新;「已经装的这一份」也要留下,因为要把它交给节点握手时带给 Backend
if [ -n "$feed_error" ] && [ ! -s "$OFFER_MANIFEST" ]; then
cp "$WORK/feed.manifest" "$OFFER_MANIFEST"; cp "$WORK/feed.sig" "$OFFER_SIG"
fi
}
# 把业务进程放的清单和签名复制到临时目录;不是普通文件(缺失、符号链接)就当作没有
@@ -165,6 +165,36 @@ write_status() { # state available_json error
fi
}
# 节点握手时要带给 Backend 的「我是从哪份 CI 签名清单安装的」:Backend 用 CI 公钥验过,才相信清单里列的官方二进制哈希。
# 写在节点只读挂载的状态目录里(节点自己改不了),清单在前、签名在后;内容没变就不写(保护路由器的闪存)。
write_installed() {
if cmp -s "$OFFER_MANIFEST" "$STATE_DIR/$COMPONENT.installed.manifest" 2>/dev/null && cmp -s "$OFFER_SIG" "$STATE_DIR/$COMPONENT.installed.sig" 2>/dev/null; then
return 0
fi
for pair in "installed.manifest:$OFFER_MANIFEST" "installed.sig:$OFFER_SIG"; do
name="${pair%%:*}"; src="${pair#*:}"
if [ -n "$DATA_VOLUME" ]; then
"$ENGINE" run --pull=never --rm -i --user 0 --network none --read-only --cap-drop ALL --cap-add DAC_OVERRIDE --security-opt no-new-privileges \
-v "$STATUS_VOLUME:/status" --entrypoint /bin/sh "$HELPER_IMAGE" \
-c 'tmp=$(mktemp /status/.installed.XXXXXX); cat > "$tmp"; chmod 644 "$tmp"; mv -f "$tmp" "/status/$1"' sh "$name" < "$src" || return 0
else
destination="$(mktemp "$STATUS_DIR/.installed.XXXXXX")"
cp "$src" "$destination"
chmod 644 "$destination"
mv -f "$destination" "$STATUS_DIR/$name"
fi
done
cp "$OFFER_MANIFEST" "$STATE_DIR/$COMPONENT.installed.manifest"
cp "$OFFER_SIG" "$STATE_DIR/$COMPONENT.installed.sig"
}
# 第一次部署(deploy-node.sh 已经验过签并按 digest 固定)时还没有安装记录:清单的 digest 就是正在跑的这个镜像,就记下来,不当作「有更新」
seed_installed() {
[ ! -r "$INSTALLED" ] || return 0
running="$(sed -n 's/^IMAGE=.*@\(sha256:[0-9a-f]*\)$/\1/p' "$ENV_FILE" 2>/dev/null | head -n 1)"
if [ -n "$running" ] && [ "$running" = "$M_DIGEST" ]; then printf '%s\n%s\n' "$M_SEQ" "$M_DIGEST" > "$INSTALLED"; fi
}
current_digest() { [ -r "$INSTALLED" ] && sed -n 2p "$INSTALLED" || echo ""; }
installed_seq() { [ -r "$INSTALLED" ] && sed -n 1p "$INSTALLED" || echo 0; }
@@ -251,13 +281,19 @@ do_check() {
if ! verify_offer; then
# 没有新清单,或者清单已经安装过:不算错误,只是没有更新
case "$ERR" in
"no offer"|"stale manifest"*) write_status up_to_date null "" ;;
"no offer") write_status up_to_date null "" ;;
"stale manifest"*)
# 就是已经装的这一份(同一个 seq、同一个 digest):交给节点握手时带给 Backend
if [ "$M_SEQ" = "$(installed_seq)" ] && [ "$M_DIGEST" = "$(current_digest)" ]; then write_installed; fi
write_status up_to_date null "" ;;
*) write_status invalid null "$ERR"; echo "offer rejected: $ERR" >&2 ;;
esac
return 0
fi
adopt_next_key
seed_installed
if [ "$M_DIGEST" = "$(current_digest)" ]; then
write_installed
write_status up_to_date null ""
return 0
fi
@@ -303,6 +339,7 @@ do_apply() {
fi
if [ "$healthy" = true ] && service_ctl is-active --quiet "$UNIT" && [ "$before" = "$after" ]; then
printf '%s\n%s\n' "$M_SEQ" "$M_DIGEST" > "$INSTALLED"
write_installed
write_status up_to_date null ""
echo "updated to $M_TAG"
return 0