watcher: a name removed from the targets leaves the state (closes #223)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
At startup, before the first check, Run removes from the loaded state the domain, hostname and certificate entries of names no longer in DNSWATCHER_TARGETS, so the dashboard, /api/v1/status and the startup notification count only configured names. A configured domain's own records, saved as a hostname entry under its name, are kept. Nothing is notified. Each port check, next to the removal of stale port entries, now also removes the certificate entries for an address a name no longer resolves to, except while none of its nameservers answered, as port entries already were. Model: opus-5-5
This commit is contained in:
@@ -130,6 +130,7 @@ func (w *Watcher) Run(ctx context.Context) {
|
||||
"tlsInterval", w.config.TLSInterval.String(),
|
||||
)
|
||||
|
||||
w.cleanupRemovedTargets()
|
||||
w.RunOnce(ctx)
|
||||
w.maybeSendTestNotification(ctx)
|
||||
|
||||
@@ -200,6 +201,31 @@ func (w *Watcher) detectFirstRun() {
|
||||
}
|
||||
}
|
||||
|
||||
// cleanupRemovedTargets removes from the loaded state the domain,
|
||||
// hostname and certificate entries of names no longer in the
|
||||
// configuration, which changes only at a restart. Nothing is notified.
|
||||
// A configured domain's own records are saved as a hostname entry under
|
||||
// its name, which is kept.
|
||||
func (w *Watcher) cleanupRemovedTargets() {
|
||||
for _, name := range w.state.GetAllDomainNames() {
|
||||
if !w.isDomain(name) {
|
||||
w.state.DeleteDomainState(name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range w.state.GetAllHostnames() {
|
||||
if !w.isConfigured(name) {
|
||||
w.state.DeleteHostnameState(name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, key := range w.state.GetAllCertificateKeys() {
|
||||
if _, hostname := parseCertKey(key); !w.isConfigured(hostname) {
|
||||
w.state.DeleteCertificateState(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runDNSChecks performs DNS resolution for all configured domains
|
||||
// and hostnames, updating state with freshly resolved records.
|
||||
// This must complete before port or TLS checks run so those
|
||||
@@ -807,8 +833,10 @@ func (w *Watcher) checkAllPorts(ctx context.Context) {
|
||||
}
|
||||
|
||||
// Phase 3: Remove port state entries that no longer have
|
||||
// any hostname referencing them.
|
||||
// any hostname referencing them, and certificate entries for
|
||||
// an address their name no longer has.
|
||||
w.cleanupStalePorts(associations)
|
||||
w.cleanupStaleCertificates()
|
||||
}
|
||||
|
||||
// buildPortAssociations constructs a map from IP:port keys to
|
||||
@@ -892,11 +920,45 @@ func (w *Watcher) cleanupStalePorts(
|
||||
}
|
||||
}
|
||||
|
||||
// cleanupStaleCertificates removes the certificate entries for an
|
||||
// address their name no longer resolves to. An entry saved for a name
|
||||
// none of whose nameservers answered is kept: that name's addresses are
|
||||
// not known, not gone.
|
||||
func (w *Watcher) cleanupStaleCertificates() {
|
||||
for _, key := range w.state.GetAllCertificateKeys() {
|
||||
ip, hostname := parseCertKey(key)
|
||||
|
||||
if slices.Contains(w.collectIPs(hostname), ip) ||
|
||||
w.noNameserverAnswered(hostname) {
|
||||
continue
|
||||
}
|
||||
|
||||
w.state.DeleteCertificateState(key)
|
||||
}
|
||||
}
|
||||
|
||||
// parseCertKey splits an "ip:port:hostname" certificate key into its
|
||||
// address and hostname.
|
||||
func parseCertKey(key string) (string, string) {
|
||||
lastColon := strings.LastIndex(key, ":")
|
||||
if lastColon < 0 {
|
||||
return "", key
|
||||
}
|
||||
|
||||
ip, _ := parsePortKey(key[:lastColon])
|
||||
|
||||
return ip, key[lastColon+1:]
|
||||
}
|
||||
|
||||
// isConfigured reports whether name is a configured domain or hostname.
|
||||
func (w *Watcher) isConfigured(name string) bool {
|
||||
return w.isDomain(name) || slices.Contains(w.config.Hostnames, name)
|
||||
}
|
||||
|
||||
// noNameserverAnswered reports whether name is a configured domain or
|
||||
// hostname and none of its nameservers answered on its last check.
|
||||
func (w *Watcher) noNameserverAnswered(name string) bool {
|
||||
if !slices.Contains(w.config.Hostnames, name) &&
|
||||
!slices.Contains(w.config.Domains, name) {
|
||||
if !w.isConfigured(name) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user