hub: HostLeafChecker — proactive agent re-key detection (host_leaf_changed) v0.22.0
Watches each host's reported local-API leaf fp; alerts on change (trust-on-first-report). Sibling of HostCapabilityChecker; store.GetHostLeafFingerprints reads report_json (no migration); hub-generated event (no allowlist change). Change red-proof + first-obs-seed + empty-skip + blocked-drop tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pg8ANF97SEeKYSN5Jxw3qJ
This commit is contained in:
@@ -1673,3 +1673,41 @@ func (s *Store) GetHostCapabilities() ([]HostCapabilityRow, error) {
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// HostLeafRow is the per-host served-leaf-fingerprint the HostLeafChecker reads — extracted from the
|
||||
// latest host-report's report_json (no dedicated column; the fp rides the report body, same as the
|
||||
// capabilities snapshot). LeafFP is "" for a pre-v0.48.0 agent / local-API-disabled host.
|
||||
type HostLeafRow struct {
|
||||
HostID string
|
||||
CustomerID string
|
||||
LeafFP string
|
||||
}
|
||||
|
||||
// GetHostLeafFingerprints returns the latest reported local-API leaf fp per host (mirrors
|
||||
// GetHostCapabilities — MAX(id) per host, parsed from report_json so there is no schema migration).
|
||||
func (s *Store) GetHostLeafFingerprints() ([]HostLeafRow, error) {
|
||||
rows, err := s.db.Query(`
|
||||
SELECT hr.host_id, hr.customer_id, hr.report_json
|
||||
FROM host_reports hr
|
||||
JOIN (SELECT host_id, MAX(id) AS mx FROM host_reports GROUP BY host_id) latest
|
||||
ON hr.id = latest.mx`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []HostLeafRow
|
||||
for rows.Next() {
|
||||
var r HostLeafRow
|
||||
var reportJSON string
|
||||
if err := rows.Scan(&r.HostID, &r.CustomerID, &reportJSON); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var body struct {
|
||||
LeafFingerprint string `json:"leaf_fingerprint"`
|
||||
}
|
||||
_ = json.Unmarshal([]byte(reportJSON), &body) // malformed/old body → "" → no alert
|
||||
r.LeafFP = body.LeafFingerprint
|
||||
out = append(out, r)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user