R-812 option A: the Proxmox package lane (layer pve) + the /etc/pve write gate
The wrapper gains layer "pve" (slow lane): the host's Proxmox userspace packages only — origin "Proxmox Debian Repository", never a kernel / boot / firmware / microcode name (R14), no removal, no undo, a new package only from an allow-list; authority = a signed os_pve_step or the root-owned ring-0 mark. The night leg runs it in ring 0 after a healthy host step; ring 1 only by a signed job (PVEStepExecutor). While it runs, the agent's own /etc/pve writes (every non-GET API call, pct config verbs, pvesm, pveum, felhom-pbs-apply) wait on internal/pvegate. Health = the host rule + unchanged container ids + pveversion reads the installed pve-manager. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
+66
-16
@@ -21,6 +21,11 @@
|
||||
# or, for an unsigned ring-0 step, the root-owned TRUST_FILE saying `"ring0_slow_lane": true` (set by hand on the demo
|
||||
# boxes only). The agent's own config is NOT trusted for either: the agent can write it. A Docker step also needs
|
||||
# `live-restore` ON (R15) — without it every container restarts.
|
||||
# Agent v0.151.0 (R-812 option A, `09` §3 decision 163) adds the layer "pve": the HOST's Proxmox USERSPACE packages,
|
||||
# lane "slow" only, origin "Proxmox Debian Repository" only, never a kernel / boot / firmware / microcode name (R14 —
|
||||
# the kernel is R-836's lane), no removal, no undo, a NEW package only from PVE_NEW_ALLOW, an appliance only (R12), and
|
||||
# the same authority as the Docker step (R3: a signed `os_pve_step`, or the root-owned ring-0 mark). Select
|
||||
# "pending-pve" (ring 0): every installed Proxmox-origin package with a pending upgrade, minus HOST_SLOW_RE.
|
||||
#
|
||||
# Modes (plan field "mode"):
|
||||
# inventory `apt-get update`, then report what is installed (with origin), what is pending, and health.
|
||||
@@ -100,6 +105,12 @@ HOST_SERVICES = ["pveproxy", "pvedaemon", "pvestatd", "pve-cluster", "felhom-age
|
||||
DOCKER_NAMES = ("containerd.io", "docker-buildx-plugin", "docker-ce", "docker-ce-cli", "docker-ce-rootless-extras",
|
||||
"docker-compose-plugin")
|
||||
DOCKER_ORIGIN = "Docker CE"
|
||||
# The Proxmox package lane (R-812 option A): the origin apt prints for download.proxmox.com, and the ONLY new packages
|
||||
# a pve step may add (measured on demo-felhom 2026-10-07: a full upgrade adds proxmox-firewall-data and the kernel;
|
||||
# the kernel is refused by R14 whatever this list says).
|
||||
PVE_ORIGIN = "Proxmox Debian Repository"
|
||||
PVE_NEW_ALLOW = ("proxmox-firewall-data",)
|
||||
PVE_SIGNED_OP = "os_pve_step"
|
||||
# ROOT-OWNED trust anchors (the installer writes them; the demo boxes got them by hand, R-840). Never the agent's config.
|
||||
TRUST_FILE = "/etc/felhom/os-trust.json" # {"host_id": "...", "ring0_slow_lane": false}
|
||||
TRUST_SIGNERS = "/etc/felhom/operator-signers" # ssh allowed_signers: <key_id> namespaces="felhom-op-v1" <key>
|
||||
@@ -434,12 +445,14 @@ class Apply:
|
||||
raise Refused("R11", "bundle is a host-layer mode")
|
||||
return mode, "host", 0, "bundle"
|
||||
layer = plan.get("layer")
|
||||
if layer not in ("guest", "host", "docker"):
|
||||
raise Refused("R12", f"layer {layer!r} is not guest, host or docker")
|
||||
if layer not in ("guest", "host", "docker", "pve"):
|
||||
raise Refused("R12", f"layer {layer!r} is not guest, host, docker or pve")
|
||||
lane = plan.get("lane", "fast")
|
||||
if layer == "docker" and lane != "slow":
|
||||
raise Refused("R3", "the Docker engine is the slow lane (`11` §5.8); a fast-lane Docker plan is refused")
|
||||
if layer != "docker" and lane != "fast":
|
||||
if layer == "pve" and lane != "slow":
|
||||
raise Refused("R3", "the Proxmox packages are the slow lane (`11` §5.10); a fast-lane pve plan is refused")
|
||||
if layer not in ("docker", "pve") and lane != "fast":
|
||||
raise Refused("R3", f"the {layer} layer has no slow lane in this release (kernel, Proxmox: `11` §8 step 6)")
|
||||
if mode == "facts" and layer != "host":
|
||||
raise Refused("R11", "facts is a host-layer mode (it reads the host and the guest)")
|
||||
@@ -447,7 +460,7 @@ class Apply:
|
||||
raise Refused("R11", "live-restore-on is a guest-layer mode")
|
||||
if mode == "oom-check" and layer != "docker":
|
||||
raise Refused("R11", "oom-check is a docker-layer mode (it checks the guest's Docker engine)")
|
||||
if plan.get("undo") and layer != "docker":
|
||||
if plan.get("undo") and layer != "docker": # the pve layer has no undo in this release (R-812 option A)
|
||||
raise Refused("R5", "an undo (downgrade) exists only for the Docker layer, inside a signed job")
|
||||
vmid = plan.get("vmid")
|
||||
if not isinstance(vmid, int) or isinstance(vmid, bool) or vmid <= 0:
|
||||
@@ -458,17 +471,19 @@ class Apply:
|
||||
if plan.get("allow_new"):
|
||||
raise Refused("R6", "allow_new is a slow-lane field; the fast lane never adds a package")
|
||||
select = plan.get("select", "listed")
|
||||
if select not in ("listed", "pending-fast", "pending-docker"):
|
||||
if select not in ("listed", "pending-fast", "pending-docker", "pending-pve"):
|
||||
raise Refused("R11", f"unknown select {select!r}")
|
||||
if (select == "pending-docker") != (layer == "docker" and select != "listed"):
|
||||
if select == "pending-docker" or layer == "docker":
|
||||
raise Refused("R11", f"select {select!r} does not fit layer {layer!r}")
|
||||
if (select == "pending-pve") != (layer == "pve" and select != "listed"):
|
||||
raise Refused("R11", f"select {select!r} does not fit layer {layer!r}")
|
||||
pk = plan.get("packages", [])
|
||||
if not isinstance(pk, list):
|
||||
raise Refused("R11", "packages must be a list")
|
||||
if mode == "apply" and select == "listed" and not pk:
|
||||
raise Refused("R11", "packages must be a non-empty list in apply mode (select listed)")
|
||||
if select in ("pending-fast", "pending-docker") and pk:
|
||||
if select in ("pending-fast", "pending-docker", "pending-pve") and pk:
|
||||
raise Refused("R11", f"select {select} takes no package list")
|
||||
seen = set()
|
||||
for e in pk:
|
||||
@@ -488,6 +503,12 @@ class Apply:
|
||||
continue
|
||||
if n in DOCKER_NAMES:
|
||||
raise Refused("R2", f"{n} is a Docker package — the slow lane (`11` §5.8), never in a {layer} plan")
|
||||
if layer == "pve":
|
||||
if o != PVE_ORIGIN:
|
||||
raise Refused("R2", f"{n}: origin {o!r} is not {PVE_ORIGIN!r} (the pve layer)")
|
||||
if HOST_SLOW_RE.match(n):
|
||||
raise Refused("R14", f"{n} is a kernel / boot / firmware package — never the pve lane (R-836)")
|
||||
continue
|
||||
if o not in FAST_ORIGINS:
|
||||
raise Refused("R2", f"{n}: origin {o!r} is not Debian / Debian-Security (the fast lane, `11` C3)")
|
||||
if layer == "host" and HOST_SLOW_RE.match(n):
|
||||
@@ -630,12 +651,13 @@ class Apply:
|
||||
now = self.r.now()
|
||||
self.r.write_nonces({k: v for k, v in seen.items() if v > now})
|
||||
|
||||
def docker_authority(self, plan):
|
||||
"""R3 for the docker layer: returns (who, undo). A signed job binds the EXACT package list and the undo flag."""
|
||||
def docker_authority(self, plan, op_name=SIGNED_OP):
|
||||
"""R3 for the docker (and, op_name os_pve_step, the pve) layer: returns (who, undo). A signed job binds the EXACT
|
||||
package list and the undo flag."""
|
||||
trust = self.load_trust()
|
||||
signed = plan.get("signed")
|
||||
if signed:
|
||||
params = self.verify_signed(signed, trust)
|
||||
params = self.verify_signed(signed, trust, op_name=op_name)
|
||||
want = sorted(f"{e.get('name')}={e.get('version')}" for e in params.get("packages") or [])
|
||||
got = sorted(f"{e['name']}={e['version']}" for e in plan.get("packages", []))
|
||||
if not want or want != got:
|
||||
@@ -649,7 +671,7 @@ class Apply:
|
||||
raise Refused("R3", "an undo (downgrade) needs a signed operator job")
|
||||
if trust.get("ring0_slow_lane") is True:
|
||||
return "ring0", False
|
||||
raise Refused("R3", "a Docker step needs a signed operator job (ring 1) or this box's root-owned ring-0 mark")
|
||||
raise Refused("R3", f"a {self.layer} slow-lane step needs a signed operator job (ring 1) or this box's root-owned ring-0 mark")
|
||||
|
||||
def live_restore(self):
|
||||
rc, out, _ = self.g(["docker", "info", "--format", "{{.LiveRestoreEnabled}}"], timeout=60)
|
||||
@@ -795,8 +817,8 @@ class Apply:
|
||||
|
||||
# ---------- target helpers ----------
|
||||
def x(self, argv, timeout=1800):
|
||||
"""Run in the TARGET layer: the guest via pct exec, or the host directly."""
|
||||
if self.layer == "host":
|
||||
"""Run in the TARGET layer: the guest via pct exec, or the host directly (host and pve)."""
|
||||
if self.layer in ("host", "pve"):
|
||||
return self.r.host(argv, timeout)
|
||||
return self.r.guest(self.vmid, argv, timeout) # guest and docker both live in the customer guest
|
||||
|
||||
@@ -891,7 +913,7 @@ class Apply:
|
||||
def restart_needed(self):
|
||||
"""Processes still mapping deleted files, OUTSIDE containers (C11). Guest: outside docker; host: outside the
|
||||
LXC guests (the host's /proc shows guest processes too)."""
|
||||
skip = RESTART_SKIP_CGROUP["host" if self.layer == "host" else "guest"]
|
||||
skip = RESTART_SKIP_CGROUP["host" if self.layer in ("host", "pve") else "guest"]
|
||||
script = ('for p in /proc/[0-9]*; do grep -q "(deleted)" $p/maps 2>/dev/null || continue; '
|
||||
'grep -q "%s" $p/cgroup 2>/dev/null && continue; echo "${p#/proc/} $(cat $p/comm 2>/dev/null)"; done' % skip)
|
||||
rc, out, _ = self.x(["sh", "-c", script], timeout=120)
|
||||
@@ -965,7 +987,7 @@ class Apply:
|
||||
return Bundle(self).from_plan(plan)
|
||||
if self.mode == "agent_update":
|
||||
return self.agent_update(plan)
|
||||
if self.layer == "host":
|
||||
if self.layer in ("host", "pve"):
|
||||
self.check_appliance()
|
||||
self.check_guest(self.vmid)
|
||||
log = self.r.log
|
||||
@@ -976,6 +998,9 @@ class Apply:
|
||||
self.report["oom_check"] = self.oom_check()
|
||||
return 0
|
||||
self.who, self.allow_downgrade = ("fast", False)
|
||||
if self.layer == "pve" and self.mode == "apply":
|
||||
self.who, self.allow_downgrade = self.docker_authority(plan, op_name=PVE_SIGNED_OP)
|
||||
self.report["authority"] = self.who
|
||||
if self.layer == "docker" and self.mode == "apply":
|
||||
self.who, self.allow_downgrade = self.docker_authority(plan)
|
||||
if self.live_restore() != "true":
|
||||
@@ -988,7 +1013,7 @@ class Apply:
|
||||
log(f"os-apply: START release={plan.get('release_id')} layer={self.layer}" +
|
||||
(f":{self.vmid}" if self.layer != "host" else "") +
|
||||
f" lane={plan.get('lane', 'fast')} mode={self.mode} select={self.select} packages={len(plan.get('packages', []))}" +
|
||||
(f" authority={self.who}{' UNDO' if self.allow_downgrade else ''}" if self.layer == "docker" else ""))
|
||||
(f" authority={self.who}{' UNDO' if self.allow_downgrade else ''}" if self.layer in ("docker", "pve") else ""))
|
||||
if self.apt_lock_held():
|
||||
raise Refused("R9", f"another apt/dpkg holds the lock on the {self.layer}")
|
||||
self.report["health_before"] = self.health()
|
||||
@@ -1012,6 +1037,8 @@ class Apply:
|
||||
if self.layer == "docker":
|
||||
rc_v, out_v, _ = self.g(["docker", "version", "--format", "{{.Server.Version}}"], timeout=60)
|
||||
self.report["docker_engine"] = out_v.strip() if rc_v == 0 and out_v.strip() else "unknown"
|
||||
if self.layer == "pve":
|
||||
self.report["pve_manager"] = self.pve_manager()
|
||||
self.report["reboot_scanned"] = "reboot_needed" in self.report
|
||||
self.report["health_after"] = self.health()
|
||||
if self.layer == "docker" and self.mode == "apply":
|
||||
@@ -1161,10 +1188,26 @@ class Apply:
|
||||
return [{"name": p["name"], "version": p["to"], "origin": DOCKER_ORIGIN} for p in pend
|
||||
if p["from"] is not None and p["name"] in DOCKER_NAMES and self.origin_name(p["origin"]) == {DOCKER_ORIGIN}]
|
||||
|
||||
def pending_pve(self):
|
||||
"""Ring 0 (select pending-pve): the newest pending version of each INSTALLED Proxmox-origin package, never a
|
||||
kernel / boot / firmware / microcode name (HOST_SLOW_RE, R-836's lane), never another origin."""
|
||||
rc, pend, remv, _ = self.simulate(["dist-upgrade"])
|
||||
return [{"name": p["name"], "version": p["to"], "origin": PVE_ORIGIN} for p in pend
|
||||
if p["from"] is not None and not HOST_SLOW_RE.match(p["name"]) and p["name"] not in DOCKER_NAMES
|
||||
and self.origin_name(p["origin"]) == {PVE_ORIGIN}]
|
||||
|
||||
def pve_manager(self):
|
||||
"""pveversion's pve-manager version ("unknown" when it cannot be read)."""
|
||||
rc, out, _ = self.r.host(["pveversion"], 60)
|
||||
m = re.match(r"^pve-manager/([^/\s]+)", out.strip()) if rc == 0 else None
|
||||
return m.group(1) if m else "unknown"
|
||||
|
||||
def origin_ok(self, origin):
|
||||
o = self.origin_name(origin)
|
||||
if self.layer == "docker":
|
||||
return o == {DOCKER_ORIGIN}
|
||||
if self.layer == "pve":
|
||||
return o == {PVE_ORIGIN}
|
||||
return bool(o & set(FAST_ORIGINS))
|
||||
|
||||
def apply(self, plan):
|
||||
@@ -1173,6 +1216,8 @@ class Apply:
|
||||
packages = plan["packages"]
|
||||
elif self.select == "pending-docker":
|
||||
packages = self.pending_docker()
|
||||
elif self.select == "pending-pve":
|
||||
packages = self.pending_pve()
|
||||
else:
|
||||
packages = self.pending_fast()
|
||||
cmp_op = "ne" if self.allow_downgrade else "gt"
|
||||
@@ -1221,6 +1266,11 @@ class Apply:
|
||||
raise Refused("R4", f"the plan would remove {', '.join(remv[:5])}")
|
||||
want = dict(upgrade)
|
||||
for p in sim:
|
||||
if p["from"] is None and self.layer == "pve" and p["name"] in PVE_NEW_ALLOW and self.origin_ok(p["origin"]) \
|
||||
and not HOST_SLOW_RE.match(p["name"]):
|
||||
self.r.log(f"os-apply: NEW {p['name']}={p['to']} (on the pve lane's allow-list)")
|
||||
self.report.setdefault("added", []).append({"name": p["name"], "version": p["to"]})
|
||||
continue
|
||||
if p["from"] is None:
|
||||
raise Refused("R6", f"the plan would add a package that is not installed: {p['name']}")
|
||||
if p["name"] not in want:
|
||||
@@ -1231,7 +1281,7 @@ class Apply:
|
||||
raise Refused("R5", f"{p['name']} would be downgraded {p['from']} -> {p['to']}")
|
||||
if not self.origin_ok(p["origin"]):
|
||||
raise Refused("R2", f"{p['name']} would come from {p['origin']}, not the {self.layer} layer's origin")
|
||||
if self.layer == "host" and HOST_SLOW_RE.match(p["name"]):
|
||||
if self.layer in ("host", "pve") and HOST_SLOW_RE.match(p["name"]):
|
||||
raise Refused("R14", f"{p['name']} is a kernel / boot / firmware package — the host's slow lane")
|
||||
need = self.download_bytes(args)
|
||||
free = self.free_bytes()
|
||||
|
||||
@@ -232,6 +232,8 @@ class Fake:
|
||||
return (0, self.daemon_json, "") if self.daemon_json is not None else (1, "", "No such file")
|
||||
if cmd == "cat" and a[1] == "/etc/debian_version":
|
||||
return 0, "13.7\n", ""
|
||||
if cmd == "pveversion":
|
||||
return 0, f"pve-manager/{self.installed.get('pve-manager', '9.2.2')}/abcdef (running kernel: 7.0.14-20-pve)\n", ""
|
||||
if cmd == "uname":
|
||||
return 0, "7.0.14-20-pve\n", ""
|
||||
if cmd == "apt-mark":
|
||||
@@ -284,7 +286,7 @@ class Fake:
|
||||
n, v = x.split("=", 1)
|
||||
if v not in self.avail(n):
|
||||
return 100, "", f"E: Version '{v}' for '{n}' was not found"
|
||||
origin = "Docker CE:trixie" if n in osapply.DOCKER_NAMES else SEC if n == "openssl" else DEB
|
||||
origin = getattr(self, "origins", {}).get(n) or ("Docker CE:trixie" if n in osapply.DOCKER_NAMES else SEC if n == "openssl" else DEB)
|
||||
out += f"Inst {n} [{self.installed[n]}] ({v} {origin} [amd64])\n"
|
||||
out += "".join(l + "\n" for l in self.extra_sim)
|
||||
return 0, out, ""
|
||||
@@ -1324,5 +1326,155 @@ class OOMCheck(unittest.TestCase):
|
||||
self.assertEqual((rc, rep["refused"]["code"]), (2, "R11"), rep)
|
||||
|
||||
|
||||
PVE = "Proxmox Debian Repository:stable"
|
||||
PVE_SET = [{"name": "pve-manager", "version": "9.2.21", "origin": "Proxmox Debian Repository"},
|
||||
{"name": "libpve-common-perl", "version": "9.1.9", "origin": "Proxmox Debian Repository"}]
|
||||
|
||||
|
||||
def pve_fake(signed=None, ring0=False):
|
||||
f = Fake()
|
||||
f.installed.update({"pve-manager": "9.2.2", "libpve-common-perl": "9.1.1", "proxmox-kernel-helper": "9.0.4"})
|
||||
f.live["pve-manager"] = {"9.2.21", "9.2.2"}
|
||||
f.live["libpve-common-perl"] = {"9.1.9", "9.1.1"}
|
||||
f.origins = {"pve-manager": PVE, "libpve-common-perl": PVE, "proxmox-kernel-helper": PVE, "shim-signed": PVE,
|
||||
"proxmox-firewall-data": PVE}
|
||||
f.plan = {"release_id": "os-pve-t1", "layer": "pve", "lane": "slow", "vmid": 9201, "mode": "apply",
|
||||
"packages": [dict(p) for p in PVE_SET]}
|
||||
if ring0:
|
||||
f.files[osapply.TRUST_FILE] = json.dumps({"host_id": "demo-hp-bb76ea", "ring0_slow_lane": True})
|
||||
if signed is not None:
|
||||
f.plan["signed"] = signed
|
||||
return f
|
||||
|
||||
|
||||
class PVELane(unittest.TestCase):
|
||||
"""R-812 option A (`09` §3 decision 163): the host's Proxmox USERSPACE packages, slow lane, no kernel / boot /
|
||||
firmware / microcode (R14), no removal, a new package only from PVE_NEW_ALLOW. Red-proof: audits/day-2026-10-07/B/."""
|
||||
|
||||
def refused(self, f, code):
|
||||
rc, rep = run(f)
|
||||
self.assertEqual(rc, 2, rep)
|
||||
self.assertEqual(rep["refused"]["code"], code, rep)
|
||||
self.assertEqual(f.installed["pve-manager"], "9.2.2", "nothing may be installed on a refusal")
|
||||
return rep
|
||||
|
||||
# THE RED TEST (design-R-812 §5): before the pve layer existed this plan was refused R12.
|
||||
def test_pve_manager_plan_is_installed_on_the_host(self):
|
||||
f = pve_fake(signed=signed_job(packages=PVE_SET, op="os_pve_step"))
|
||||
rc, rep = run(f)
|
||||
self.assertEqual(rc, 0, rep)
|
||||
self.assertEqual(f.installed["pve-manager"], "9.2.21")
|
||||
self.assertEqual(rep["authority"], "signed")
|
||||
self.assertEqual(rep["pve_manager"], "9.2.21", "pveversion after the step is reported")
|
||||
inst = [c for c in f.calls if c[0] == "host" and "install" in c[1] and "-s" not in c[1] and "-f" not in c[1]
|
||||
and "--print-uris" not in c[1]]
|
||||
self.assertTrue(inst, "the pve layer installs on the HOST")
|
||||
self.assertFalse([c for c in f.calls if c[0] == "guest" and "install" in c[2]], "nothing installed in the guest")
|
||||
self.assertEqual(sorted(rep["health_after"]["host_services"]), sorted(osapply.HOST_SERVICES))
|
||||
|
||||
def test_kernel_in_a_pve_plan_is_refused(self):
|
||||
f = pve_fake(ring0=True)
|
||||
f.plan["packages"].append({"name": "proxmox-kernel-7.0", "version": "7.0.14-20", "origin": "Proxmox Debian Repository"})
|
||||
self.refused(f, "R14")
|
||||
|
||||
def test_shim_in_a_pve_plan_is_refused(self):
|
||||
f = pve_fake(ring0=True)
|
||||
f.plan["packages"].append({"name": "shim-signed", "version": "1.47+pmx1", "origin": "Proxmox Debian Repository"})
|
||||
self.refused(f, "R14")
|
||||
|
||||
def test_kernel_pulled_in_by_the_simulation_is_refused(self):
|
||||
f = pve_fake(ring0=True)
|
||||
f.installed["proxmox-kernel-helper"] = "9.0.4"
|
||||
f.extra_sim = ["Inst proxmox-kernel-helper [9.0.4] (9.0.6 Proxmox Debian Repository:stable [all])"]
|
||||
self.refused(f, "R6") # not in the plan — refused before the name check; R14 below when it IS in the plan
|
||||
g = pve_fake(ring0=True)
|
||||
g.live["proxmox-kernel-helper"] = {"9.0.6"}
|
||||
g.plan["packages"] = [dict(PVE_SET[0])]
|
||||
g.extra_sim = ["Inst proxmox-kernel-helper [9.0.4] (9.0.6 Proxmox Debian Repository:stable [all])"]
|
||||
# a kernel-helper the plan did not name is R6; the R14 name check covers a listed one (test above)
|
||||
self.refused(g, "R6")
|
||||
|
||||
def test_debian_package_in_a_pve_plan_is_refused(self):
|
||||
f = pve_fake(ring0=True)
|
||||
f.plan["packages"].append({"name": "libc6", "version": "2.41-12+deb13u4", "origin": "Debian"})
|
||||
self.refused(f, "R2")
|
||||
|
||||
def test_debian_origin_in_the_pve_simulation_is_refused(self):
|
||||
f = pve_fake(ring0=True)
|
||||
f.origins["libpve-common-perl"] = DEB
|
||||
self.refused(f, "R2")
|
||||
|
||||
def test_docker_package_in_a_pve_plan_is_refused(self):
|
||||
f = pve_fake(ring0=True)
|
||||
f.plan["packages"].append({"name": "docker-ce", "version": "5:29.8.2-1~debian.13~trixie", "origin": "Proxmox Debian Repository"})
|
||||
self.refused(f, "R2")
|
||||
|
||||
def test_pve_in_the_fast_lane_is_refused(self):
|
||||
f = pve_fake(ring0=True)
|
||||
f.plan["lane"] = "fast"
|
||||
self.refused(f, "R3")
|
||||
|
||||
def test_no_authority_is_refused(self):
|
||||
self.refused(pve_fake(), "R3")
|
||||
|
||||
def test_docker_signed_op_does_not_authorize_a_pve_step(self):
|
||||
self.refused(pve_fake(signed=signed_job(packages=PVE_SET, op="os_docker_step")), "R3")
|
||||
|
||||
def test_pve_on_a_byo_box_is_refused(self):
|
||||
f = pve_fake(ring0=True)
|
||||
f.files[osapply.INSTALL_STATE] = json.dumps({"mode": "byo"})
|
||||
self.refused(f, "R12")
|
||||
|
||||
def test_unlisted_new_package_is_refused(self):
|
||||
f = pve_fake(ring0=True)
|
||||
f.extra_sim = ["Inst proxmox-new-thing (1.0 Proxmox Debian Repository:stable [all])"]
|
||||
self.refused(f, "R6")
|
||||
|
||||
def test_allow_listed_new_package_is_accepted(self):
|
||||
f = pve_fake(ring0=True)
|
||||
f.extra_sim = ["Inst proxmox-firewall-data (0.1 Proxmox Debian Repository:stable [all])"]
|
||||
rc, rep = run(f)
|
||||
self.assertEqual(rc, 0, rep)
|
||||
self.assertEqual(f.installed["pve-manager"], "9.2.21")
|
||||
|
||||
def test_allow_new_is_never_an_open_door_in_the_fast_lane(self):
|
||||
f = Fake()
|
||||
f.plan["layer"] = "host"
|
||||
f.extra_sim = ["Inst proxmox-firewall-data (0.1 Proxmox Debian Repository:stable [all])"]
|
||||
rc, rep = run(f)
|
||||
self.assertEqual((rc, rep["refused"]["code"]), (2, "R6"), rep)
|
||||
|
||||
def test_undo_is_refused(self):
|
||||
f = pve_fake(signed=signed_job(packages=PVE_SET, op="os_pve_step"))
|
||||
f.plan["undo"] = True
|
||||
self.refused(f, "R5")
|
||||
|
||||
def test_ring0_pending_pve_takes_only_installed_proxmox_userspace(self):
|
||||
f = pve_fake(ring0=True)
|
||||
f.plan["select"], f.plan["packages"] = "pending-pve", []
|
||||
f.installed.update({"linux-image-amd64": "6.12.1", "tailscale": "1.102.2"})
|
||||
f.pending_sim = [
|
||||
"Inst pve-manager [9.2.2] (9.2.21 Proxmox Debian Repository:stable [amd64])",
|
||||
"Inst libpve-common-perl [9.1.1] (9.1.9 Proxmox Debian Repository:stable [all])",
|
||||
"Inst proxmox-kernel-helper [9.0.4] (9.0.6 Proxmox Debian Repository:stable [all])",
|
||||
"Inst proxmox-kernel-7.0.14-20-pve-signed (7.0.14-20 Proxmox Debian Repository:stable [amd64])",
|
||||
"Inst proxmox-firewall-data (0.1 Proxmox Debian Repository:stable [all])",
|
||||
"Inst libc6 [2.41-12+deb13u3] (2.41-12+deb13u4 Debian:13.7/stable [amd64])",
|
||||
"Inst tailscale [1.102.2] (1.102.5 Tailscale:pkgs.tailscale.com [amd64])",
|
||||
]
|
||||
rc, rep = run(f)
|
||||
self.assertEqual(rc, 0, rep)
|
||||
self.assertEqual(rep["authority"], "ring0")
|
||||
self.assertEqual(sorted(u["name"] for u in rep["upgraded"]), ["libpve-common-perl", "pve-manager"],
|
||||
"pending-pve: installed, Proxmox-origin, never kernel/boot/firmware, never Debian or other origins")
|
||||
self.assertEqual(f.installed["libc6"], "2.41-12+deb13u3")
|
||||
|
||||
def test_select_pending_pve_needs_the_pve_layer(self):
|
||||
f = Fake()
|
||||
f.plan["layer"], f.plan["select"], f.plan["packages"] = "host", "pending-pve", []
|
||||
rc, rep = run(f)
|
||||
self.assertEqual((rc, rep["refused"]["code"]), (2, "R11"), rep)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user