hub v0.135.0: CSRF on the Basic-auth path (R-135), console passwords sealed at rest (R-133), boxes left behind listed and alarmed (R-604, R-530), no-e-mail banner (R-508)
gates / gates (push) Successful in 29s
gates / gates (push) Successful in 29s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -8,6 +8,8 @@ import (
|
||||
"time"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/osupdates"
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/semver"
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/sysfacts"
|
||||
)
|
||||
|
||||
@@ -35,6 +37,7 @@ type systemRow struct {
|
||||
Held, RebootSince, KernelPanic, Oops cell
|
||||
CrashRestarts24h, Guard cell
|
||||
Bundle cell // R-840: the root-owned config bundle
|
||||
Agent cell // R-530: the box's agent against the vouched one
|
||||
// guest
|
||||
GuestDebian, GuestRelease, GuestPending, GuestRestart cell
|
||||
// docker
|
||||
@@ -51,9 +54,62 @@ type OSSystemView interface {
|
||||
Candidates() []osupdates.Status
|
||||
Thresholds() (stale, reboot, notCovered time.Duration)
|
||||
BundleThreshold() time.Duration
|
||||
AgentThreshold() time.Duration
|
||||
ApproveDocker() (string, error)
|
||||
}
|
||||
|
||||
// agentCell is the "Agent" cell (R-530, hub v0.135.0): the box's agent against the vouched one, how far behind and
|
||||
// since when. Amber while behind; red from the alarm's wait on (the operator alarm fires then). An unreadable
|
||||
// version is "unknown", never a guess; nothing vouched → the version alone.
|
||||
func agentCell(boxAgent, vouched string, since time.Time, after time.Duration, now time.Time) cell {
|
||||
if !semver.Valid(boxAgent) {
|
||||
return unknownCell("")
|
||||
}
|
||||
c := cell{Text: boxAgent}
|
||||
if !semver.Valid(vouched) {
|
||||
c.Title = "no vouched agent to compare with"
|
||||
return c
|
||||
}
|
||||
if semver.Compare(boxAgent, vouched) >= 0 {
|
||||
c.Title = "current (vouched " + vouched + ")"
|
||||
return c
|
||||
}
|
||||
c.Class = "warn"
|
||||
c.Text = boxAgent + " → " + vouched
|
||||
c.Title = osupdates.ReleasesBehind(boxAgent, vouched) + " — sign an agent_update for this box"
|
||||
if !since.IsZero() {
|
||||
c.Text += " (since " + since.UTC().Format("2006-01-02") + ")"
|
||||
if now.Sub(since) >= after {
|
||||
c.Class = "bad"
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// floorRow is one line of the System page's "Version floors" table (R-604).
|
||||
type floorRow struct {
|
||||
CustomerID, CustomerName, Version string
|
||||
Age cell
|
||||
HeldBack bool // the override is BELOW the global floor: the global does not move this box
|
||||
}
|
||||
|
||||
func buildFloorRows(ovs []store.CustomerFloorOverride, global string, now time.Time) []floorRow {
|
||||
var out []floorRow
|
||||
for _, o := range ovs {
|
||||
r := floorRow{CustomerID: o.CustomerID, CustomerName: o.CustomerName, Version: o.Version}
|
||||
if o.SetAt.IsZero() {
|
||||
r.Age = cell{Text: "unknown", Class: "warn", Title: "set before hub v0.135.0 — the hub did not record when"}
|
||||
} else {
|
||||
r.Age = plain(ago(o.SetAt, now) + " (" + o.SetAt.UTC().Format("2006-01-02") + ")")
|
||||
}
|
||||
if semver.Valid(global) && semver.Valid(o.Version) && semver.Compare(global, o.Version) > 0 {
|
||||
r.HeldBack = true
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func plain(s string) cell { return cell{Text: s} }
|
||||
|
||||
// bundleCell is the "Root files" cell (R-840): the box's config bundle against the vouched agent's. Amber while behind,
|
||||
@@ -240,12 +296,26 @@ func (s *Server) handleSystem(w http.ResponseWriter, r *http.Request) {
|
||||
stale, reboot, notCov := view.Thresholds()
|
||||
rows := buildSystemRows(lines, facts, names, stale, reboot, notCov, time.Now())
|
||||
man := s.store.GetArtifactManifest()
|
||||
agents := map[string]string{}
|
||||
for _, h := range hosts {
|
||||
agents[h.HostID] = h.AgentVersion
|
||||
}
|
||||
for i := range rows {
|
||||
rows[i].Bundle = bundleCell(facts[rows[i].HostID], man.AgentVersion, man.BundleSHA256,
|
||||
s.store.BundleBehindSince(rows[i].HostID), view.BundleThreshold(), time.Now())
|
||||
rows[i].Agent = agentCell(agents[rows[i].HostID], man.AgentVersion,
|
||||
s.store.AgentBehindSince(rows[i].HostID), view.AgentThreshold(), time.Now())
|
||||
}
|
||||
global := s.store.GetGlobalMinControllerVersion()
|
||||
ovs, oerr := s.store.CustomerFloorOverrides()
|
||||
if oerr != nil {
|
||||
s.logger.Printf("[ERROR] system page: floor overrides: %v", oerr)
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"Rows": rows,
|
||||
"GlobalFloor": global,
|
||||
"VouchedAgent": man.AgentVersion,
|
||||
"Floors": buildFloorRows(ovs, global, time.Now()),
|
||||
"Releases": view.Releases(),
|
||||
"Cancelled": view.CancelledReleases(),
|
||||
"Candidates": view.Candidates(),
|
||||
|
||||
Reference in New Issue
Block a user