Make Proxmox permission help token-aware and backup-safe

Remove the user placeholder command that Bash treats as file redirection. Diagnose the configured account and privilege-separated token on the affected scope without broader grants, token rotation or guest probes. Clarify that API-only guest reads still use the backup channel and preserve the monitoring-outage precaution in the existing help. Connected shipped-Markdown controls bind both entry points to the generated token/ACL contract.

Contract-Neutral: Existing help and regression controls only; no runtime, schema, collector, token, ACL, navigation or dependency contract change.
Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-10-05 03:43:34 +01:00
parent bbe2649d91
commit 3c249ecacf
7 changed files with 218 additions and 26 deletions
+9 -1
View File
@@ -5,7 +5,7 @@ Pulse agents incorporate several security mechanisms to ensure that the code run
**Start with the least privilege that answers your monitoring question.** For
Proxmox VE, PBS, and PMG, that is usually no agent at all: API-only monitoring
with a read-only token covers inventory, status, and metrics, and the
generated setup script creates a privilege-separated monitoring user for it
generated setup script creates a privilege-separated monitoring token for it
(see [Proxmox Deployment Choices](#proxmox-deployment-choices)). Install a
host agent only where you want data the platform API cannot provide, and on
Linux consider the supported
@@ -89,6 +89,14 @@ You do not need a Pulse agent on every Proxmox-related host just to see basic
cluster inventory and utilization. Start with the least-privilege path that
answers your monitoring question:
**API-only does not mean guest-agent-free.** VM guest filesystem and memory
collection can still send active requests on the channel used by freeze-enabled
backups. Read permissions and the absence of a host agent do not prove backup
safety. For an affected installation, follow the
[backup safety precaution](VM_DISK_MONITORING.md#backup-safety), including the
monitoring/alert outage while Pulse is stopped; do not add permissions or send
manual probes during a freeze or thaw.
| Goal | Recommended path | Host privilege needed? |
|---|---|---|
| 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 |
+32 -12
View File
@@ -630,23 +630,43 @@ remove credentials, cookies, secret URLs and private host, network or personal
information. See [Getting Help](#-getting-help) for safe evidence collection.
### Check Permissions (Proxmox)
If Pulse can't see VMs or storage, check the user permissions on Proxmox:
```bash
pveum user permissions <user>@pam
```
At minimum, ensure the user/token has read access for inventory and metrics:
- `Sys.Audit`
- `Datastore.Audit`
If Pulse cannot see VMs, storage or guest readings, start with the **user,
realm and token ID configured for the affected Pulse connection**, not a
guessed account or your administrator login. Use the existing connection
settings; do not dump the server configuration or expose the token secret.
For VM guest agent features on PVE 9+, prefer:
With privilege separation enabled, the token's effective access is the
**intersection of the user and token permissions**: both must allow the
required privilege on the affected resource path. Ask the Proxmox
administrator to inspect both sides, including inherited ACLs and their
propagation, for that VM, storage or node. An administrator session or
user-only permission listing does not prove the token has access.
- `VM.GuestAgent.Audit` — required for disk usage and guest info
- `VM.GuestAgent.FileRead` — required for accurate memory monitoring (excludes buff/cache)
Inspect the existing denial and compare it with the relevant read privileges:
For PVE 8 only, use `VM.Monitor` instead of the `VM.GuestAgent.*` privileges.
- Inventory and metrics: `Sys.Audit` and `Datastore.Audit`, on the relevant
node/storage scope.
- VM filesystem usage and guest information on PVE 9+: `VM.GuestAgent.Audit`.
- Guest memory through `/proc/meminfo` on PVE 9+: also
`VM.GuestAgent.FileRead`.
- On PVE 8, `VM.Monitor` is the legacy guest-agent fallback, not an extra
requirement for PVE 9+.
Note: The built-in `PVEAuditor` role cannot be modified. Create a custom role (e.g. `PulseMonitor`) with the above privileges added, and assign it to your Pulse API token. After upgrading to PVE 9, add the `VM.GuestAgent.*` privileges and remove legacy `VM.Monitor` from the custom role.
The built-in `PVEAuditor` role cannot be modified. Where a read privilege is
actually missing, use the existing narrow custom role and matching scoped
user/token ACLs. **Do not disable privilege separation, grant Administrator or
add guest execution/write privileges** to diagnose missing data. Do not rerun
setup or replace a token as a permissions test: setup can rotate an existing
credential. Make any necessary access repair in your normal maintenance
window, outside backups, then observe normal polling without manual
guest-agent probes. `Sys.Modify` is not a read-only monitoring privilege; see
[pending-update access](#proxmox-pending-update-access) before considering it.
Keep token secrets and full permission listings private. A report needs only
the relevant missing privilege and a redacted denial, not private identities,
resource paths or the complete ACL tree. Successful inventory does not prove
that guest readings are fresh, or that a guest has thawed.
**Rocky Linux / RHEL VMs**: File-read restrictions in `/etc/sysconfig/qemu-ga`
can explain missing guest memory; they do not by themselves establish why disk
+6
View File
@@ -128,6 +128,12 @@ procedure; a reboot interrupts its workloads.
Use the dedicated account and token actually configured for that Pulse
connection, not a guessed account name. Review their effective privileges for
the affected VM; a host-root diagnostic does not test the API token's access.
With privilege separation enabled, access is the intersection of user and token
permissions: both must allow the required read on the affected VM. Do not disable
privilege separation or recreate the token to diagnose a missing reading. See
[Check permissions](TROUBLESHOOTING.md#check-permissions-proxmox) for the scoped
inspection and repair boundary. A permitted read does not prove disk freshness,
responsiveness or thaw.
| Collection | Proxmox VE 9+ | Proxmox VE 8 |
| --- | --- | --- |
@@ -5,7 +5,7 @@ Pulse agents incorporate several security mechanisms to ensure that the code run
**Start with the least privilege that answers your monitoring question.** For
Proxmox VE, PBS, and PMG, that is usually no agent at all: API-only monitoring
with a read-only token covers inventory, status, and metrics, and the
generated setup script creates a privilege-separated monitoring user for it
generated setup script creates a privilege-separated monitoring token for it
(see [Proxmox Deployment Choices](#proxmox-deployment-choices)). Install a
host agent only where you want data the platform API cannot provide, and on
Linux consider the supported
@@ -89,6 +89,14 @@ You do not need a Pulse agent on every Proxmox-related host just to see basic
cluster inventory and utilization. Start with the least-privilege path that
answers your monitoring question:
**API-only does not mean guest-agent-free.** VM guest filesystem and memory
collection can still send active requests on the channel used by freeze-enabled
backups. Read permissions and the absence of a host agent do not prove backup
safety. For an affected installation, follow the
[backup safety precaution](VM_DISK_MONITORING.md#backup-safety), including the
monitoring/alert outage while Pulse is stopped; do not add permissions or send
manual probes during a freeze or thaw.
| Goal | Recommended path | Host privilege needed? |
|---|---|---|
| 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 |
+32 -12
View File
@@ -630,23 +630,43 @@ remove credentials, cookies, secret URLs and private host, network or personal
information. See [Getting Help](#-getting-help) for safe evidence collection.
### Check Permissions (Proxmox)
If Pulse can't see VMs or storage, check the user permissions on Proxmox:
```bash
pveum user permissions <user>@pam
```
At minimum, ensure the user/token has read access for inventory and metrics:
- `Sys.Audit`
- `Datastore.Audit`
If Pulse cannot see VMs, storage or guest readings, start with the **user,
realm and token ID configured for the affected Pulse connection**, not a
guessed account or your administrator login. Use the existing connection
settings; do not dump the server configuration or expose the token secret.
For VM guest agent features on PVE 9+, prefer:
With privilege separation enabled, the token's effective access is the
**intersection of the user and token permissions**: both must allow the
required privilege on the affected resource path. Ask the Proxmox
administrator to inspect both sides, including inherited ACLs and their
propagation, for that VM, storage or node. An administrator session or
user-only permission listing does not prove the token has access.
- `VM.GuestAgent.Audit` — required for disk usage and guest info
- `VM.GuestAgent.FileRead` — required for accurate memory monitoring (excludes buff/cache)
Inspect the existing denial and compare it with the relevant read privileges:
For PVE 8 only, use `VM.Monitor` instead of the `VM.GuestAgent.*` privileges.
- Inventory and metrics: `Sys.Audit` and `Datastore.Audit`, on the relevant
node/storage scope.
- VM filesystem usage and guest information on PVE 9+: `VM.GuestAgent.Audit`.
- Guest memory through `/proc/meminfo` on PVE 9+: also
`VM.GuestAgent.FileRead`.
- On PVE 8, `VM.Monitor` is the legacy guest-agent fallback, not an extra
requirement for PVE 9+.
Note: The built-in `PVEAuditor` role cannot be modified. Create a custom role (e.g. `PulseMonitor`) with the above privileges added, and assign it to your Pulse API token. After upgrading to PVE 9, add the `VM.GuestAgent.*` privileges and remove legacy `VM.Monitor` from the custom role.
The built-in `PVEAuditor` role cannot be modified. Where a read privilege is
actually missing, use the existing narrow custom role and matching scoped
user/token ACLs. **Do not disable privilege separation, grant Administrator or
add guest execution/write privileges** to diagnose missing data. Do not rerun
setup or replace a token as a permissions test: setup can rotate an existing
credential. Make any necessary access repair in your normal maintenance
window, outside backups, then observe normal polling without manual
guest-agent probes. `Sys.Modify` is not a read-only monitoring privilege; see
[pending-update access](#proxmox-pending-update-access) before considering it.
Keep token secrets and full permission listings private. A report needs only
the relevant missing privilege and a redacted denial, not private identities,
resource paths or the complete ACL tree. Successful inventory does not prove
that guest readings are fresh, or that a guest has thawed.
**Rocky Linux / RHEL VMs**: File-read restrictions in `/etc/sysconfig/qemu-ga`
can explain missing guest memory; they do not by themselves establish why disk
@@ -128,6 +128,12 @@ procedure; a reboot interrupts its workloads.
Use the dedicated account and token actually configured for that Pulse
connection, not a guessed account name. Review their effective privileges for
the affected VM; a host-root diagnostic does not test the API token's access.
With privilege separation enabled, access is the intersection of user and token
permissions: both must allow the required read on the affected VM. Do not disable
privilege separation or recreate the token to diagnose a missing reading. See
[Check permissions](TROUBLESHOOTING.md#check-permissions-proxmox) for the scoped
inspection and repair boundary. A permitted read does not prove disk freshness,
responsiveness or thaw.
| Collection | Proxmox VE 9+ | Proxmox VE 8 |
| --- | --- | --- |
@@ -0,0 +1,124 @@
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 repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../..');
function renderGuide(name: string): HTMLElement {
const article = document.createElement('article');
article.innerHTML = renderDocMarkdown(
readFileSync(path.join(repoRoot, 'frontend-modern/public/docs', `${name}.md`), 'utf8'),
name,
);
return article;
}
function section(article: HTMLElement, id: string): HTMLElement {
const heading = article.querySelector(`#${id}`);
expect(heading, `missing permission guide section ${id}`).not.toBeNull();
return followingContent(heading!);
}
function followingContent(heading: Element): HTMLElement {
const content = document.createElement('div');
for (let element = heading.nextElementSibling; element; element = element.nextElementSibling) {
if (/^H[1-3]$/.test(element.tagName)) break;
content.appendChild(element.cloneNode(true));
}
return content;
}
const prose = (element: HTMLElement): string =>
(element.textContent ?? '').replace(/\s+/g, ' ').trim();
describe('shipped Proxmox permission diagnosis', () => {
it('checks the configured user and token on the affected resource, not an administrator', () => {
const content = prose(section(renderGuide('TROUBLESHOOTING'), 'check-permissions-proxmox'));
expect(content).toContain(
'user, realm and token ID configured for the affected Pulse connection',
);
expect(content).toContain('intersection of the user and token permissions');
expect(content).toContain(
'both must allow the required privilege on the affected resource path',
);
expect(content).toContain('inherited ACLs and their propagation');
expect(content).toContain('administrator session or user-only permission listing');
expect(content).toContain('does not prove the token has access');
});
it('removes the literal placeholder command that redirects and overwrites local files', () => {
const content = section(renderGuide('TROUBLESHOOTING'), 'check-permissions-proxmox');
// The old <user>@pam snippet passes bash -n, but redirects from `user`
// into `@pam` instead of passing a user ID. Inspection needs no command.
expect(content.querySelector('pre')).toBeNull();
expect(prose(content)).not.toContain('pveum user permissions');
expect(prose(content)).toContain('Inspect the existing denial');
});
it('preserves least privilege and avoids rotating credentials or probing a frozen guest', () => {
const content = prose(section(renderGuide('TROUBLESHOOTING'), 'check-permissions-proxmox'));
expect(content).toContain('Do not disable privilege separation');
expect(content).toContain('grant Administrator');
expect(content).toContain('add guest execution/write privileges');
expect(content).toContain('Do not rerun setup or replace a token as a permissions test');
expect(content).toContain('normal maintenance window, outside backups');
expect(content).toContain('normal polling without manual guest-agent probes');
expect(content).toContain('Keep token secrets and full permission listings private');
expect(content).toContain('missing privilege and a redacted denial');
expect(content).toContain('Sys.Modify is not a read-only monitoring privilege');
});
it('distinguishes active guest reads from passive inventory before recommending permissions', () => {
const content = section(renderGuide('AGENT_SECURITY'), 'proxmox-deployment-choices');
expect(prose(content)).toContain('API-only does not mean guest-agent-free');
expect(prose(content)).toContain(
'active requests on the channel used by freeze-enabled backups',
);
expect(prose(content)).toContain(
'Read permissions and the absence of a host agent do not prove backup safety',
);
const warning = Array.from(content.querySelectorAll('p')).find((paragraph) =>
paragraph.textContent?.includes('API-only does not mean guest-agent-free'),
);
const table = content.querySelector('table');
expect(table).not.toBeNull();
expect(warning?.compareDocumentPosition(table!)).toBe(Node.DOCUMENT_POSITION_FOLLOWING);
expect(warning?.querySelector('a')?.getAttribute('href')).toBe(
'/docs/VM_DISK_MONITORING#backup-safety',
);
});
it('keeps the disk-specific entry point token-aware without promising a fresh reading', () => {
const heading = Array.from(renderGuide('VM_DISK_MONITORING').querySelectorAll('h2')).find(
(element) => element.textContent === '⚙️ Permissions',
);
expect(heading).toBeDefined();
const content = followingContent(heading!);
const text = prose(content);
expect(text).toContain('intersection of user and token permissions');
expect(text).toContain('on the affected VM');
expect(text).toContain('Do not disable privilege separation or recreate the token');
expect(text).toContain('does not prove disk freshness, responsiveness or thaw');
expect(content.querySelector('a')?.getAttribute('href')).toBe(
'/docs/TROUBLESHOOTING#check-permissions-proxmox',
);
});
it('matches the existing generated token and ACL contract without running setup', () => {
// Static binding only: neither this test nor the docs create a real token.
const setup = readFileSync(
path.join(repoRoot, 'internal/api/configapi/setup_script_render.go'),
'utf8',
);
expect(setup).toContain('--privsep 1');
expect(setup).toContain('pveum aclmod / -user pulse-monitor@pve -role PVEAuditor');
expect(setup).toContain('pveum aclmod / -token "$PULSE_TOKEN_ID" -role PVEAuditor');
for (const name of ['TROUBLESHOOTING', 'VM_DISK_MONITORING', 'AGENT_SECURITY']) {
expect(readFileSync(path.join(repoRoot, 'docs', `${name}.md`))).toEqual(
readFileSync(path.join(repoRoot, 'frontend-modern/public/docs', `${name}.md`)),
);
}
});
});