Files
pulse/internal/hostagent/privilege.go
T
rcourtman 4686efd8c8 Add a supported least-privilege agent install profile
The unified agent's Linux installer only offered the root profile, and the
docs called non-root unsupported. That default is the most-cited reason
security-conscious evaluators reject Pulse without trying it. install.sh
gains --least-privilege: the service runs as a dedicated nologin
pulse-agent system user with every existing hardening directive, no
LXC-attach ambient capabilities, docker-group membership for socket reads,
and refusal (not silent root fallback) on appliance platforms, non-systemd
init systems, and --enable-commands. Optional --grant-smart and --grant-pct
restore the only two collectors that need elevation through
visudo-validated exact-command sudoers rules and root-owned wrappers the
agent reaches via new absolute-path-only PULSE_SMARTCTL_PATH /
PULSE_PCT_PATH overrides; the pct grant covers pct list and pct df only
and can never widen into pct exec. --update preserves the profile and its
grants by reading the installed unit.

The agent now authors a privilege block in its report (effective root,
service user, active helpers), carried through models into the fleet
doctor as a descriptive field: Agent Doctor shows the profile and its
helpers instead of presenting intentionally absent collectors as a fault,
and a least-privilege agent can never be marked unhealthy on that evidence
alone.
2026-08-20 20:49:44 +01:00

28 lines
1014 B
Go

package hostagent
import (
"os"
"os/user"
"strings"
agentshost "github.com/rcourtman/pulse-go-rewrite/pkg/agents/host"
)
// collectPrivilegeStatus reports the privilege the agent actually runs with.
// The values are facts about this process, not configuration: effective uid,
// the service account name, and whether the scoped privilege-helper overrides
// a least-privilege install configures are in effect. On Windows Geteuid is
// always -1, so RunningAsRoot stays false and ServiceUser carries the account
// name; the server renders the profile descriptively rather than judging it.
func collectPrivilegeStatus() *agentshost.PrivilegeStatus {
status := &agentshost.PrivilegeStatus{
RunningAsRoot: os.Geteuid() == 0,
SmartctlHelper: strings.TrimSpace(os.Getenv("PULSE_SMARTCTL_PATH")) != "",
PctHelper: strings.TrimSpace(os.Getenv("PULSE_PCT_PATH")) != "",
}
if current, err := user.Current(); err == nil {
status.ServiceUser = strings.TrimSpace(current.Username)
}
return status
}