Files

133 lines
7.5 KiB
Bash
Raw Permalink 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.
#!/usr/bin/env bash
# 更新器从镜像仓库取签名清单的测试:用一个假的 podman(只实现 pull 和 image inspect)和真的 openssl,
# 不需要容器引擎、网络或 root。运行:bash packaging/update/tests/feed-test.sh
# 覆盖:正常取到、仓库里的清单被篡改、用别的密钥签的、旧的 seq、换钥(next_key)、仓库不可用时回退到信箱、FEED=off。
set -eu
HERE="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$HERE/../../.." && pwd)"
T="$(mktemp -d)"
trap 'rm -rf "$T"' EXIT
mkdir -p "$T/bin" "$T/feed" "$T/data/update" "$T/state" "$T/conf"
fail=0
# 假的 flock 和 podman:feed 目录下有 <引用>/labels 文件("manifest_b64|signature")就算拉得到
printf '#!/bin/sh\nexit 0\n' > "$T/bin/flock"
cat > "$T/bin/podman" <<'STUB'
#!/bin/sh
dir="$FAKE_FEED"
case "$1" in
pull) for last; do :; done; key="$(printf '%s' "$last" | tr '/:@' '___')"; [ -f "$dir/$key" ] ;;
image) for last; do :; done; key="$(printf '%s' "$last" | tr '/:@' '___')"; cat "$dir/$key" ;;
*) exit 1 ;;
esac
STUB
chmod +x "$T/bin/flock" "$T/bin/podman"
export PATH="$T/bin:$PATH" FAKE_FEED="$T/feed" MOSHIRO_UPDATE_CONF_DIR="$T/conf"
for k in a b c; do
openssl ecparam -name prime256v1 -genkey -noout -out "$T/$k.pem" 2>/dev/null
openssl ec -in "$T/$k.pem" -pubout -out "$T/$k.pub" 2>/dev/null
done
REPO=registry.example/moshiro-dev/moshiro-agent
REF_KEY="$(printf '%s' "$REPO-manifest:dev" | tr '/:@' '___')"
cat > "$T/conf/agent.conf" <<EOF
DATA_DIR=$T/data
IMAGE_REPO=$REPO
UNIT=x
PUBKEY=$T/a.pub
ENV_FILE=$T/env
STATE_DIR=$T/state
POLICY=manual
ENGINE=podman
EOF
DIGEST="sha256:$(printf 'a%.0s' $(seq 64))"
# publish <签名私钥> <seq> [next 公钥]:写出仓库里的清单标签,和 CI 的 release-manifest.sh + publish-manifest.sh 产出同样的格式
publish() {
local next=""
if [ -n "${3:-}" ]; then next=",\"next_key\":\"$(openssl pkey -pubin -in "$3" -outform DER | base64 -w0)\""; fi
printf '{"schema":1,"component":"agent","channel":"dev","image":"%s","digest":"%s","tag":"dev-%s","version":"0.2.0","commit":"x","built_at":"t","seq":%s%s}' "$REPO" "$DIGEST" "$2" "$2" "$next" > "$T/m.json"
openssl dgst -sha256 -sign "$1" -out "$T/s.bin" "$T/m.json"
printf '%s|%s' "$(base64 -w0 "$T/m.json")" "$(base64 -w0 "$T/s.bin")" > "$T/feed/$REF_KEY"
}
status() { sed -n 's/.*"state":"\([^"]*\)".*"error":"\([^"]*\)".*/\1|\2/p' "$T/data/update-status/status.json"; }
check() { sh "$ROOT/packaging/update/moshiro-update.sh" check agent >/dev/null 2>&1 || true; status; }
expect() { # 名称 期望的状态前缀 实际
if [[ "$3" == "$2"* ]]; then echo "ok $1 ($3)"; else echo "FAIL $1: expected $2*, got '$3'"; fail=1; fi
}
publish "$T/a.pem" 10
expect "valid manifest from the registry" "available|" "$(check)"
publish "$T/a.pem" 10; sed -i 's/^\(.\{40\}\)./\1X/' "$T/feed/$REF_KEY"
expect "tampered manifest is rejected, not ignored" "invalid|" "$(check)"
publish "$T/c.pem" 11
expect "manifest signed by another key" "invalid|signature check failed" "$(check)"
printf '%s' "$DIGEST" > "$T/state/agent.installed.tmp"; printf '99999\n%s\n' "$DIGEST" > "$T/state/agent.installed"
publish "$T/a.pem" 12
expect "stale seq is just up to date" "up_to_date|" "$(check)"
rm -f "$T/state/agent.installed"
publish "$T/a.pem" 20 "$T/b.pub"
expect "manifest carrying next_key" "available|" "$(check)"
publish "$T/a.pem" 21
expect "old key no longer accepted after rotation" "invalid|signature check failed" "$(check)"
publish "$T/b.pem" 22
expect "new key accepted" "available|" "$(check)"
rm -f "$T/feed/$REF_KEY"
expect "registry unavailable and empty mailbox" "up_to_date|" "$(check)"
# 信箱回退:仓库不可用,面板放进信箱的清单照样能用(用新钥签)
printf '{"schema":1,"component":"agent","channel":"dev","image":"%s","digest":"%s","tag":"dev-30","version":"0.2.0","commit":"x","built_at":"t","seq":30}' "$REPO" "$DIGEST" > "$T/data/update/offer.manifest"
openssl dgst -sha256 -sign "$T/b.pem" -out "$T/s.bin" "$T/data/update/offer.manifest"
base64 -w0 "$T/s.bin" > "$T/data/update/offer.sig"
expect "mailbox still works without the registry" "available|" "$(check)"
# FEED=off:仓库里有更新的清单也不看
publish "$T/b.pem" 40
echo "FEED=off" >> "$T/conf/agent.conf"
out="$(check)"
if grep -q '"seq":40' "$T/data/update-status/status.json"; then echo "FAIL FEED=off still read the registry"; fail=1; else echo "ok FEED=off ignores the registry ($out)"; fi
# --- deploy-node.sh 的首次安装:从仓库取清单、用随脚本带的发布公钥验签,再按清单里的 digest 固定镜像
mkdir -p "$T/pkg/update"
cp "$T/b.pub" "$T/pkg/update/release-key.pub.pem"
sed -n '/^pull_quiet() {/,/^if \[ -z "\$IMAGE" \]; then/p' "$ROOT/packaging/container/deploy-node.sh" | sed '$d' > "$T/resolve.sh"
resolve() { # 签名私钥 组件 镜像仓库
rm -f "$T/feed"/*
printf '{"schema":1,"component":"%s","channel":"dev","image":"%s","digest":"%s","tag":"dev-1","version":"0.2.0","commit":"x","built_at":"t","seq":1}' "$2" "$3" "$DIGEST" > "$T/m.json"
openssl dgst -sha256 -sign "$1" -out "$T/s.bin" "$T/m.json"
printf '%s|%s' "$(base64 -w0 "$T/m.json")" "$(base64 -w0 "$T/s.bin")" > "$T/feed/$(printf '%s' "$REPO-manifest:dev" | tr '/:@' '___')"
: > "$T/feed/$(printf '%s' "$REPO@$DIGEST" | tr '/:@' '___')"
( ENGINE=podman AUTHFILE="" REPO="$REPO" CHANNEL="" ROLE=agent PACKAGING="$T/pkg" IMAGE=""
. "$T/resolve.sh"
resolve_verified >/dev/null 2>&1 && printf '%s' "$IMAGE" || printf 'REFUSED' )
}
expect "first install: signed manifest pins the digest" "$REPO@$DIGEST" "$(resolve "$T/b.pem" agent "$REPO")"
expect "first install: wrong signing key is refused" "REFUSED" "$(resolve "$T/c.pem" agent "$REPO")"
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"