mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 04:33:17 +00:00
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:
@@ -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
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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`)),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user