mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-10 00:14:38 +00:00
parent
806fd7409c
commit
28cee7c467
11 changed files with 125 additions and 44 deletions
|
|
@ -137,15 +137,16 @@ pveum user permissions <user>@pam
|
|||
At minimum, ensure the user/token has read access for inventory and metrics:
|
||||
|
||||
- `Sys.Audit`
|
||||
- `VM.Monitor`
|
||||
- `Datastore.Audit`
|
||||
|
||||
For VM guest agent features on PVE 9+, also ensure:
|
||||
For VM guest agent features on PVE 9+, prefer:
|
||||
|
||||
- `VM.GuestAgent.Audit` — required for disk usage and guest info
|
||||
- `VM.GuestAgent.FileRead` — required for accurate memory monitoring (excludes buff/cache)
|
||||
|
||||
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.
|
||||
For PVE 8 only, use `VM.Monitor` instead of the `VM.GuestAgent.*` privileges.
|
||||
|
||||
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.
|
||||
|
||||
**Rocky Linux / RHEL VMs**: The default qemu-guest-agent configuration may block file-read RPCs (`guest-file-open`, `guest-file-read`, `guest-file-close`). If memory or disk data is missing for these VMs, check `/etc/sysconfig/qemu-ga` and ensure those operations are not blocked, then restart the agent. Refer to your distro's qemu-guest-agent documentation for the exact config syntax.
|
||||
|
||||
|
|
|
|||
|
|
@ -97,6 +97,11 @@ that binary, not separate customer-facing agent products.
|
|||
|
||||
## Shared Boundaries
|
||||
|
||||
PVE node setup shared boundaries that render or copy `PulseMonitor`
|
||||
permissions must treat `VM.GuestAgent.Audit` plus `VM.GuestAgent.FileRead` as
|
||||
the PVE 9+ primary contract, with `VM.Monitor` retained only as the legacy PVE
|
||||
8 fallback when guest-agent privileges are unavailable.
|
||||
|
||||
`internal/dockeragent/` is lifecycle-adjacent for agent binary/update trust
|
||||
and owns the Docker / Podman collection module used by `pulse-agent`, but
|
||||
Docker runtime capability truth is monitoring-owned. Lifecycle consumers must
|
||||
|
|
@ -3179,7 +3184,10 @@ presentation in the extracted node setup surface
|
|||
`useNodeModalState.ts`): the copied PVE permission snippet must stay
|
||||
aligned with the canonical backend setup script, including comma-joined
|
||||
privilege transport and non-destructive `PulseMonitor` role updates, instead
|
||||
of shipping a stale local fork.
|
||||
of shipping a stale local fork. PVE 9+ guest-agent privileges
|
||||
`VM.GuestAgent.Audit` and `VM.GuestAgent.FileRead` are the primary setup
|
||||
contract; legacy `VM.Monitor` may appear only as the PVE 8 fallback when the
|
||||
guest-agent privilege probe is unavailable.
|
||||
That same node setup modal owner must also route Proxmox agent-install command
|
||||
generation through the canonical `NodesAPI.getAgentInstallCommand` client for
|
||||
both PVE and PBS, instead of mixing client-mediated and ad hoc raw POST
|
||||
|
|
|
|||
|
|
@ -136,6 +136,12 @@ product API routes free of maintainer commercial analytics.
|
|||
|
||||
## Shared Boundaries
|
||||
|
||||
PVE setup API consumers, generated scripts, runtime setup, installer setup, and
|
||||
browser manual guidance must share one `PulseMonitor` privilege contract:
|
||||
prefer `VM.GuestAgent.Audit` plus `VM.GuestAgent.FileRead` on PVE 9+, and use
|
||||
legacy `VM.Monitor` only as the PVE 8 fallback when guest-agent privileges are
|
||||
unavailable.
|
||||
|
||||
Mobile onboarding QR/deep-link payload readiness is a shared backend/frontend
|
||||
contract. `internal/api/onboarding_handlers.go` owns the `409
|
||||
onboarding_not_ready` diagnostic response when Remote Access, relay
|
||||
|
|
@ -1440,7 +1446,11 @@ payload shape change when the portal presents compact client rows.
|
|||
`PVEDatastoreAdmin` ACLs to both `pulse-monitor@pve` and the concrete token
|
||||
id. PBS scripts must grant `Audit` to both `pulse-monitor@pbs` and the
|
||||
concrete token id. Browser/runtime setup callers must not fork this into
|
||||
token-shared, token-unprivileged, or user-only ACL variants.
|
||||
token-shared, token-unprivileged, or user-only ACL variants. For PVE,
|
||||
`PulseMonitor` privilege selection must prefer `VM.GuestAgent.Audit` plus
|
||||
`VM.GuestAgent.FileRead` on PVE 9+ and reserve `VM.Monitor` for the legacy
|
||||
PVE 8 fallback, so API-generated scripts, runtime setup, installer setup,
|
||||
and browser manual guidance stay on one privilege contract.
|
||||
67. `internal/api/enterprise_extension_rbac_admin.go` shared with `organization-settings`: RBAC admin extension endpoints are both an organization settings control surface and a canonical API payload contract boundary.
|
||||
68. `internal/api/licensing_bridge.go` shared with `cloud-paid`: commercial licensing bridge handlers carry both API payload contract and cloud-paid entitlement boundary ownership.
|
||||
69. `internal/api/licensing_handlers.go` shared with `cloud-paid`: commercial licensing handlers carry both API payload contract and cloud-paid entitlement boundary ownership.
|
||||
|
|
|
|||
|
|
@ -1733,15 +1733,19 @@ also validate the returned canonical `type`, normalized `host`, and live
|
|||
registration cannot drift onto a stale or mismatched bootstrap response.
|
||||
Install-time PVE auto-registration must also create privilege-separated
|
||||
Pulse-managed monitor tokens and mirror effective ACLs to the concrete token id
|
||||
rather than relying on user-only grants or shared-token inheritance. That same
|
||||
install-time token creation must extract the token secret deterministically: it
|
||||
must request the machine-readable `pveum ... --output-format json` form first
|
||||
and parse the `value` field, falling back to the legacy box-drawing table
|
||||
layout only when an older pveum rejects the JSON flag — matching the hardened
|
||||
web-setup render path (`internal/api/setup_script_render.go`) so token capture
|
||||
does not silently fail or mis-parse when pveum's table formatting drifts across
|
||||
versions/locales. `scripts/installtests/root_install_sh_test.go` is the owned
|
||||
proof surface for that install-time extraction. A
|
||||
rather than relying on user-only grants or shared-token inheritance. Its
|
||||
`PulseMonitor` role setup must prefer `VM.GuestAgent.Audit` plus
|
||||
`VM.GuestAgent.FileRead` when those PVE 9+ privileges are available, and fall
|
||||
back to legacy `VM.Monitor` only when the guest-agent privilege probe is
|
||||
unavailable. That same install-time token creation must extract the token
|
||||
secret deterministically: it must request the machine-readable
|
||||
`pveum ... --output-format json` form first and parse the `value` field,
|
||||
falling back to the legacy box-drawing table layout only when an older pveum
|
||||
rejects the JSON flag — matching the hardened web-setup render path
|
||||
(`internal/api/setup_script_render.go`) so token capture does not silently fail
|
||||
or mis-parse when pveum's table formatting drifts across versions/locales.
|
||||
`scripts/installtests/root_install_sh_test.go` is the owned proof surface for
|
||||
that install-time extraction. A
|
||||
non-empty `expires` field alone is not sufficient; the installer must reject
|
||||
bootstrap responses whose expiry is already in the past. That same bootstrap
|
||||
consumer must also fail closed unless the runtime-owned setup metadata is
|
||||
|
|
|
|||
|
|
@ -556,8 +556,8 @@ export const NodeModalSetupGuideSection: Component<NodeModalSetupGuideSectionPro
|
|||
</div>
|
||||
<p class="text-muted text-xs mt-1">
|
||||
Note: PVEAuditor gives read-only API access. PulseMonitor adds Sys.Audit
|
||||
plus either VM.Monitor (PVE 8) or VM.GuestAgent.Audit (PVE 9+) for disk and
|
||||
guest metrics. PVEDatastoreAdmin on /storage adds backup visibility.
|
||||
plus VM.GuestAgent.Audit/FileRead (PVE 9+) or VM.Monitor (PVE 8) for disk
|
||||
and guest metrics. PVEDatastoreAdmin on /storage adds backup visibility.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { PVE_MANUAL_PERMISSION_COMMAND } from '../nodeModalModel';
|
||||
|
||||
describe('nodeModalModel', () => {
|
||||
it('prefers PVE 9 guest-agent privileges before legacy VM.Monitor', () => {
|
||||
expect(PVE_MANUAL_PERMISSION_COMMAND).toContain('VM.GuestAgent.Audit');
|
||||
expect(PVE_MANUAL_PERMISSION_COMMAND).toContain('VM.GuestAgent.FileRead');
|
||||
expect(PVE_MANUAL_PERMISSION_COMMAND).toContain('VM.Monitor');
|
||||
|
||||
const guestBranch = PVE_MANUAL_PERMISSION_COMMAND.indexOf(
|
||||
'if [ "$HAS_GUEST_AGENT_AUDIT" = true ]; then',
|
||||
);
|
||||
const monitorBranch = PVE_MANUAL_PERMISSION_COMMAND.indexOf(
|
||||
'pveum role add PulseTmpVMMonitor -privs VM.Monitor',
|
||||
);
|
||||
|
||||
expect(guestBranch).toBeGreaterThanOrEqual(0);
|
||||
expect(monitorBranch).toBeGreaterThanOrEqual(0);
|
||||
expect(guestBranch).toBeLessThan(monitorBranch);
|
||||
});
|
||||
});
|
||||
|
|
@ -64,6 +64,7 @@ import nodeModalAuthenticationSectionSource from '../NodeModalAuthenticationSect
|
|||
import nodeModalMonitoringSectionSource from '../NodeModalMonitoringSection.tsx?raw';
|
||||
import nodeModalSetupGuideSectionSource from '../NodeModalSetupGuideSection.tsx?raw';
|
||||
import nodeModalStatusFooterSource from '../NodeModalStatusFooter.tsx?raw';
|
||||
import nodeModalModelSource from '../nodeModalModel.ts?raw';
|
||||
import nodeModalStateSource from '../useNodeModalState.ts?raw';
|
||||
import availabilityTargetSlotSource from '../ConnectionEditor/CredentialSlots/AvailabilityTargetSlot.tsx?raw';
|
||||
import trueNASCredentialSlotSource from '../ConnectionEditor/CredentialSlots/TrueNASCredentialSlot.tsx?raw';
|
||||
|
|
@ -1291,6 +1292,19 @@ describe('settings architecture guardrails', () => {
|
|||
expect(nodeModalSetupGuideSectionSource).toContain('Existing source repair');
|
||||
expect(nodeModalSetupGuideSectionSource).toContain('Audit/Repair');
|
||||
expect(nodeModalSetupGuideSectionSource).toContain('without rotating the current API token');
|
||||
expect(nodeModalSetupGuideSectionSource).toContain('VM.GuestAgent.Audit/FileRead');
|
||||
expect(nodeModalModelSource).toContain('HAS_GUEST_AGENT_AUDIT');
|
||||
expect(nodeModalModelSource).toContain('VM.GuestAgent.FileRead');
|
||||
expect(nodeModalModelSource).toContain('PulseTmpVMMonitor');
|
||||
const guestAgentBranch = nodeModalModelSource.indexOf(
|
||||
'if [ "$HAS_GUEST_AGENT_AUDIT" = true ]; then',
|
||||
);
|
||||
const monitorFallback = nodeModalModelSource.indexOf(
|
||||
'pveum role add PulseTmpVMMonitor -privs VM.Monitor',
|
||||
);
|
||||
expect(guestAgentBranch).toBeGreaterThanOrEqual(0);
|
||||
expect(monitorFallback).toBeGreaterThanOrEqual(0);
|
||||
expect(guestAgentBranch).toBeLessThan(monitorFallback);
|
||||
expect(nodeModalSetupGuideSectionSource).toContain('Docker inside Proxmox LXCs');
|
||||
expect(nodeModalSetupGuideSectionSource).toContain(
|
||||
'PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY=true',
|
||||
|
|
|
|||
|
|
@ -56,24 +56,31 @@ else
|
|||
fi
|
||||
fi
|
||||
|
||||
# VM guest agent / monitor privileges
|
||||
VM_PRIV=""
|
||||
if pveum role list 2>/dev/null | grep -q "VM.Monitor"; then
|
||||
VM_PRIV="VM.Monitor"
|
||||
elif pveum role list 2>/dev/null | grep -q "VM.GuestAgent.Audit"; then
|
||||
VM_PRIV="VM.GuestAgent.Audit"
|
||||
else
|
||||
if pveum role add PulseTmpVMMonitor -privs VM.Monitor 2>/dev/null; then
|
||||
VM_PRIV="VM.Monitor"
|
||||
pveum role delete PulseTmpVMMonitor 2>/dev/null
|
||||
elif pveum role add PulseTmpGuestAudit -privs VM.GuestAgent.Audit 2>/dev/null; then
|
||||
VM_PRIV="VM.GuestAgent.Audit"
|
||||
pveum role delete PulseTmpGuestAudit 2>/dev/null
|
||||
fi
|
||||
# VM guest agent (PVE 9+) / monitor (PVE 8) privileges
|
||||
HAS_GUEST_AGENT_AUDIT=false
|
||||
if pveum role list 2>/dev/null | grep -q "VM.GuestAgent.Audit"; then
|
||||
HAS_GUEST_AGENT_AUDIT=true
|
||||
elif pveum role add PulseTmpGuestAudit -privs VM.GuestAgent.Audit 2>/dev/null; then
|
||||
HAS_GUEST_AGENT_AUDIT=true
|
||||
pveum role delete PulseTmpGuestAudit 2>/dev/null
|
||||
fi
|
||||
|
||||
if [ -n "$VM_PRIV" ]; then
|
||||
EXTRA_PRIVS+=("$VM_PRIV")
|
||||
if [ "$HAS_GUEST_AGENT_AUDIT" = true ]; then
|
||||
EXTRA_PRIVS+=("VM.GuestAgent.Audit")
|
||||
|
||||
if pveum role list 2>/dev/null | grep -q "VM.GuestAgent.FileRead"; then
|
||||
EXTRA_PRIVS+=("VM.GuestAgent.FileRead")
|
||||
elif pveum role add PulseTmpGuestFileRead -privs VM.GuestAgent.FileRead 2>/dev/null; then
|
||||
EXTRA_PRIVS+=("VM.GuestAgent.FileRead")
|
||||
pveum role delete PulseTmpGuestFileRead 2>/dev/null
|
||||
fi
|
||||
else
|
||||
if pveum role list 2>/dev/null | grep -q "VM.Monitor"; then
|
||||
EXTRA_PRIVS+=("VM.Monitor")
|
||||
elif pveum role add PulseTmpVMMonitor -privs VM.Monitor 2>/dev/null; then
|
||||
EXTRA_PRIVS+=("VM.Monitor")
|
||||
pveum role delete PulseTmpVMMonitor 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ \${#EXTRA_PRIVS[@]} -gt 0 ]; then
|
||||
|
|
|
|||
29
install.sh
29
install.sh
|
|
@ -2314,14 +2314,6 @@ PY
|
|||
pveum role delete PulseSysAuditProbe >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
local has_vm_monitor=false
|
||||
if pveum role list 2>/dev/null | grep -q "VM.Monitor"; then
|
||||
has_vm_monitor=true
|
||||
elif pveum role add PulseVmMonitorProbe -privs VM.Monitor >/dev/null 2>&1; then
|
||||
has_vm_monitor=true
|
||||
pveum role delete PulseVmMonitorProbe >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
local has_guest_audit=false
|
||||
if pveum role list 2>/dev/null | grep -q "VM.GuestAgent.Audit"; then
|
||||
has_guest_audit=true
|
||||
|
|
@ -2330,10 +2322,25 @@ PY
|
|||
pveum role delete PulseGuestAuditProbe >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
if [[ "$has_vm_monitor" == true ]]; then
|
||||
extra_privs+=("VM.Monitor")
|
||||
elif [[ "$has_guest_audit" == true ]]; then
|
||||
if [[ "$has_guest_audit" == true ]]; then
|
||||
extra_privs+=("VM.GuestAgent.Audit")
|
||||
if pveum role list 2>/dev/null | grep -q "VM.GuestAgent.FileRead"; then
|
||||
extra_privs+=("VM.GuestAgent.FileRead")
|
||||
elif pveum role add PulseGuestFileReadProbe -privs VM.GuestAgent.FileRead >/dev/null 2>&1; then
|
||||
extra_privs+=("VM.GuestAgent.FileRead")
|
||||
pveum role delete PulseGuestFileReadProbe >/dev/null 2>&1 || true
|
||||
fi
|
||||
else
|
||||
local has_vm_monitor=false
|
||||
if pveum role list 2>/dev/null | grep -q "VM.Monitor"; then
|
||||
has_vm_monitor=true
|
||||
elif pveum role add PulseVmMonitorProbe -privs VM.Monitor >/dev/null 2>&1; then
|
||||
has_vm_monitor=true
|
||||
pveum role delete PulseVmMonitorProbe >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [[ "$has_vm_monitor" == true ]]; then
|
||||
extra_privs+=("VM.Monitor")
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ${#extra_privs[@]} -gt 0 ]]; then
|
||||
|
|
|
|||
|
|
@ -345,6 +345,10 @@ func TestRootInstallScriptAutoRegisterUsesSecureContractShape(t *testing.T) {
|
|||
`pveum aclmod /storage -token "$token_id" -role PVEDatastoreAdmin`,
|
||||
`priv_string="$(IFS=,; echo "${extra_privs[*]}")"`,
|
||||
`pveum role modify PulseMonitor -privs "$priv_string"`,
|
||||
`pveum role add PulseGuestFileReadProbe -privs VM.GuestAgent.FileRead`,
|
||||
`extra_privs+=("VM.GuestAgent.Audit")`,
|
||||
`extra_privs+=("VM.GuestAgent.FileRead")`,
|
||||
`extra_privs+=("VM.Monitor")`,
|
||||
`slug = re.sub(r"[^a-z0-9]+", "-", host)`,
|
||||
`print(f"pulse-{slug}")`,
|
||||
}
|
||||
|
|
@ -356,6 +360,11 @@ func TestRootInstallScriptAutoRegisterUsesSecureContractShape(t *testing.T) {
|
|||
if strings.Contains(script, `local token_name="pulse-${pulse_host_slug}-$(date +%s)"`) {
|
||||
t.Fatalf("root install.sh preserved stale timestamp-suffixed Proxmox token naming")
|
||||
}
|
||||
guestBranch := strings.Index(script, `if [[ "$has_guest_audit" == true ]]; then`)
|
||||
monitorBranch := strings.Index(script, `if [[ "$has_vm_monitor" == true ]]; then`)
|
||||
if guestBranch < 0 || monitorBranch < 0 || guestBranch > monitorBranch {
|
||||
t.Fatalf("root install.sh must prefer VM.GuestAgent.* privileges before legacy VM.Monitor")
|
||||
}
|
||||
forbidden := []string{
|
||||
`local bootstrap_token=""`,
|
||||
`X-Setup-Token: $bootstrap_token`,
|
||||
|
|
|
|||
|
|
@ -2917,7 +2917,7 @@ class SubsystemLookupTest(unittest.TestCase):
|
|||
{
|
||||
"heading": "## Shared Boundaries",
|
||||
"path": "internal/api/access_control_handlers.go",
|
||||
"line": 1146,
|
||||
"line": 1152,
|
||||
"heading_line": 137,
|
||||
}
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue