mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-26 13:39:47 +00:00
Integrate reviewed confined PVE helper guidance
Change-source: pulse-maintainer
This commit is contained in:
+41
-22
@@ -101,7 +101,7 @@ manual probes during a freeze or thaw.
|
||||
|---|---|---|
|
||||
| PVE/PBS/PMG inventory, node status, VM/container status, storage usage, and normal Proxmox API metrics | Add the Proxmox connection with a read-only or narrowly scoped API token | No |
|
||||
| VM guest disk and memory details through QEMU Guest Agent | Use Proxmox API permissions such as `VM.GuestAgent.Audit` and `VM.GuestAgent.FileRead` where supported | No host agent for the Proxmox node |
|
||||
| All mounted LXC filesystem capacities and usage | Install the Unified Agent on the owning PVE node; the safe profile uses the typed helper's bounded fixed-shape `pct` reads | Yes, but the safe profile confines it to the root helper rather than a root collector |
|
||||
| Configured LXC filesystem capacities and usage | Install the Unified Agent on the owning PVE node; the safe profile uses [socket-free discovery and bounded mount probes](#proxmox-lxc-filesystem-readings-with-the-typed-helper) | Yes, but the safe profile confines it to the root helper rather than a root collector |
|
||||
| Docker/Podman containers inside a VM or LXC through guest-local reporting | Install the agent inside that VM/LXC with Docker/Podman monitoring enabled, or use another explicit guest access/reporting path | Requires runtime-socket access: root-equivalent for a rootful daemon, or a collector-owned rootless socket |
|
||||
| Docker containers inside an LXC from a Proxmox host agent | Turn on **Discover Docker in LXC guests** in Settings → System → General (admin only), or start Pulse with `PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY=true` to lock it on; optionally limit guests with `PULSE_PROXMOX_GUEST_DOCKER_INVENTORY_VMIDS=101,102` | Requires a root/equivalent Pulse agent on the Proxmox node and explicit server opt-in |
|
||||
| Host SMART, temperatures, local ZFS/Ceph/mdadm detail, arbitrary mount reads, and full host telemetry | Install the agent on that host | Varies: the safe typed helper covers SMART; capabilities outside the matrix still require the explicit root/full-telemetry profile |
|
||||
@@ -114,10 +114,10 @@ LXCs by default. Its automatic LXC filesystem collector is a node-local
|
||||
capacity query only. On cgroup v2 PVE hosts it finds running guests from the
|
||||
node's container configs and cgroup state, reads each running guest's init
|
||||
process ID and status from cgroup and `/proc`, and measures mount capacity with
|
||||
statfs through `/proc/<pid>/root`. When that discovery cannot establish a guest's state (for
|
||||
example on cgroup v1 hosts) it falls back to `pct list`, with `lxc-info` to
|
||||
find each guest's init process, and it runs `pct df <vmid>` for a guest only
|
||||
when the statfs probe is unavailable or fails. It reports
|
||||
statfs through `/proc/<pid>/root`. Root or explicitly `--grant-pct` collectors can fall back to
|
||||
`pct list` and `pct df <vmid>` (with `lxc-info` where permitted) when discovery or a mount probe fails.
|
||||
That command path is not usable inside the isolated typed helper; see
|
||||
[helper-backed LXC readings](#proxmox-lxc-filesystem-readings-with-the-typed-helper). It reports
|
||||
mount keys, volume labels, mount paths, and capacity/usage numbers. It does not
|
||||
run a command inside the guest or read guest file contents, environment, or
|
||||
container-runtime metadata. It skips guests reported stopped and bounds command
|
||||
@@ -198,22 +198,6 @@ block devices. If the helper is missing, incompatible, or rejects a request,
|
||||
only the affected telemetry disappears; the collector does not fall back to
|
||||
sudo, root, or a broader local command path.
|
||||
|
||||
Proxmox's `pct list` uses pmxcfs abstract Unix IPC, which is not reachable from
|
||||
the helper's private network namespace. On affected PVE hosts this leaves
|
||||
helper-backed LXC filesystem telemetry unavailable, while independent SMART
|
||||
and API monitoring can continue. Prefer the existing Proxmox API connection
|
||||
for the readings it provides; do not add a unit override or a root/sudo
|
||||
fallback. A successful SMART reading does not establish LXC helper health.
|
||||
|
||||
When an inventory succeeds only partly, operation v2 preserves the collected
|
||||
rows and explicitly names omitted container IDs. The helper module stays
|
||||
degraded, and omitted containers cannot refresh old filesystem readings.
|
||||
Operation v1 remains complete-only; a newer collector uses it only when an
|
||||
older helper explicitly rejects v2. An unavailable inventory is not an empty
|
||||
healthy snapshot or zero disk usage. An installer-managed helper upgrade
|
||||
restarts the verified helper after checking the effective isolated unit and
|
||||
socket identity; a collector-only automatic update cannot replace its helper.
|
||||
|
||||
The installer treats the effective systemd configuration as part of the safe
|
||||
profile boundary. The collector, helper service, helper socket, and an
|
||||
installed action runner must load from the installer-owned `FragmentPath` with
|
||||
@@ -291,6 +275,41 @@ packet never reaches Docker. The post-run attester re-verifies those same
|
||||
snapshot paths against the receipt. No qualifying RC containing this boundary
|
||||
has been published yet.
|
||||
|
||||
### Proxmox LXC filesystem readings with the typed helper
|
||||
|
||||
The current helper can collect LXC filesystem readings **without `pct` or
|
||||
`lxc-info` requests**. It reads the owning node's container configuration,
|
||||
finds running guests and their init processes through cgroup v2 and `/proc`,
|
||||
and measures configured mounts with the bounded filesystem prober. It does not
|
||||
execute a command inside a guest, read guest file contents or accept a
|
||||
caller-selected VMID or path.
|
||||
|
||||
Proxmox's `pct` and `lxc-info` commands use host-network-namespace Unix IPC.
|
||||
Their fallback cannot recover a failed discovery or mount probe inside
|
||||
`PrivateNetwork=true`. On a cgroup v1 host, or when a running guest's state
|
||||
cannot be established, the helper can therefore report degraded or unavailable
|
||||
inventory rather than a complete list. Independent SMART and API readings can
|
||||
still work; a successful SMART reading does not establish LXC helper health.
|
||||
|
||||
Use the existing reading's status, age and collection path before treating
|
||||
missing filesystems as missing guests. Keep the Pulse server, collector and
|
||||
helper versions separate; a newer guide or collector alone does not establish
|
||||
that the installed helper has this path. Older helper versions relying on
|
||||
`pct` can fail even when SMART works. Prefer the existing Proxmox API connection
|
||||
for the readings it already provides, without claiming it covers every mount.
|
||||
Do not remove `PrivateNetwork`, add a unit override, enable a root/sudo
|
||||
fallback, reinstall or restart anything merely to diagnose a missing reading.
|
||||
Keep credentials and full service/configuration records private.
|
||||
|
||||
When an inventory succeeds only partly, operation v2 preserves the collected
|
||||
rows and explicitly names omitted container IDs. The helper module stays
|
||||
degraded, and omitted containers cannot refresh old filesystem readings.
|
||||
Operation v1 remains complete-only; a newer collector uses it only when an
|
||||
older helper explicitly rejects v2. An unavailable inventory is not an empty
|
||||
healthy snapshot or zero disk usage. An installer-managed helper upgrade
|
||||
restarts the verified helper after checking the effective isolated unit and
|
||||
socket identity; a collector-only automatic update cannot replace its helper.
|
||||
|
||||
### Safe-profile support and qualification matrix
|
||||
|
||||
`Qualified` below means the named evidence exercised the behavior on a real
|
||||
@@ -305,7 +324,7 @@ or generic command path.
|
||||
| Proxmox VE/PBS/PMG inventory, status, storage, and ordinary metrics | API-only connection with a narrowly scoped token; no host agent | No collector, helper, or runner authority is required for this data | Supported independently of the safe host-agent profile; it does not prove host-local SMART, LXC filesystem, or action parity | `agent-lifecycle`: keep API permissions and returned telemetry covered by provider tests |
|
||||
| Standard Linux systemd host telemetry and collector update | Unprivileged `pulse-agent` plus the root-owned typed helper | Core `/proc`, filesystem, network, RAID, and hwmon telemetry stays in the collector; signed updates cross the fixed helper protocol and commit only after an authoritative report | **Qualified on disposable Ubuntu 24.04.4 arm64 at committed main `22fd662fb794f63efb9d3ca2158de73c4e07e1b8`** across the schema-v6 twenty-scenario run. It covers install, migration, explicit/automatic rollback, helper protocol and resource boundaries, effective-unit override rejection, reporting continuity, authoritative executable-digest commit, watchdog rollback, helper-restart recovery, and last-known-good restoration. It remains opt-in pending exact-RC reproduction, representative provider/appliance parity, and external review | `deployment-installability` and `security-privacy`: reproduce the same helper transaction from trusted release artifacts on representative hosts and accept the external boundary review |
|
||||
| Linux SMART telemetry | `smart.snapshot` through the no-network helper; no caller-selected device or arguments | Implemented, unqualified on representative physical disks. Helper failure omits/degrades SMART only; the collector does not retry as root | Does not yet justify SMART parity or a default change | `agent-lifecycle`: record live SATA, SAS/controller, USB bridge, and NVMe evidence, including standby, permission failure, timeout, and partial-data cases |
|
||||
| Proxmox node-local LXC filesystem telemetry | `proxmox.lxc_filesystems` through the no-network helper. Configs are read through the pmxcfs mount, running guests and their init processes come from cgroup v2 and `/proc`, and usage comes from the shared filesystem prober, which pins the guest's init, re-checks its identity, and resolves each mount inside the guest root (openat2, or a symlink-refusing component walk where the helper's seccomp filter blocks openat2); the caller selects no VMID, path, or command. `pct` and `lxc-info` cannot work inside the helper, because pmxcfs and the LXC monitor listen on abstract Unix sockets that belong to the host network namespace (#2511); they remain the fallback whenever discovery cannot establish a guest's state, on cgroup v1 hosts, for root and `--grant-pct` installs, and per container when the prober read fails | Implemented. Exercised on a disposable PVE 9.2.18 node under the helper unit's sandbox properties, with running unprivileged (rootfs plus a mount point) and privileged guests and a stopped one; readings matched `pct df`. This is one PVE version, with no timeout, output-bound, helper-loss, or live nested-namespace evidence. Helper failure omits/degrades this snapshot only | Does not yet justify Proxmox host-agent parity or a default change | `agent-lifecycle`: record timeout, output-bound, and helper-loss behavior across supported PVE versions |
|
||||
| Proxmox node-local LXC filesystem telemetry | `proxmox.lxc_filesystems` through the no-network helper's [socket-free discovery and bounded mount probes](#proxmox-lxc-filesystem-readings-with-the-typed-helper). Failed discovery or probes remain degraded/unavailable; command fallback works only outside the helper, in a separately chosen root or `--grant-pct` profile | Implemented. Exercised on a disposable PVE 9.2.18 node under the helper unit's sandbox properties, with running unprivileged (rootfs plus a mount point) and privileged guests and a stopped one; readings matched `pct df`. This is one PVE version, with no timeout, output-bound, helper-loss, or live nested-namespace evidence. Helper failure omits/degrades this snapshot only | Does not yet justify Proxmox host-agent parity or a default change | `agent-lifecycle`: record timeout, output-bound, and helper-loss behavior across supported PVE versions |
|
||||
| Rootful Docker or Podman inventory | No direct collector access to a root-equivalent daemon socket | Implemented as a typed-helper summary-only fallback. Migration preserves container ID/name/image/state/status/creation inventory and marks the report `typed-helper-summary`; stats, secondary inventories, update checks, and actions remain unavailable | The standalone rootless packet exercises live same-family rootful Docker and Podman helper fallback plus summary-inventory semantic parity during socket loss. It does not qualify the complete standalone rootful fresh-install, migration, restart, helper-loss/recovery, and bounds matrix. Full rootful parity remains an explicit default blocker; the legacy/root profile is not safe-profile evidence | `agent-lifecycle`: record the remaining standalone lifecycle matrix on representative rootful Docker and Podman and decide explicitly whether reduced telemetry is sufficient |
|
||||
| Collector-owned rootless Docker or Podman | Direct access only to one usable runtime socket owned by the `pulse-agent` UID whose daemon also attests rootless mode | Qualified locally for the exact source-bound monitoring path. Discovery is deferred until the collector account exists and accepts exactly one readable/writable, non-symlink socket owned by that UID across Docker and Podman. Cross-user, root-owned, remote, unreadable, non-rootless-daemon, or ambiguous endpoints are rejected. An installer-owned exact pin survives daemon loss and update; the running collector can move between direct rootless monitoring and rootful typed-helper summary without regaining action authority | **Qualified on distinct disposable Ubuntu 24.04/systemd 255 arm64 hosts at commit `60041ad9e60c282c892f944e04f777b874991a5d`.** All eleven canonical scenarios passed for Docker and all eleven for Podman. Receipt SHA-256: `7a116d63ab0cd1560482165055f0a8c9158ce0a8333a27bd9e8256582a52dfb5`; local attestation SHA-256: `566279ecd7d7dfa89c92f24243e9fcd5ae3e295d4284bd559d4be42f1e83b3b5`. This is local opt-in artifact-bound self-attestation, not published-RC provenance, action/update qualification, external review, or authorization for a default change | `deployment-installability`: reproduce the exact packet from published release artifacts; separately qualify any desired rootless image-update/action surface; retain the opt-in default until the remaining provider, appliance, and review conditions pass |
|
||||
| Separate runner package update and package-cache cleanup | Root-owned `pulse-agent-runner`, host-bound action credential, typed request, postcondition, and durable receipt | The schema-v6 committed-main systemd receipt records a real verified apt-cache mutation, stale-fingerprint refusal, replay, nonce-bound readiness, effective-unit override rejection, exact credential rotation, bodyless self-revocation, and collector/helper continuity. A separate production Router regression exercises HTTPS issuance, WSS admission, encrypted token persistence, failed-rotation rollback, exact socket invalidation, two server restarts, old-secret rejection, and durable self-revoke | Qualified for the exercised systemd fixture paths and focused production Router lifecycle. Focused installer tests also cover atomic pending cancellation and fail-closed credential retention. The evidence does not cover every runner operation, representative providers, or an exact release candidate | `agent-lifecycle` and `api-contracts`: reproduce the combined systemd and production Router path from trusted RC artifacts with representative package-update success/failure/cancellation evidence |
|
||||
|
||||
@@ -691,11 +691,13 @@ and task inventory on the Docker page.
|
||||
|
||||
Pulse does not use a Proxmox node agent to look inside LXCs by default. The
|
||||
node agent does automatically collect filesystem capacity for running LXCs
|
||||
when the local `pct` tool is available. On cgroup v2 PVE hosts it finds running
|
||||
guests from the node's container configs and cgroup state and measures mount
|
||||
capacity with statfs through each guest's init process in `/proc`; it falls back
|
||||
to bounded `pct list` and `pct df <vmid>` calls when that discovery or a
|
||||
per-container probe cannot establish a guest's state. It reports only mount
|
||||
when the local `pct` tool is available. The current collector first uses
|
||||
socket-free cgroup v2 and `/proc` discovery plus bounded mount probes. A
|
||||
root or explicitly `--grant-pct` collector can fall back to `pct list` and
|
||||
`pct df <vmid>` (with `lxc-info` where permitted); those commands cannot recover readings inside the isolated
|
||||
typed helper. See [helper-backed LXC readings](AGENT_SECURITY.md#proxmox-lxc-filesystem-readings-with-the-typed-helper)
|
||||
for partial, unavailable and older-helper results. Do not remove `PrivateNetwork`
|
||||
or broaden the agent's privileges to clear a missing reading. It reports only mount
|
||||
keys, volume labels, mount paths, and capacity/usage values; it does not run
|
||||
commands inside a guest or read guest files. Stopped LXCs retain the normal
|
||||
API-derived disk view.
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "399618939d3e7d1711b24ecbd43ba9129fed2d56",
|
||||
"verified_at": "2026-10-14T05:40:36Z",
|
||||
"result": "passed",
|
||||
"documentation_paths": [
|
||||
"docs/AGENT_SECURITY.md",
|
||||
"docs/UNIFIED_AGENT.md",
|
||||
"frontend-modern/public/docs/AGENT_SECURITY.md",
|
||||
"frontend-modern/public/docs/UNIFIED_AGENT.md"
|
||||
],
|
||||
"content_sha256": {
|
||||
"docs/AGENT_SECURITY.md": "ed6179dfb8665f707cdd2e38cb840f91b161729779b009802ad40eb3a4a16f6e",
|
||||
"docs/UNIFIED_AGENT.md": "6ddbd9d5661bccf5c32c08b666d5c55d68844c44beda571fff9b5599d37f7b47",
|
||||
"frontend-modern/public/docs/AGENT_SECURITY.md": "ed6179dfb8665f707cdd2e38cb840f91b161729779b009802ad40eb3a4a16f6e",
|
||||
"frontend-modern/public/docs/UNIFIED_AGENT.md": "6ddbd9d5661bccf5c32c08b666d5c55d68844c44beda571fff9b5599d37f7b47"
|
||||
},
|
||||
"routes": [
|
||||
"/docs/UNIFIED_AGENT#inside-guest-runtime-boundaries",
|
||||
"/docs/AGENT_SECURITY#proxmox-lxc-filesystem-readings-with-the-typed-helper"
|
||||
],
|
||||
"viewports": [
|
||||
{
|
||||
"engine": "desktop-chromium",
|
||||
"browser_version": "141.0.7390.37",
|
||||
"width": 1360,
|
||||
"height": 900,
|
||||
"theme": "light"
|
||||
},
|
||||
{
|
||||
"engine": "phone-webkit",
|
||||
"browser_version": "26.0",
|
||||
"width": 390,
|
||||
"height": 900,
|
||||
"theme": "dark"
|
||||
}
|
||||
],
|
||||
"playwright": "1.56.1",
|
||||
"source_inputs": 2827,
|
||||
"binding_sha256": "4e24d7487f8bd99e0ee568ee70d6bc5949bfe28a7c9a0aea5767fe0bd0b4719a",
|
||||
"served_document_sha256": {
|
||||
"frontend-modern/public/docs/AGENT_SECURITY.md": "ed6179dfb8665f707cdd2e38cb840f91b161729779b009802ad40eb3a4a16f6e",
|
||||
"frontend-modern/public/docs/UNIFIED_AGENT.md": "6ddbd9d5661bccf5c32c08b666d5c55d68844c44beda571fff9b5599d37f7b47"
|
||||
},
|
||||
"interactions": [
|
||||
"Actual shipped Unified Agent link: keyboard Enter on desktop, touch on phone; same-router navigation, heading focus, reload deep-link focus.",
|
||||
"Confined discovery/fallback, old-version, partial-v2/strict-v1, not-zero and helper-update distinctions rendered from actual mirrored files; no API requests or host operation."
|
||||
],
|
||||
"pixel_review": "All six captures opened with native view_image; readable body, wrapped phone heading/code and safety warnings, no page overflow. Existing wide qualification matrix has a separate horizontal viewport; not every column is qualified.",
|
||||
"captures": [
|
||||
{
|
||||
"file": "desktop-chromium-entry.png",
|
||||
"sha256": "0f0adc112d0b0259dfae0dc481fa470e0d1939bf560cf2f5ebd72e7a36116c54"
|
||||
},
|
||||
{
|
||||
"file": "desktop-chromium-helper.png",
|
||||
"sha256": "e89cc59255e039576df50c0682c2f74c993dbdad0e745ba7769b2ceea5cbb89b"
|
||||
},
|
||||
{
|
||||
"file": "desktop-chromium-partial.png",
|
||||
"sha256": "d66fa63d1678f17b237b5ba3c6995e50138cd8d14522d0e4534f5ac847d36ed2"
|
||||
},
|
||||
{
|
||||
"file": "phone-webkit-entry.png",
|
||||
"sha256": "518c801ce3efb2b70920ba4c08a09b24f786ef7195f990daf89a28478495e946"
|
||||
},
|
||||
{
|
||||
"file": "phone-webkit-helper.png",
|
||||
"sha256": "bdab502180e3c4436b616463aadef0bb393a0a785cea1a6356546a61dbb7f281"
|
||||
},
|
||||
{
|
||||
"file": "phone-webkit-partial.png",
|
||||
"sha256": "606087d9992d36a8f9d075babd381e77a76e6ef3153f2e9015fd7847fc892164"
|
||||
}
|
||||
],
|
||||
"cleanup": {
|
||||
"browser_closed": true,
|
||||
"server_closed": true
|
||||
},
|
||||
"limits": "Static shipped help, not native PVE/helper recovery, every-device, published or installed acceptance. A frontend-file guard skip is not this browser proof."
|
||||
}
|
||||
@@ -55,7 +55,7 @@ try {
|
||||
for (const file of ['package.json', 'package-lock.json', 'tsconfig.json', 'index.html'])
|
||||
binding.sources[file] = hash(fs.readFileSync(path.join(root, file)));
|
||||
fs.mkdirSync(path.join(output, 'docs'), { recursive: true });
|
||||
for (const name of ['FAQ', 'UNIFIED_AGENT', 'TROUBLESHOOTING', 'DOCKER', 'PBS']) {
|
||||
for (const name of ['FAQ', 'UNIFIED_AGENT', 'TROUBLESHOOTING', 'DOCKER', 'PBS', 'AGENT_SECURITY']) {
|
||||
const file = `public/docs/${name}.md`;
|
||||
binding.sources[file] = hash(fs.readFileSync(path.join(root, file)));
|
||||
fs.copyFileSync(path.join(root, file), path.join(output, 'docs', `${name}.md`));
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
// Real shipped Docs fetch/renderer/router, with no host or agent operation.
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const http = require('node:http');
|
||||
const crypto = require('node:crypto');
|
||||
const assert = require('node:assert/strict');
|
||||
const { chromium, webkit } = require('playwright');
|
||||
const root = '/workspace/frontend-modern';
|
||||
const preview = '/workspace/tmp/doc-command-help/static';
|
||||
const output = '/workspace/tmp/pve-helper-guidance';
|
||||
const anchor = 'proxmox-lxc-filesystem-readings-with-the-typed-helper';
|
||||
const digest = bytes => crypto.createHash('sha256').update(bytes).digest('hex');
|
||||
const bindingBytes = fs.readFileSync(path.join(preview, 'binding.json'));
|
||||
const binding = JSON.parse(bindingBytes);
|
||||
const result = { result: 'incomplete', source_sha: binding.source_sha,
|
||||
binding_sha256: digest(bindingBytes), playwright: require('playwright/package.json').version,
|
||||
cases: [], captures: [], errors: [], requests: [], served: [], cleanup: {},
|
||||
limits: 'Shipped help navigation/reading only; no native PVE, helper, installed recovery or release acceptance.' };
|
||||
|
||||
(async () => {
|
||||
let server, browser;
|
||||
try {
|
||||
fs.mkdirSync(output, { recursive: true });
|
||||
assert.equal(result.playwright, '1.56.1');
|
||||
assert.equal(result.playwright, JSON.parse(fs.readFileSync('/workspace/tests/integration/package-lock.json'))
|
||||
.packages['node_modules/@playwright/test'].version);
|
||||
for (const [file, hash] of Object.entries(binding.sources))
|
||||
assert.equal(digest(fs.readFileSync(path.join(root, file))), hash, file);
|
||||
const assets = new Map();
|
||||
for (const [file, hash] of Object.entries(binding.artifacts)) {
|
||||
const bytes = fs.readFileSync(path.join(preview, file));
|
||||
assert.equal(digest(bytes), hash, file); assets.set(file, bytes);
|
||||
}
|
||||
for (const name of ['AGENT_SECURITY', 'UNIFIED_AGENT'])
|
||||
assert.equal(binding.artifacts[`docs/${name}.md`], binding.sources[`public/docs/${name}.md`]);
|
||||
result.source_inputs = Object.keys(binding.sources).length;
|
||||
server = http.createServer((req, res) => {
|
||||
if (req.method !== 'GET') { res.writeHead(405); res.end(); return; }
|
||||
const pathname = new URL(req.url, 'http://fixture.invalid').pathname;
|
||||
const file = pathname.startsWith('/docs/') && !pathname.endsWith('.md')
|
||||
? 'browser-tests/doc-command-help.html' : pathname.slice(1);
|
||||
if (!assets.has(file)) { res.writeHead(404); res.end(); return; }
|
||||
const bytes = assets.get(file);
|
||||
result.served.push({ pathname, file, sha256: digest(bytes) });
|
||||
const ext = path.extname(file);
|
||||
res.writeHead(200, { 'Content-Type': ({ '.md': 'text/markdown', '.css': 'text/css',
|
||||
'.js': 'text/javascript', '.html': 'text/html' })[ext] + '; charset=utf-8', 'Cache-Control': 'no-store' });
|
||||
res.end(bytes);
|
||||
});
|
||||
await new Promise((resolve, reject) => {
|
||||
server.once('error', reject); server.listen(0, '127.0.0.1', resolve);
|
||||
});
|
||||
const origin = `http://127.0.0.1:${server.address().port}`;
|
||||
for (const [name, engine, width, dark] of [
|
||||
['desktop-chromium', chromium, 1360, false], ['phone-webkit', webkit, 390, true],
|
||||
]) {
|
||||
browser = await engine.launch(engine === chromium
|
||||
? { headless: true, channel: 'chromium', args: ['--no-sandbox'] } : { headless: true });
|
||||
const context = await browser.newContext({ viewport: { width, height: 900 },
|
||||
isMobile: width < 500, hasTouch: width < 500, locale: 'en-GB' });
|
||||
const page = await context.newPage(); page.setDefaultTimeout(12000);
|
||||
const record = { name, width, height: 900, dark, browser: browser.version(), checks: [] };
|
||||
result.cases.push(record);
|
||||
page.on('pageerror', error => result.errors.push(error.message));
|
||||
await page.route('**/*', route => {
|
||||
const url = new URL(route.request().url()); result.requests.push({ name, path: url.pathname });
|
||||
assert.equal(url.origin, origin, 'no external requests permitted');
|
||||
assert(!url.pathname.startsWith('/api/'), 'help must not probe a backend');
|
||||
return route.continue();
|
||||
});
|
||||
await page.addInitScript(dark => document.addEventListener('DOMContentLoaded', () =>
|
||||
document.documentElement.classList.toggle('dark', dark)), dark);
|
||||
const capture = async suffix => {
|
||||
await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))));
|
||||
assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), 'page overflow');
|
||||
const file = `${name}-${suffix}.png`; await page.screenshot({ path: path.join(output, file) });
|
||||
result.captures.push({ file, sha256: digest(fs.readFileSync(path.join(output, file))), name });
|
||||
};
|
||||
const activate = async link => {
|
||||
if (width < 500) await link.tap(); else { await link.focus(); await page.keyboard.press('Enter'); }
|
||||
};
|
||||
await page.goto(`${origin}/docs/UNIFIED_AGENT#inside-guest-runtime-boundaries`, { waitUntil: 'domcontentloaded' });
|
||||
const link = page.locator(`article a[href="/docs/AGENT_SECURITY#${anchor}"]`);
|
||||
await link.waitFor();
|
||||
await link.evaluate(element => window.scrollBy(0, element.closest('p').getBoundingClientRect().top - 64));
|
||||
await capture('entry');
|
||||
await page.evaluate(() => window.__helperDocsShell = 'same-shell');
|
||||
await activate(link); await page.waitForURL(`**/docs/AGENT_SECURITY#${anchor}`);
|
||||
await page.waitForFunction(anchor => {
|
||||
const heading = document.getElementById(anchor);
|
||||
return heading && document.activeElement === heading && heading.getBoundingClientRect().top >= 0
|
||||
&& heading.getBoundingClientRect().top < innerHeight;
|
||||
}, anchor);
|
||||
assert.equal(await page.evaluate(() => window.__helperDocsShell), 'same-shell');
|
||||
const section = await page.locator(`#${anchor}`).evaluate(heading => {
|
||||
let text = ''; for (let el = heading.nextElementSibling; el && el.tagName !== 'H3'; el = el.nextElementSibling)
|
||||
text += el.textContent + ' '; return text.replace(/\s+/g, ' ');
|
||||
});
|
||||
for (const phrase of ['without pct or lxc-info requests', 'fallback cannot recover',
|
||||
'cgroup v1', 'degraded or unavailable', 'Do not remove PrivateNetwork',
|
||||
'omitted containers cannot refresh old filesystem readings', 'Operation v1 remains complete-only',
|
||||
'collector-only automatic update cannot replace its helper']) assert(section.includes(phrase), phrase);
|
||||
record.checks.push('actual shipped cross-guide link, keyboard/touch, same shell, focused anchor, all boundary/partial distinctions');
|
||||
await capture('helper');
|
||||
const partial = page.locator('article p').filter({ hasText: 'When an inventory succeeds only partly' });
|
||||
await partial.evaluate(element => window.scrollBy(0, element.getBoundingClientRect().top - 64));
|
||||
await capture('partial');
|
||||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||
await page.waitForFunction(anchor => document.activeElement?.id === anchor, anchor);
|
||||
record.checks.push('reload preserves deep-link heading focus');
|
||||
await context.close(); await browser.close(); browser = null;
|
||||
}
|
||||
assert.equal(result.errors.length, 0);
|
||||
result.result = 'passed';
|
||||
} catch (error) { result.failure = error.stack; throw error; }
|
||||
finally {
|
||||
if (browser) await browser.close(); result.cleanup.browser_closed = true;
|
||||
if (server) await new Promise((resolve, reject) => {
|
||||
server.close(error => error ? reject(error) : resolve()); server.closeAllConnections();
|
||||
}); result.cleanup.server_closed = true;
|
||||
fs.writeFileSync(path.join(output, 'result.json'), JSON.stringify(result, null, 2) + '\n');
|
||||
}
|
||||
})().catch(error => { console.error(error); process.exitCode = 1; });
|
||||
@@ -101,7 +101,7 @@ manual probes during a freeze or thaw.
|
||||
|---|---|---|
|
||||
| PVE/PBS/PMG inventory, node status, VM/container status, storage usage, and normal Proxmox API metrics | Add the Proxmox connection with a read-only or narrowly scoped API token | No |
|
||||
| VM guest disk and memory details through QEMU Guest Agent | Use Proxmox API permissions such as `VM.GuestAgent.Audit` and `VM.GuestAgent.FileRead` where supported | No host agent for the Proxmox node |
|
||||
| All mounted LXC filesystem capacities and usage | Install the Unified Agent on the owning PVE node; the safe profile uses the typed helper's bounded fixed-shape `pct` reads | Yes, but the safe profile confines it to the root helper rather than a root collector |
|
||||
| Configured LXC filesystem capacities and usage | Install the Unified Agent on the owning PVE node; the safe profile uses [socket-free discovery and bounded mount probes](#proxmox-lxc-filesystem-readings-with-the-typed-helper) | Yes, but the safe profile confines it to the root helper rather than a root collector |
|
||||
| Docker/Podman containers inside a VM or LXC through guest-local reporting | Install the agent inside that VM/LXC with Docker/Podman monitoring enabled, or use another explicit guest access/reporting path | Requires runtime-socket access: root-equivalent for a rootful daemon, or a collector-owned rootless socket |
|
||||
| Docker containers inside an LXC from a Proxmox host agent | Turn on **Discover Docker in LXC guests** in Settings → System → General (admin only), or start Pulse with `PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY=true` to lock it on; optionally limit guests with `PULSE_PROXMOX_GUEST_DOCKER_INVENTORY_VMIDS=101,102` | Requires a root/equivalent Pulse agent on the Proxmox node and explicit server opt-in |
|
||||
| Host SMART, temperatures, local ZFS/Ceph/mdadm detail, arbitrary mount reads, and full host telemetry | Install the agent on that host | Varies: the safe typed helper covers SMART; capabilities outside the matrix still require the explicit root/full-telemetry profile |
|
||||
@@ -114,10 +114,10 @@ LXCs by default. Its automatic LXC filesystem collector is a node-local
|
||||
capacity query only. On cgroup v2 PVE hosts it finds running guests from the
|
||||
node's container configs and cgroup state, reads each running guest's init
|
||||
process ID and status from cgroup and `/proc`, and measures mount capacity with
|
||||
statfs through `/proc/<pid>/root`. When that discovery cannot establish a guest's state (for
|
||||
example on cgroup v1 hosts) it falls back to `pct list`, with `lxc-info` to
|
||||
find each guest's init process, and it runs `pct df <vmid>` for a guest only
|
||||
when the statfs probe is unavailable or fails. It reports
|
||||
statfs through `/proc/<pid>/root`. Root or explicitly `--grant-pct` collectors can fall back to
|
||||
`pct list` and `pct df <vmid>` (with `lxc-info` where permitted) when discovery or a mount probe fails.
|
||||
That command path is not usable inside the isolated typed helper; see
|
||||
[helper-backed LXC readings](#proxmox-lxc-filesystem-readings-with-the-typed-helper). It reports
|
||||
mount keys, volume labels, mount paths, and capacity/usage numbers. It does not
|
||||
run a command inside the guest or read guest file contents, environment, or
|
||||
container-runtime metadata. It skips guests reported stopped and bounds command
|
||||
@@ -198,22 +198,6 @@ block devices. If the helper is missing, incompatible, or rejects a request,
|
||||
only the affected telemetry disappears; the collector does not fall back to
|
||||
sudo, root, or a broader local command path.
|
||||
|
||||
Proxmox's `pct list` uses pmxcfs abstract Unix IPC, which is not reachable from
|
||||
the helper's private network namespace. On affected PVE hosts this leaves
|
||||
helper-backed LXC filesystem telemetry unavailable, while independent SMART
|
||||
and API monitoring can continue. Prefer the existing Proxmox API connection
|
||||
for the readings it provides; do not add a unit override or a root/sudo
|
||||
fallback. A successful SMART reading does not establish LXC helper health.
|
||||
|
||||
When an inventory succeeds only partly, operation v2 preserves the collected
|
||||
rows and explicitly names omitted container IDs. The helper module stays
|
||||
degraded, and omitted containers cannot refresh old filesystem readings.
|
||||
Operation v1 remains complete-only; a newer collector uses it only when an
|
||||
older helper explicitly rejects v2. An unavailable inventory is not an empty
|
||||
healthy snapshot or zero disk usage. An installer-managed helper upgrade
|
||||
restarts the verified helper after checking the effective isolated unit and
|
||||
socket identity; a collector-only automatic update cannot replace its helper.
|
||||
|
||||
The installer treats the effective systemd configuration as part of the safe
|
||||
profile boundary. The collector, helper service, helper socket, and an
|
||||
installed action runner must load from the installer-owned `FragmentPath` with
|
||||
@@ -291,6 +275,41 @@ packet never reaches Docker. The post-run attester re-verifies those same
|
||||
snapshot paths against the receipt. No qualifying RC containing this boundary
|
||||
has been published yet.
|
||||
|
||||
### Proxmox LXC filesystem readings with the typed helper
|
||||
|
||||
The current helper can collect LXC filesystem readings **without `pct` or
|
||||
`lxc-info` requests**. It reads the owning node's container configuration,
|
||||
finds running guests and their init processes through cgroup v2 and `/proc`,
|
||||
and measures configured mounts with the bounded filesystem prober. It does not
|
||||
execute a command inside a guest, read guest file contents or accept a
|
||||
caller-selected VMID or path.
|
||||
|
||||
Proxmox's `pct` and `lxc-info` commands use host-network-namespace Unix IPC.
|
||||
Their fallback cannot recover a failed discovery or mount probe inside
|
||||
`PrivateNetwork=true`. On a cgroup v1 host, or when a running guest's state
|
||||
cannot be established, the helper can therefore report degraded or unavailable
|
||||
inventory rather than a complete list. Independent SMART and API readings can
|
||||
still work; a successful SMART reading does not establish LXC helper health.
|
||||
|
||||
Use the existing reading's status, age and collection path before treating
|
||||
missing filesystems as missing guests. Keep the Pulse server, collector and
|
||||
helper versions separate; a newer guide or collector alone does not establish
|
||||
that the installed helper has this path. Older helper versions relying on
|
||||
`pct` can fail even when SMART works. Prefer the existing Proxmox API connection
|
||||
for the readings it already provides, without claiming it covers every mount.
|
||||
Do not remove `PrivateNetwork`, add a unit override, enable a root/sudo
|
||||
fallback, reinstall or restart anything merely to diagnose a missing reading.
|
||||
Keep credentials and full service/configuration records private.
|
||||
|
||||
When an inventory succeeds only partly, operation v2 preserves the collected
|
||||
rows and explicitly names omitted container IDs. The helper module stays
|
||||
degraded, and omitted containers cannot refresh old filesystem readings.
|
||||
Operation v1 remains complete-only; a newer collector uses it only when an
|
||||
older helper explicitly rejects v2. An unavailable inventory is not an empty
|
||||
healthy snapshot or zero disk usage. An installer-managed helper upgrade
|
||||
restarts the verified helper after checking the effective isolated unit and
|
||||
socket identity; a collector-only automatic update cannot replace its helper.
|
||||
|
||||
### Safe-profile support and qualification matrix
|
||||
|
||||
`Qualified` below means the named evidence exercised the behavior on a real
|
||||
@@ -305,7 +324,7 @@ or generic command path.
|
||||
| Proxmox VE/PBS/PMG inventory, status, storage, and ordinary metrics | API-only connection with a narrowly scoped token; no host agent | No collector, helper, or runner authority is required for this data | Supported independently of the safe host-agent profile; it does not prove host-local SMART, LXC filesystem, or action parity | `agent-lifecycle`: keep API permissions and returned telemetry covered by provider tests |
|
||||
| Standard Linux systemd host telemetry and collector update | Unprivileged `pulse-agent` plus the root-owned typed helper | Core `/proc`, filesystem, network, RAID, and hwmon telemetry stays in the collector; signed updates cross the fixed helper protocol and commit only after an authoritative report | **Qualified on disposable Ubuntu 24.04.4 arm64 at committed main `22fd662fb794f63efb9d3ca2158de73c4e07e1b8`** across the schema-v6 twenty-scenario run. It covers install, migration, explicit/automatic rollback, helper protocol and resource boundaries, effective-unit override rejection, reporting continuity, authoritative executable-digest commit, watchdog rollback, helper-restart recovery, and last-known-good restoration. It remains opt-in pending exact-RC reproduction, representative provider/appliance parity, and external review | `deployment-installability` and `security-privacy`: reproduce the same helper transaction from trusted release artifacts on representative hosts and accept the external boundary review |
|
||||
| Linux SMART telemetry | `smart.snapshot` through the no-network helper; no caller-selected device or arguments | Implemented, unqualified on representative physical disks. Helper failure omits/degrades SMART only; the collector does not retry as root | Does not yet justify SMART parity or a default change | `agent-lifecycle`: record live SATA, SAS/controller, USB bridge, and NVMe evidence, including standby, permission failure, timeout, and partial-data cases |
|
||||
| Proxmox node-local LXC filesystem telemetry | `proxmox.lxc_filesystems` through the no-network helper. Configs are read through the pmxcfs mount, running guests and their init processes come from cgroup v2 and `/proc`, and usage comes from the shared filesystem prober, which pins the guest's init, re-checks its identity, and resolves each mount inside the guest root (openat2, or a symlink-refusing component walk where the helper's seccomp filter blocks openat2); the caller selects no VMID, path, or command. `pct` and `lxc-info` cannot work inside the helper, because pmxcfs and the LXC monitor listen on abstract Unix sockets that belong to the host network namespace (#2511); they remain the fallback whenever discovery cannot establish a guest's state, on cgroup v1 hosts, for root and `--grant-pct` installs, and per container when the prober read fails | Implemented. Exercised on a disposable PVE 9.2.18 node under the helper unit's sandbox properties, with running unprivileged (rootfs plus a mount point) and privileged guests and a stopped one; readings matched `pct df`. This is one PVE version, with no timeout, output-bound, helper-loss, or live nested-namespace evidence. Helper failure omits/degrades this snapshot only | Does not yet justify Proxmox host-agent parity or a default change | `agent-lifecycle`: record timeout, output-bound, and helper-loss behavior across supported PVE versions |
|
||||
| Proxmox node-local LXC filesystem telemetry | `proxmox.lxc_filesystems` through the no-network helper's [socket-free discovery and bounded mount probes](#proxmox-lxc-filesystem-readings-with-the-typed-helper). Failed discovery or probes remain degraded/unavailable; command fallback works only outside the helper, in a separately chosen root or `--grant-pct` profile | Implemented. Exercised on a disposable PVE 9.2.18 node under the helper unit's sandbox properties, with running unprivileged (rootfs plus a mount point) and privileged guests and a stopped one; readings matched `pct df`. This is one PVE version, with no timeout, output-bound, helper-loss, or live nested-namespace evidence. Helper failure omits/degrades this snapshot only | Does not yet justify Proxmox host-agent parity or a default change | `agent-lifecycle`: record timeout, output-bound, and helper-loss behavior across supported PVE versions |
|
||||
| Rootful Docker or Podman inventory | No direct collector access to a root-equivalent daemon socket | Implemented as a typed-helper summary-only fallback. Migration preserves container ID/name/image/state/status/creation inventory and marks the report `typed-helper-summary`; stats, secondary inventories, update checks, and actions remain unavailable | The standalone rootless packet exercises live same-family rootful Docker and Podman helper fallback plus summary-inventory semantic parity during socket loss. It does not qualify the complete standalone rootful fresh-install, migration, restart, helper-loss/recovery, and bounds matrix. Full rootful parity remains an explicit default blocker; the legacy/root profile is not safe-profile evidence | `agent-lifecycle`: record the remaining standalone lifecycle matrix on representative rootful Docker and Podman and decide explicitly whether reduced telemetry is sufficient |
|
||||
| Collector-owned rootless Docker or Podman | Direct access only to one usable runtime socket owned by the `pulse-agent` UID whose daemon also attests rootless mode | Qualified locally for the exact source-bound monitoring path. Discovery is deferred until the collector account exists and accepts exactly one readable/writable, non-symlink socket owned by that UID across Docker and Podman. Cross-user, root-owned, remote, unreadable, non-rootless-daemon, or ambiguous endpoints are rejected. An installer-owned exact pin survives daemon loss and update; the running collector can move between direct rootless monitoring and rootful typed-helper summary without regaining action authority | **Qualified on distinct disposable Ubuntu 24.04/systemd 255 arm64 hosts at commit `60041ad9e60c282c892f944e04f777b874991a5d`.** All eleven canonical scenarios passed for Docker and all eleven for Podman. Receipt SHA-256: `7a116d63ab0cd1560482165055f0a8c9158ce0a8333a27bd9e8256582a52dfb5`; local attestation SHA-256: `566279ecd7d7dfa89c92f24243e9fcd5ae3e295d4284bd559d4be42f1e83b3b5`. This is local opt-in artifact-bound self-attestation, not published-RC provenance, action/update qualification, external review, or authorization for a default change | `deployment-installability`: reproduce the exact packet from published release artifacts; separately qualify any desired rootless image-update/action surface; retain the opt-in default until the remaining provider, appliance, and review conditions pass |
|
||||
| Separate runner package update and package-cache cleanup | Root-owned `pulse-agent-runner`, host-bound action credential, typed request, postcondition, and durable receipt | The schema-v6 committed-main systemd receipt records a real verified apt-cache mutation, stale-fingerprint refusal, replay, nonce-bound readiness, effective-unit override rejection, exact credential rotation, bodyless self-revocation, and collector/helper continuity. A separate production Router regression exercises HTTPS issuance, WSS admission, encrypted token persistence, failed-rotation rollback, exact socket invalidation, two server restarts, old-secret rejection, and durable self-revoke | Qualified for the exercised systemd fixture paths and focused production Router lifecycle. Focused installer tests also cover atomic pending cancellation and fail-closed credential retention. The evidence does not cover every runner operation, representative providers, or an exact release candidate | `agent-lifecycle` and `api-contracts`: reproduce the combined systemd and production Router path from trusted RC artifacts with representative package-update success/failure/cancellation evidence |
|
||||
|
||||
@@ -691,11 +691,13 @@ and task inventory on the Docker page.
|
||||
|
||||
Pulse does not use a Proxmox node agent to look inside LXCs by default. The
|
||||
node agent does automatically collect filesystem capacity for running LXCs
|
||||
when the local `pct` tool is available. On cgroup v2 PVE hosts it finds running
|
||||
guests from the node's container configs and cgroup state and measures mount
|
||||
capacity with statfs through each guest's init process in `/proc`; it falls back
|
||||
to bounded `pct list` and `pct df <vmid>` calls when that discovery or a
|
||||
per-container probe cannot establish a guest's state. It reports only mount
|
||||
when the local `pct` tool is available. The current collector first uses
|
||||
socket-free cgroup v2 and `/proc` discovery plus bounded mount probes. A
|
||||
root or explicitly `--grant-pct` collector can fall back to `pct list` and
|
||||
`pct df <vmid>` (with `lxc-info` where permitted); those commands cannot recover readings inside the isolated
|
||||
typed helper. See [helper-backed LXC readings](AGENT_SECURITY.md#proxmox-lxc-filesystem-readings-with-the-typed-helper)
|
||||
for partial, unavailable and older-helper results. Do not remove `PrivateNetwork`
|
||||
or broaden the agent's privileges to clear a missing reading. It reports only mount
|
||||
keys, volume labels, mount paths, and capacity/usage values; it does not run
|
||||
commands inside a guest or read guest files. Stopped LXCs retain the normal
|
||||
API-derived disk view.
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { renderDocMarkdown } from '../docMarkdown';
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../..');
|
||||
const anchor = 'proxmox-lxc-filesystem-readings-with-the-typed-helper';
|
||||
const article = (name: string): HTMLElement => {
|
||||
const element = document.createElement('article');
|
||||
element.innerHTML = renderDocMarkdown(
|
||||
readFileSync(path.join(root, 'frontend-modern/public/docs', `${name}.md`), 'utf8'), name,
|
||||
);
|
||||
return element;
|
||||
};
|
||||
|
||||
describe('confined PVE helper reading guidance', () => {
|
||||
it('links the deployment choice and support matrix to the same shipped section', () => {
|
||||
const guide = article('AGENT_SECURITY');
|
||||
expect(guide.querySelector(`#${anchor}`)?.textContent).toBe(
|
||||
'Proxmox LXC filesystem readings with the typed helper',
|
||||
);
|
||||
expect(guide.querySelectorAll(`a[href="#${anchor}"]`).length).toBeGreaterThanOrEqual(3);
|
||||
const row = [...guide.querySelectorAll('tr')].find(element =>
|
||||
element.textContent?.includes('Proxmox node-local LXC filesystem telemetry'))!;
|
||||
expect(row.textContent).toContain('command fallback works only outside the helper');
|
||||
expect(row.querySelector(`a[href="#${anchor}"]`)).not.toBeNull();
|
||||
});
|
||||
|
||||
it('keeps the confined-helper distinction reachable from Unified Agent', () => {
|
||||
const guide = article('UNIFIED_AGENT');
|
||||
const link = guide.querySelector(`a[href="/docs/AGENT_SECURITY#${anchor}"]`);
|
||||
expect(link?.textContent).toBe('helper-backed LXC readings');
|
||||
expect(link?.closest('p')?.textContent?.replace(/\s+/g, ' ')).toContain(
|
||||
'those commands cannot recover readings inside the isolated typed helper',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Static guide/source binding, not installed PVE or filesystem recovery."""
|
||||
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
HEADING = '### Proxmox LXC filesystem readings with the typed helper\n'
|
||||
ANCHOR = '#proxmox-lxc-filesystem-readings-with-the-typed-helper'
|
||||
|
||||
|
||||
def plain(text):
|
||||
return ' '.join(text.replace('**', '').replace('`', '').split())
|
||||
|
||||
|
||||
class PVEHelperGuidanceTest(unittest.TestCase):
|
||||
# A source-root override supports a sensitivity check of the real parent.
|
||||
root = ROOT
|
||||
|
||||
def test_existing_guides_and_matrix_reach_one_consistent_boundary(self):
|
||||
security = (self.root / 'docs/AGENT_SECURITY.md').read_text()
|
||||
general = plain(security.split('## Proxmox Deployment Choices\n', 1)[1].split('\n## ', 1)[0])
|
||||
self.assertNotIn("the safe profile uses the typed helper's bounded fixed-shape pct reads", general)
|
||||
self.assertIn('Root or explicitly --grant-pct collectors can fall back', general)
|
||||
self.assertIn('not usable inside the isolated typed helper', general)
|
||||
self.assertGreaterEqual(security.count(ANCHOR), 3)
|
||||
row = next(line for line in security.splitlines() if line.startswith('| Proxmox node-local LXC'))
|
||||
self.assertIn('command fallback works only outside the helper', row)
|
||||
self.assertNotIn('they remain the fallback whenever', row)
|
||||
agent = (self.root / 'docs/UNIFIED_AGENT.md').read_text().split(
|
||||
'### Inside-Guest Runtime Boundaries\n', 1)[1].split('\n## ', 1)[0]
|
||||
self.assertIn('AGENT_SECURITY.md' + ANCHOR, agent)
|
||||
self.assertIn('those commands cannot recover readings inside the isolated', plain(agent))
|
||||
for name in ('AGENT_SECURITY.md', 'UNIFIED_AGENT.md'):
|
||||
self.assertEqual((self.root / 'docs' / name).read_bytes(),
|
||||
(self.root / 'frontend-modern/public/docs' / name).read_bytes())
|
||||
|
||||
def test_partial_and_unknown_readings_do_not_authorise_privilege_or_state_churn(self):
|
||||
text = plain((self.root / 'docs/AGENT_SECURITY.md').read_text().split(HEADING, 1)[1].split('\n### ', 1)[0])
|
||||
for distinction in ('without pct or lxc-info requests', 'cgroup v2 and /proc',
|
||||
'fallback cannot recover', 'cgroup v1', 'degraded or unavailable',
|
||||
'successful SMART reading does not establish LXC helper health',
|
||||
"reading's status, age and collection path", 'helper versions separate',
|
||||
'Older helper versions', 'without claiming it covers every mount',
|
||||
'Do not remove PrivateNetwork', 'root/sudo fallback, reinstall or restart',
|
||||
'full service/configuration records private', 'operation v2 preserves',
|
||||
'omitted containers cannot refresh old filesystem readings',
|
||||
'Operation v1 remains complete-only', 'explicitly rejects v2',
|
||||
'not an empty healthy snapshot or zero disk usage',
|
||||
'collector-only automatic update cannot replace its helper'):
|
||||
with self.subTest(distinction=distinction):
|
||||
self.assertIn(distinction, text)
|
||||
self.assertNotIn('```', text, 'read existing evidence rather than run a probe')
|
||||
|
||||
def test_current_collector_has_the_described_closed_discovery_and_partial_path(self):
|
||||
discover = (self.root / 'internal/hostagent/proxmox_lxc_running.go').read_text()
|
||||
collect = (self.root / 'internal/hostagent/proxmox_lxc_filesystems.go').read_text()
|
||||
tests = (self.root / 'internal/hostagent/proxmox_lxc_filesystems_test.go').read_text()
|
||||
for symbol in ('discoverRunningProxmoxLXCContainers', 'cgroup.controllers', 'filesystemprobe.IsNamespaceInit'):
|
||||
self.assertIn(symbol, discover)
|
||||
for symbol in ('CollectProxmoxLXCFilesystemsLocalResult', 'ProxmoxLXCCollectionPartial',
|
||||
'OmittedVMIDs', 'result.Degraded = true'):
|
||||
self.assertIn(symbol, collect)
|
||||
self.assertIn('TestDiscoverRunningProxmoxLXCContainersFindsInitWithoutSockets', tests)
|
||||
self.assertIn('pct and lxc-info exist but every call fails', tests)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user