feat(cert): dial the TLS terminator directly so cert-expiry works from the container
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
ci / web (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled

checkCertExpiry now accepts a `dial` address and sets ServerName to the
hostname — it connects to the terminator's IP while SNI/cert-read use the
hostname. The scheduler container has no mesh interface and the host resolver
doesn't know the split-horizon zone, so *.hubris.network can't be dialed by
name from there; dialing Caddy's lab IP (reachable on the LAN) makes the probe
work. The builder passes through a cert entity's `dial` attribute.

Re-seed the 20 *.hubris.network certificate entities with dial=192.168.8.175
(Caddy) and uses-certificate edges; cert-expiry monitoring now has real data.
This commit is contained in:
2026-07-29 19:33:45 +02:00
parent 3d88f52988
commit 2d8eb91b25
3 changed files with 73 additions and 7 deletions

View File

@@ -249,17 +249,26 @@ func buildKind(kind string, t Target, attrs map[string]any, host, user string, p
}}, ""
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.
// The host whose cert to read (SNI / cert CN). Prefer an explicit
// `hostname` attribute, then `cn`, then a dotted name. Hourly: expiry
// changes once a day, but a renewal or 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"
}
// `dial` is the TLS terminator's address to connect to (Caddy's lab
// IP), used when the hostname doesn't resolve/reach from the scheduler.
// Without it the probe can't reach *.hubris.network from a container
// with no mesh / split-horizon DNS.
dial, _ := attrs["dial"].(string)
config := map[string]any{"host": host, "warn_days": 30, "crit_days": 7}
if dial != "" {
config["dial"] = dial
}
return []checkDef{{
kind: "cert-expiry",
config: map[string]any{"host": host, "warn_days": 30, "crit_days": 7},
config: config,
interval: 3600,
}}, ""
}

View File

@@ -536,6 +536,12 @@ func checkDisk(ctx context.Context, cd sqlcgen.ListEnabledCheckDefsRow) checkRes
func checkCertExpiry(ctx context.Context, cd sqlcgen.ListEnabledCheckDefsRow) checkResult {
cfg := struct {
Host string `json:"host"`
// Dial is an optional explicit dial address (the TLS terminator's IP)
// for when the hostname doesn't resolve/reach from the scheduler — the
// container has no mesh interface and the host resolver doesn't know
// the split-horizon zone, so *.hubris.network dials Caddy's lab IP
// directly while SNI/cert-read still uses Host.
Dial string `json:"dial"`
Port int `json:"port"`
WarnDays int `json:"warn_days"`
CritDays int `json:"crit_days"`
@@ -556,9 +562,13 @@ func checkCertExpiry(ctx context.Context, cd sqlcgen.ListEnabledCheckDefsRow) ch
timeout = 30 * time.Second
}
addr := net.JoinHostPort(cfg.Host, fmt.Sprintf("%d", cfg.Port))
dialHost := cfg.Host
if cfg.Dial != "" {
dialHost = cfg.Dial
}
addr := net.JoinHostPort(dialHost, fmt.Sprintf("%d", cfg.Port))
d := tls.Dialer{Config: &tls.Config{InsecureSkipVerify: true}}
d := tls.Dialer{Config: &tls.Config{InsecureSkipVerify: true, ServerName: cfg.Host}}
conn, err := d.DialContext(ctx, "tcp", addr)
if err != nil {
return checkResult{