feat(checks): wire up TLS certificate expiry monitoring
The ontology declared monitoring [cert-expiry] on the certificate type and a working checkCertExpiry probe existed, but checkdefaults had no cert-expiry builder and no certificate entities were seeded — so certificate expiry, a real failure mode, was invisible. Add a KindCertExpiry builder (dials the cert's hostname on :443 hourly, warns at 30d / crit at 7d) and seed certificate entities for the 20 public *.hubris.network routes plus uses-certificate edges from each ingress route.
This commit is contained in:
@@ -24,13 +24,14 @@ import (
|
|||||||
// check_defs.kind values — one semantic kind can expand to several concrete
|
// check_defs.kind values — one semantic kind can expand to several concrete
|
||||||
// checks (`resource` becomes four ssh-script rows).
|
// checks (`resource` becomes four ssh-script rows).
|
||||||
const (
|
const (
|
||||||
KindPing = "ping"
|
KindPing = "ping"
|
||||||
KindResource = "resource"
|
KindResource = "resource"
|
||||||
KindUpdates = "updates"
|
KindUpdates = "updates"
|
||||||
KindProcess = "process"
|
KindProcess = "process"
|
||||||
KindHTTP = "http"
|
KindHTTP = "http"
|
||||||
KindCapacity = "capacity"
|
KindCapacity = "capacity"
|
||||||
KindBackup = "backup-freshness"
|
KindBackup = "backup-freshness"
|
||||||
|
KindCertExpiry = "cert-expiry"
|
||||||
)
|
)
|
||||||
|
|
||||||
// defaultBackupMaxAge is how long a backup target may go without a new
|
// defaultBackupMaxAge is how long a backup target may go without a new
|
||||||
@@ -246,11 +247,40 @@ func buildKind(kind string, t Target, attrs map[string]any, host, user string, p
|
|||||||
config: map[string]any{"url": url, "max_status": 500},
|
config: map[string]any{"url": url, "max_status": 500},
|
||||||
interval: 60,
|
interval: 60,
|
||||||
}}, ""
|
}}, ""
|
||||||
|
|
||||||
|
case KindCertExpiry:
|
||||||
|
// The host to TLS-dial for the cert. Prefer an explicit `hostname`
|
||||||
|
// attribute, then `cn`, then a dotted name (a certificate's name is
|
||||||
|
// its CN/SAN). Hourly: expiry changes once a day, but a renewal or a
|
||||||
|
// mis-issued cert is worth noticing within the hour.
|
||||||
|
host := certHost(t, attrs)
|
||||||
|
if host == "" {
|
||||||
|
return nil, "no hostname / cn / dotted name to dial for the cert"
|
||||||
|
}
|
||||||
|
return []checkDef{{
|
||||||
|
kind: "cert-expiry",
|
||||||
|
config: map[string]any{"host": host, "warn_days": 30, "crit_days": 7},
|
||||||
|
interval: 3600,
|
||||||
|
}}, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, "no builder for this kind yet"
|
return nil, "no builder for this kind yet"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// certHost works out the hostname to TLS-dial for a certificate's expiry.
|
||||||
|
func certHost(t Target, attrs map[string]any) string {
|
||||||
|
for _, key := range []string{"hostname", "cn", "san"} {
|
||||||
|
if v, ok := attrs[key].(string); ok && v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A dotted name is a hostname (hubris.network, media.hubris.network).
|
||||||
|
if strings.Contains(t.Name, ".") && !strings.Contains(t.Name, " ") {
|
||||||
|
return t.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// writeCheck upserts one check_def and its backing check entity.
|
// writeCheck upserts one check_def and its backing check entity.
|
||||||
//
|
//
|
||||||
// The entity upsert MUST return the row's id. The previous version generated
|
// The entity upsert MUST return the row's id. The previous version generated
|
||||||
|
|||||||
@@ -339,6 +339,30 @@ entities:
|
|||||||
- {slug: "ingress:sab.hubris.network", type: ingress-route, name: sab.hubris.network,
|
- {slug: "ingress:sab.hubris.network", type: ingress-route, name: sab.hubris.network,
|
||||||
attributes: {forward_auth: true}}
|
attributes: {forward_auth: true}}
|
||||||
|
|
||||||
|
# ─── TLS certificates (Caddy-managed, *.hubris.network) ───────────
|
||||||
|
# One entity per public hostname; the cert-expiry checker dials each on
|
||||||
|
# :443 and reads NotAfter. The cn/name is the hostname the checker dials.
|
||||||
|
- {slug: "cert:proxmox.hubris.network", type: certificate, name: proxmox.hubris.network}
|
||||||
|
- {slug: "cert:git.hubris.network", type: certificate, name: git.hubris.network}
|
||||||
|
- {slug: "cert:auth.hubris.network", type: certificate, name: auth.hubris.network}
|
||||||
|
- {slug: "cert:media.hubris.network", type: certificate, name: media.hubris.network}
|
||||||
|
- {slug: "cert:cloud.hubris.network", type: certificate, name: cloud.hubris.network}
|
||||||
|
- {slug: "cert:paperless.hubris.network", type: certificate, name: paperless.hubris.network}
|
||||||
|
- {slug: "cert:matrix.hubris.network", type: certificate, name: matrix.hubris.network}
|
||||||
|
- {slug: "cert:photos.hubris.network", type: certificate, name: photos.hubris.network}
|
||||||
|
- {slug: "cert:artifacto.hubris.network", type: certificate, name: artifacto.hubris.network}
|
||||||
|
- {slug: "cert:trmnl.hubris.network", type: certificate, name: trmnl.hubris.network}
|
||||||
|
- {slug: "cert:zimaos.hubris.network", type: certificate, name: zimaos.hubris.network}
|
||||||
|
- {slug: "cert:teddy.hubris.network", type: certificate, name: teddy.hubris.network}
|
||||||
|
- {slug: "cert:mcp.hubris.network", type: certificate, name: mcp.hubris.network}
|
||||||
|
- {slug: "cert:house.hubris.network", type: certificate, name: house.hubris.network}
|
||||||
|
- {slug: "cert:books.hubris.network", type: certificate, name: books.hubris.network}
|
||||||
|
- {slug: "cert:seanime.hubris.network", type: certificate, name: seanime.hubris.network}
|
||||||
|
- {slug: "cert:roms.hubris.network", type: certificate, name: roms.hubris.network}
|
||||||
|
- {slug: "cert:jellyseerr.hubris.network", type: certificate, name: jellyseerr.hubris.network}
|
||||||
|
- {slug: "cert:qbit.hubris.network", type: certificate, name: qbit.hubris.network}
|
||||||
|
- {slug: "cert:sab.hubris.network", type: certificate, name: sab.hubris.network}
|
||||||
|
|
||||||
# ─── Governance ────────────────────────────────────────────────────
|
# ─── Governance ────────────────────────────────────────────────────
|
||||||
- {slug: "person:dtoro", type: person, name: dtoro,
|
- {slug: "person:dtoro", type: person, name: dtoro,
|
||||||
attributes: {matrix_id: "@dtoro:avispero"}}
|
attributes: {matrix_id: "@dtoro:avispero"}}
|
||||||
@@ -486,6 +510,28 @@ relationships:
|
|||||||
- {source: "ingress:jellyseerr.hubris.network", target: "service:caddy", type: served-by}
|
- {source: "ingress:jellyseerr.hubris.network", target: "service:caddy", type: served-by}
|
||||||
- {source: "ingress:qbit.hubris.network", target: "service:caddy", type: served-by}
|
- {source: "ingress:qbit.hubris.network", target: "service:caddy", type: served-by}
|
||||||
- {source: "ingress:sab.hubris.network", target: "service:caddy", type: served-by}
|
- {source: "ingress:sab.hubris.network", target: "service:caddy", type: served-by}
|
||||||
|
# Each public route is served with its TLS certificate.
|
||||||
|
- {source: "ingress:proxmox.hubris.network", target: "cert:proxmox.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:git.hubris.network", target: "cert:git.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:auth.hubris.network", target: "cert:auth.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:media.hubris.network", target: "cert:media.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:cloud.hubris.network", target: "cert:cloud.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:paperless.hubris.network", target: "cert:paperless.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:matrix.hubris.network", target: "cert:matrix.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:photos.hubris.network", target: "cert:photos.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:artifacto.hubris.network", target: "cert:artifacto.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:trmnl.hubris.network", target: "cert:trmnl.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:zimaos.hubris.network", target: "cert:zimaos.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:teddy.hubris.network", target: "cert:teddy.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:mcp.hubris.network", target: "cert:mcp.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:house.hubris.network", target: "cert:house.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:books.hubris.network", target: "cert:books.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:seanime.hubris.network", target: "cert:seanime.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:roms.hubris.network", target: "cert:roms.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:jellyseerr.hubris.network", target: "cert:jellyseerr.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:qbit.hubris.network", target: "cert:qbit.hubris.network", type: uses-certificate}
|
||||||
|
- {source: "ingress:sab.hubris.network", target: "cert:sab.hubris.network", type: uses-certificate}
|
||||||
|
|
||||||
- {source: "ingress:secrets.hubris.network", target: "service:secrets-issuance", type: routes-to}
|
- {source: "ingress:secrets.hubris.network", target: "service:secrets-issuance", type: routes-to}
|
||||||
- {source: "ingress:house.hubris.network", target: "service:house", type: routes-to}
|
- {source: "ingress:house.hubris.network", target: "service:house", type: routes-to}
|
||||||
- {source: "ingress:books.hubris.network", target: "service:grimmory", type: routes-to}
|
- {source: "ingress:books.hubris.network", target: "service:grimmory", type: routes-to}
|
||||||
|
|||||||
Reference in New Issue
Block a user