feat: signed manifests live in the image registry; updaters and first deploys fetch and verify them without the panel [CI]
This commit is contained in:
1 parent
4106f831dc
commit
117d34a2af
7 files changed
+267
-28
No files matched your search
@@ -83,14 +83,8 @@ for tag in latest "$dev_tag"; do
|
||||
echo "pushed development image $repository:$tag"
|
||||
done
|
||||
|
||||
# 发布清单:给镜像的 digest 签名(私钥只在 CI 密钥里),再登记到面板;面板把它原样转给在线的节点,节点宿主机上的更新器用本机的公钥验签后才会更新。
|
||||
# 发布清单:给镜像的 digest 签名(私钥只在 CI 密钥里),放进镜像仓库(<仓库>-manifest:dev),面板在线时再登记一份加速。
|
||||
# 没有配置签名密钥(例如 fork)就只构建镜像、不发清单。
|
||||
if [[ -n "${RELEASE_SIGNING_KEY:-}" ]]; then
|
||||
digest="$(cat "$work/$component.digest")"
|
||||
release_dir="$work/$component-release"
|
||||
bash "$root/ci/release-manifest.sh" "$component" "$repository" "$digest" "$dev_tag" dev "$release_dir"
|
||||
if [[ -n "${RELEASE_API_URL:-}" ]]; then
|
||||
python3 -c 'import json,sys; print(json.dumps({"manifest": open(sys.argv[1]).read(), "signature": open(sys.argv[2]).read()}))' "$release_dir/manifest.json" "$release_dir/manifest.sig" | curl --fail-with-body --silent --show-error --retry 3 -X POST "${RELEASE_API_URL%/}/api/release/offer" -H 'Content-Type: application/json' --data-binary @- >/dev/null
|
||||
echo "registered the signed $component release with the panel"
|
||||
fi
|
||||
bash "$root/ci/publish-release-offer.sh" "$component" "$repository" "$(cat "$work/$component.digest")" "$dev_tag" dev "$work/$component-release"
|
||||
fi
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
# 把签名清单放进镜像仓库:ci/publish-manifest.sh <镜像仓库> <通道> <清单目录>
|
||||
# 清单目录里是 release-manifest.sh 产出的 manifest.json 和 manifest.sig。这里把它们装进一个没有任何文件层的小镜像的标签里,
|
||||
# 推到 <镜像仓库>-manifest:<通道>(例如 registry/moshiro-dev/moshiro-backend-manifest:dev)。
|
||||
# 宿主机更新器和 deploy-node.sh 直接从仓库取这个镜像、用本机的发布公钥验签,所以更新不依赖面板在线,也不依赖 Release 页面;
|
||||
# 仓库只是存放处,被篡改只会让验签失败。需要登录过仓库(buildah login),或设置 ZOT_USERNAME / ZOT_SECRET。
|
||||
set -euo pipefail
|
||||
|
||||
repository="${1:?usage: publish-manifest.sh <repository> <channel> <manifest-dir>}"
|
||||
channel="${2:?channel}"
|
||||
dir="${3:?manifest dir}"
|
||||
[[ "$repository" =~ ^[a-z0-9./_:-]+$ && "$channel" =~ ^[a-z0-9._-]+$ ]] || { echo "unexpected characters in repository or channel" >&2; exit 2; }
|
||||
[[ -s "$dir/manifest.json" && -s "$dir/manifest.sig" ]] || { echo "$dir needs manifest.json and manifest.sig" >&2; exit 2; }
|
||||
|
||||
ref="${repository}-manifest:${channel}"
|
||||
local_name="localhost/moshiro-manifest-build:$$"
|
||||
container="$(buildah from scratch)"
|
||||
trap 'buildah rm "$container" >/dev/null 2>&1 || true; buildah rmi "$local_name" >/dev/null 2>&1 || true' EXIT
|
||||
manifest_b64="$(base64 < "$dir/manifest.json" | tr -d '\n')"
|
||||
signature="$(tr -d '\n' < "$dir/manifest.sig")"
|
||||
buildah config \
|
||||
--label "org.moshiro.manifest=$manifest_b64" \
|
||||
--label "org.moshiro.signature=$signature" \
|
||||
--label "org.opencontainers.image.title=MoShiro signed release manifest ($channel)" \
|
||||
"$container"
|
||||
buildah commit --format oci "$container" "$local_name" >/dev/null
|
||||
|
||||
creds=()
|
||||
if [[ -n "${ZOT_USERNAME:-}" && -n "${ZOT_SECRET:-}" ]]; then creds=(--creds "$ZOT_USERNAME:$ZOT_SECRET"); fi
|
||||
for attempt in 1 2 3; do
|
||||
if buildah push --tls-verify=true ${creds[@]+"${creds[@]}"} "$local_name" "docker://$ref" >/dev/null; then
|
||||
echo "published the signed manifest at $ref"
|
||||
exit 0
|
||||
fi
|
||||
if [[ "$attempt" -lt 3 ]]; then echo "manifest push attempt $attempt failed; retrying in 5 seconds" >&2; sleep 5; fi
|
||||
done
|
||||
echo "could not publish the manifest at $ref" >&2
|
||||
exit 1
|
||||
+11
-11
@@ -1,24 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# CI 发布 webhook:镜像已推送成功后,签名并登记清单(面板验 CI 签名,不需要令牌)。面板不会因此自动重启 Backend。
|
||||
# CI 发布:镜像已推送成功后,签名清单,并发布出去。ci/publish-release-offer.sh <组件> <镜像仓库> <digest> <tag> <通道> <输出目录>
|
||||
# 1. 清单放进镜像仓库(<镜像仓库>-manifest:<通道>):更新器和首次部署都从这里取,不依赖面板,必须成功;
|
||||
# 2. 面板在线时再登记一份(/api/release/offer,面板验 CI 签名,不需要令牌)让在线的节点更快收到:只是加速,失败只警告,不让构建失败。
|
||||
# 面板不会因此自动重启 Backend。
|
||||
set -euo pipefail
|
||||
component="${1:?component}"; repository="${2:?repository}"; digest="${3:?digest}"; tag="${4:?tag}"; channel="${5:?channel}"; outdir="${6:?outdir}"
|
||||
[[ -n "${RELEASE_SIGNING_KEY:-}" ]] || { echo "release signing is not configured; no update offer published"; exit 0; }
|
||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
bash "$root/ci/release-manifest.sh" "$component" "$repository" "$digest" "$tag" "$channel" "$outdir"
|
||||
bash "$root/ci/publish-manifest.sh" "$repository" "$channel" "$outdir"
|
||||
|
||||
if [[ -n "${RELEASE_API_URL:-}" ]]; then
|
||||
python3 -c 'import json,sys; print(json.dumps({"manifest": open(sys.argv[1]).read(), "signature": open(sys.argv[2]).read()}))' \
|
||||
"$outdir/manifest.json" "$outdir/manifest.sig" > "$outdir/payload.json"
|
||||
response="$outdir/response.json"
|
||||
status="$(curl --silent --show-error --retry 3 -o "$response" -w '%{http_code}' -X POST "${RELEASE_API_URL%/}/api/release/offer" \ -H 'Content-Type: application/json' --data-binary "@$outdir/payload.json")"
|
||||
if [[ "$status" != 2* ]]; then
|
||||
if [[ "$component" == backend && "$status" == 400 ]] && grep -Fq 'manifest needs a known component' "$response"; then
|
||||
echo "the panel does not support Backend update offers yet; bootstrap it once before future Backend offers"
|
||||
else
|
||||
echo "release offer registration failed (HTTP $status)" >&2
|
||||
cat "$response" >&2
|
||||
exit 1
|
||||
fi
|
||||
status="$(curl --silent --show-error --retry 3 --max-time 30 -o "$response" -w '%{http_code}' -X POST "${RELEASE_API_URL%/}/api/release/offer" \
|
||||
-H 'Content-Type: application/json' --data-binary "@$outdir/payload.json" 2>/dev/null || true)"
|
||||
if [[ "$status" == 2* ]]; then
|
||||
echo "also registered the signed $component release with the panel ($channel)"
|
||||
else
|
||||
echo "registered the signed $component release with the panel ($channel)"
|
||||
echo "warning: the panel did not take the $component offer (HTTP ${status:-none}); updaters still get it from the registry" >&2
|
||||
fi
|
||||
fi
|
||||
Reference in new issue
Block a user