feat!: genesis-pinned operator set, server identity with an encrypted control link, key-less join codes; release key continuity
ci/woodpecker/tag/gitea-release Pipeline is pending
ci/woodpecker/tag/linux-release Pipeline failed
ci/woodpecker/tag/windows-release Pipeline was canceled

Join codes need no passkey touch and carry only public values (genesis
fingerprint, owner credentials, installer hashes). Nodes pin the genesis and
owners at enrollment and only follow passkey-signed operator set versions.
The panel's server key is listed in the set; pinned nodes require its
signature and run the control link under AES-256-GCM. Password sessions carry
no capabilities. The updater adopts a next_key from a signed manifest.

Bump version to 0.2.0. [CI SKIP]
This commit is contained in:
justme committed 2026-10-08 03:21:44 +08:00
1 parent 9bb6841dce
commit a2f88a344b
89 files changed
+2145 -1195

No files matched your search

+23 -1
View File
@@ -78,6 +78,7 @@ else
chmod 755 "$STATUS_DIR"
fi
INSTALLED="$STATE_DIR/$COMPONENT.installed" # 两行:seq、digest
ADOPTED="$STATE_DIR/$COMPONENT.key.pem" # 发布密钥换新之后采用的公钥;有它就只认它,配置里的旧公钥不再用
mkdir -p "$STATE_DIR"
# Serialize check/apply path events for this component.
exec 9>"$STATE_DIR/$COMPONENT.lock"
@@ -169,7 +170,18 @@ verify_offer() {
[ "$(wc -c < "$OFFER_MANIFEST")" -le 4096 ] || { ERR="manifest too large"; return 1; }
sigbin="$WORK/sig.bin"
base64 -d "$OFFER_SIG" > "$sigbin" 2>/dev/null || { ERR="bad signature encoding"; return 1; }
openssl dgst -sha256 -verify "$PUBKEY" -signature "$sigbin" "$OFFER_MANIFEST" >/dev/null 2>&1 || { ERR="signature check failed"; return 1; }
key="$PUBKEY"
[ -s "$ADOPTED" ] && key="$ADOPTED"
openssl dgst -sha256 -verify "$key" -signature "$sigbin" "$OFFER_MANIFEST" >/dev/null 2>&1 || { ERR="signature check failed"; return 1; }
# 密钥延续:这份清单由当前密钥签名,里面的 next_key(SPKI DER 的 base64)就是以后要认的密钥;写不成合法的公钥就整份拒绝
M_NEXT=""
next="$(field next_key)"
if [ -n "$next" ]; then
printf '%s' "$next" | base64 -d > "$WORK/next.der" 2>/dev/null \
&& openssl pkey -pubin -inform DER -in "$WORK/next.der" -outform PEM -out "$WORK/next.pem" >/dev/null 2>&1 \
&& [ -s "$WORK/next.pem" ] || { ERR="next_key is not a public key"; return 1; }
M_NEXT="$WORK/next.pem"
fi
[ "$(field component)" = "$COMPONENT" ] || { ERR="manifest is for another component"; return 1; }
[ "$(field image)" = "$IMAGE_REPO" ] || { ERR="manifest names another repository"; return 1; }
if [ -n "$CHANNEL" ]; then
@@ -183,6 +195,14 @@ verify_offer() {
[ "$M_SEQ" -gt "$(installed_seq)" ] || { ERR="stale manifest (seq $M_SEQ <= $(installed_seq))"; return 1; }
}
# 验过签的清单带了 next_key 就改认它(写临时文件再换名;已经是它就不动)
adopt_next_key() {
[ -n "$M_NEXT" ] || return 0
if [ -s "$ADOPTED" ] && cmp -s "$M_NEXT" "$ADOPTED"; then return 0; fi
cp "$M_NEXT" "$ADOPTED.new" && mv -f "$ADOPTED.new" "$ADOPTED"
echo "release key replaced by the one in the signed manifest"
}
available_json() { printf '{"version":"%s","tag":"%s","digest":"%s","seq":%s}' "$(printf '%s' "$M_VERSION" | tr -d '\042\134')" "$(printf '%s' "$M_TAG" | tr -d '\042\134')" "$M_DIGEST" "$M_SEQ"; }
do_check() {
@@ -195,6 +215,7 @@ do_check() {
esac
return 0
fi
adopt_next_key
if [ "$M_DIGEST" = "$(current_digest)" ]; then
write_status up_to_date null ""
return 0
@@ -215,6 +236,7 @@ do_apply() {
fi
fetch_offer
verify_offer || { write_status invalid null "$ERR"; echo "refusing to apply: $ERR" >&2; return 1; }
adopt_next_key
if [ "$COMPONENT" = backend ]; then
case "$requested_seq" in ''|*[!0-9]*) write_status failed "$(available_json)" "a confirmed release sequence is required"; return 1 ;; esac
[ "$requested_seq" = "$M_SEQ" ] || { write_status available "$(available_json)" "release changed; confirm again"; return 1; }