package server import ( "net/http" "sync" "time" "housecarl/internal/version" ) // fallbackInForce is what status and /healthz say while the boot fallback // (render.Fallback) is in force. const fallbackInForce = "the boot fallback is in force, not Housecarl's ruleset: the saved ruleset failed to load at boot (journalctl -u housecarl-ruleset), and housecarld couldn't apply it since; it lets nothing new in from external zones (with none, nothing but loopback)" // health is GET /healthz, for monitoring (Checkmk, a load balancer). It // needs no authentication, so it says only what a monitor needs, and reads // the ruleset at most every few seconds however often it is asked. type health struct { OK bool `json:"ok"` Version string `json:"version"` Firewall string `json:"firewall"` // applied, unconfigured, fallback, missing // Mode is enforcing, or shadow: the ruleset blocks nothing. Mode string `json:"mode"` // Probation: changes waiting to be confirmed, rolled back otherwise. Probation int `json:"probation"` // Warnings are the codes of the machine's problems (nftables_service). Warnings []string `json:"warnings"` } const healthTTL = 5 * time.Second type healthCache struct { mu sync.Mutex at time.Time out health } func (s *Server) healthz(w http.ResponseWriter, r *http.Request) { c := &s.health c.mu.Lock() if time.Since(c.at) >= healthTTL { c.out, c.at = s.checkHealth(r), time.Now() } out := c.out c.mu.Unlock() code := http.StatusOK if !out.OK { code = http.StatusServiceUnavailable } writeJSON(w, code, out) } func (s *Server) checkHealth(r *http.Request) health { out := health{Version: version.String(), Warnings: []string{}, Mode: s.mode()} t, err := s.fw.Table(r.Context()) switch { case err != nil && s.eng.State().Empty(): out.Firewall, out.OK = "unconfigured", true case err != nil: out.Firewall = "missing" case t.Fallback: out.Firewall = "fallback" default: out.Firewall, out.OK = "applied", true } pr := s.probationStatus() out.Probation = len(pr.Pending) + pr.Takeovers // The other tables matter in shadow mode (shadow_unprotected). var ruleset []byte if s.cfg.Shadow { ruleset, _ = s.fw.ListRuleset(r.Context()) } for _, p := range s.hostProblems(r.Context(), ruleset) { out.Warnings = append(out.Warnings, p.Code) } return out }