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
ci/woodpecker/push/main-amd64-build Pipeline failed
This commit is contained in:
1 parent
117d34a2af
commit
4841f7b15b
40 files changed
+1011
-21
No files matched your search
@@ -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
|
||||
|
||||
@@ -111,4 +111,22 @@ expect "first install: wrong signing key is refused" "REFUSED" "$(resolve "$T/c.
|
||||
expect "first install: manifest for another component is refused" "REFUSED" "$(resolve "$T/b.pem" probe "$REPO")"
|
||||
expect "first install: manifest naming another repository is refused" "REFUSED" "$(resolve "$T/b.pem" agent "registry.evil/moshiro-dev/moshiro-agent")"
|
||||
|
||||
# --- 第一次部署:正在跑的镜像就是清单里的这个 digest → 记成已安装(不当作有更新),并把验过的清单交给节点握手时带上
|
||||
sed -i '/^FEED=off/d' "$T/conf/agent.conf"
|
||||
rm -f "$T/state/agent.installed" "$T/state/agent.installed.manifest" "$T/state/agent.installed.sig" "$T/data/update-status/installed."* "$T/data/update/offer."*
|
||||
printf 'IMAGE=%s@%s\n' "$REPO" "$DIGEST" > "$T/env"
|
||||
publish "$T/b.pem" 50
|
||||
expect "first deploy: the running image is already up to date" "up_to_date|" "$(check)"
|
||||
if [ "$(sed -n 2p "$T/state/agent.installed" 2>/dev/null)" = "$DIGEST" ]; then echo "ok first deploy: install record seeded from the running image"; else echo "FAIL first deploy: no install record"; fail=1; fi
|
||||
label="$(cat "$T/feed/$REF_KEY")"
|
||||
if [ "$(cat "$T/data/update-status/installed.manifest" 2>/dev/null | base64 -w0)" = "${label%%|*}" ] && [ "$(cat "$T/data/update-status/installed.sig" 2>/dev/null)" = "${label#*|}" ]; then
|
||||
echo "ok the verified manifest is left for the node to present at its handshake"
|
||||
else
|
||||
echo "FAIL installed.manifest / installed.sig missing or different"; fail=1
|
||||
fi
|
||||
# 老版本的更新器没写过:同一份(seq 相同)下次检查补上
|
||||
rm -f "$T/data/update-status/installed."* "$T/state/agent.installed.manifest" "$T/state/agent.installed.sig"
|
||||
expect "already installed: still up to date" "up_to_date|" "$(check)"
|
||||
if [ -s "$T/data/update-status/installed.manifest" ] && [ -s "$T/data/update-status/installed.sig" ]; then echo "ok a missing copy is written again for the node"; else echo "FAIL the copy was not written again"; fail=1; fi
|
||||
|
||||
exit "$fail"
|
||||
Reference in new issue
Block a user