source: advance the checked projection

Source-Sha: 194a59adfd6c901635da7a9c18253bb2210e2426
Policy-Sha: b9b0be0ee74e55f561a803b8aef6be3c2134f5a83b46816a069a372f2eb04f4d
Tree-Digest: 4697f2e281c1c6ecafff17cf6eeb391a0a1ac0fa6e5fc429f5b473f4579c252c
This commit is contained in:
Source publisher 2026-09-21 14:39:08 +00:00
commit ffdccba10f
42 changed files with 2516 additions and 554 deletions

View file

@ -40,6 +40,10 @@ var (
ErrApprovalConflict = errors.New("localapi: approval conflict")
// ErrApprovalUnavailable → 503: no local authority on this host.
ErrApprovalUnavailable = errors.New("localapi: approval unavailable")
// ErrFleetJoinConflict maps mode, validation, and in-flight joins to 409.
ErrFleetJoinConflict = errors.New("localapi: fleet join conflict")
// ErrFleetJoinUnavailable maps a missing platform/helper path to 503.
ErrFleetJoinUnavailable = errors.New("localapi: fleet join unavailable")
)
// Options configures the local read-only API. Group, socket, and pipe defaults
@ -75,6 +79,11 @@ type Options struct {
// Nil disables the endpoint (503). Wrap errors in ErrApprovalConflict /
// ErrApprovalUnavailable to control the HTTP status.
ApproveUpdate func(body []byte) (interface{}, error)
// PrepareFleetJoin creates the host-held proof seed. JoinFleet consumes the
// Server URL, one-seat join token, and that seed through the one-way helper
// transition. Both are disabled outside standalone mode.
PrepareFleetJoin func() (interface{}, error)
JoinFleet func(body []byte) (interface{}, error)
// OnDesktopHealth receives each Desktop self-report (POST /v1/desktop) so the
// agent can track app liveness/version — on Linux Desktop is autostart-
// launched and this is the only signal. Nil means reports are logged only.
@ -159,24 +168,26 @@ func (s *Server) Stop() {
}
type handler struct {
cfg *config.Config
loadCache func() (*cache.LocalCache, error)
desktop DesktopStatusProvider
triggerScan func(source string) error
approveUpdate func(body []byte) (interface{}, error)
onDesktopHealth func(version string, windowOpen bool)
systemInfo func() (*system.SystemInfo, error)
topProcesses func(limit int) ([]system.TopProcess, error)
monitorSnapshot func() (*system.ResourceSnapshot, error)
processes func() (*system.FullProcessSnapshot, error)
processDetail func(pid int, caps system.ProcessCaps) (*system.FullProcess, error)
software func() (*system.SoftwareSnapshot, error)
packageDetail func(packageType, identity string) (*system.PackageDetail, error)
connections func() (*system.ConnectionSnapshot, error)
services func() (*system.ServiceSnapshot, error)
docker func() (*DockerResponse, error)
events func() ([]*models.SystemEvent, error)
scanRunning func() bool
cfg *config.Config
loadCache func() (*cache.LocalCache, error)
desktop DesktopStatusProvider
triggerScan func(source string) error
approveUpdate func(body []byte) (interface{}, error)
prepareFleetJoin func() (interface{}, error)
joinFleet func(body []byte) (interface{}, error)
onDesktopHealth func(version string, windowOpen bool)
systemInfo func() (*system.SystemInfo, error)
topProcesses func(limit int) ([]system.TopProcess, error)
monitorSnapshot func() (*system.ResourceSnapshot, error)
processes func() (*system.FullProcessSnapshot, error)
processDetail func(pid int, caps system.ProcessCaps) (*system.FullProcess, error)
software func() (*system.SoftwareSnapshot, error)
packageDetail func(packageType, identity string) (*system.PackageDetail, error)
connections func() (*system.ConnectionSnapshot, error)
services func() (*system.ServiceSnapshot, error)
docker func() (*DockerResponse, error)
events func() ([]*models.SystemEvent, error)
scanRunning func() bool
}
// DesktopStatusProvider allows the desktop manager to report its status.
@ -196,6 +207,7 @@ type IdentityResponse struct {
ConfigVersion string `json:"config_version,omitempty"`
CheckInInterval int `json:"check_in_interval"`
Registered bool `json:"registered"`
Mode string `json:"mode"`
}
type StatusResponse struct {
@ -277,24 +289,26 @@ type SecurityResponse struct {
func newHandler(opts Options) http.Handler {
h := &handler{
cfg: opts.Config,
loadCache: opts.LoadCache,
desktop: opts.DesktopProvider,
triggerScan: opts.TriggerScan,
approveUpdate: opts.ApproveUpdate,
onDesktopHealth: opts.OnDesktopHealth,
systemInfo: opts.SystemProvider,
topProcesses: opts.ProcessProvider,
monitorSnapshot: opts.MonitorProvider,
processes: opts.ProcessesProvider,
processDetail: opts.ProcessDetailProvider,
software: opts.SoftwareProvider,
packageDetail: opts.PackageDetailProvider,
connections: opts.ConnectionsProvider,
services: opts.ServicesProvider,
docker: opts.DockerProvider,
events: opts.EventsProvider,
scanRunning: opts.ScanRunning,
cfg: opts.Config,
loadCache: opts.LoadCache,
desktop: opts.DesktopProvider,
triggerScan: opts.TriggerScan,
approveUpdate: opts.ApproveUpdate,
prepareFleetJoin: opts.PrepareFleetJoin,
joinFleet: opts.JoinFleet,
onDesktopHealth: opts.OnDesktopHealth,
systemInfo: opts.SystemProvider,
topProcesses: opts.ProcessProvider,
monitorSnapshot: opts.MonitorProvider,
processes: opts.ProcessesProvider,
processDetail: opts.ProcessDetailProvider,
software: opts.SoftwareProvider,
packageDetail: opts.PackageDetailProvider,
connections: opts.ConnectionsProvider,
services: opts.ServicesProvider,
docker: opts.DockerProvider,
events: opts.EventsProvider,
scanRunning: opts.ScanRunning,
}
if h.systemInfo == nil {
h.systemInfo = func() (*system.SystemInfo, error) {
@ -345,6 +359,8 @@ func newHandler(opts Options) http.Handler {
mux.HandleFunc("/v1/desktop", h.desktopHealth)
mux.HandleFunc("/v1/actions/trigger-scan", h.triggerScanAction)
mux.HandleFunc("/v1/actions/approve-update", h.approveUpdateAction)
mux.HandleFunc("/v1/actions/prepare-fleet-join", h.prepareFleetJoinAction)
mux.HandleFunc("/v1/actions/join-fleet", h.joinFleetAction)
return mux
}
@ -566,6 +582,14 @@ func (h *handler) identity(w http.ResponseWriter, r *http.Request) {
log.Printf("[WARNING] [agent] [localapi] hostname_failed error=%v", err)
}
mode := "incomplete"
if h.cfg.IsRegistered() {
mode = "fleet"
} else if h.cfg.IsStandalone() {
mode = "standalone"
} else if h.cfg.PendingFleetJoin != nil {
mode = "joining"
}
resp := IdentityResponse{
AgentID: h.cfg.AgentID.String(),
ServerURL: h.cfg.ServerURL,
@ -578,6 +602,7 @@ func (h *handler) identity(w http.ResponseWriter, r *http.Request) {
ConfigVersion: h.cfg.Version,
CheckInInterval: h.cfg.CheckInInterval,
Registered: h.cfg.IsRegistered(),
Mode: mode,
}
writeJSON(w, resp)
}
@ -762,6 +787,69 @@ func (h *handler) approveUpdateAction(w http.ResponseWriter, r *http.Request) {
}
}
func (h *handler) prepareFleetJoinAction(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.Header().Set("Allow", http.MethodPost)
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
if h.prepareFleetJoin == nil {
http.Error(w, "fleet join unavailable", http.StatusServiceUnavailable)
return
}
result, err := h.prepareFleetJoin()
switch {
case err == nil:
writeJSON(w, result)
case errors.Is(err, ErrFleetJoinConflict):
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusConflict)
writeJSONBody(w, map[string]interface{}{"error": err.Error()})
case errors.Is(err, ErrFleetJoinUnavailable):
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusServiceUnavailable)
writeJSONBody(w, map[string]interface{}{"error": err.Error()})
default:
log.Printf("[ERROR] [agent] [localapi] fleet_join_prepare_failed error=%v", err)
http.Error(w, "fleet join preparation failed", http.StatusInternalServerError)
}
}
func (h *handler) joinFleetAction(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.Header().Set("Allow", http.MethodPost)
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
if h.joinFleet == nil {
http.Error(w, "fleet join unavailable", http.StatusServiceUnavailable)
return
}
body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))
if err != nil {
http.Error(w, "request read failed", http.StatusBadRequest)
return
}
result, err := h.joinFleet(body)
switch {
case err == nil:
writeJSON(w, result)
case errors.Is(err, ErrFleetJoinConflict):
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusConflict)
writeJSONBody(w, map[string]interface{}{"error": err.Error()})
case errors.Is(err, ErrFleetJoinUnavailable):
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusServiceUnavailable)
writeJSONBody(w, map[string]interface{}{"error": err.Error()})
default:
log.Printf("[ERROR] [agent] [localapi] fleet_join_failed error=%v", err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadGateway)
writeJSONBody(w, map[string]interface{}{"error": err.Error()})
}
}
func (h *handler) load(w http.ResponseWriter) (*cache.LocalCache, bool) {
localCache, err := h.loadCache()
if err != nil {