config: watch a target listed twice only once (closes #207)
check / check (push) Failing after 28s

ClassifyTargets kept a name every time it appeared in DNSWATCHER_TARGETS,
so example.com,Example.com. put example.com in the domain list twice and
every check looked it up, checked its ports and its certificates twice,
sending two expiry warnings per TLS check. It now skips a name it has
already kept, comparing after the lower-casing and trailing-dot removal
it already did; the list keeps the order of first appearance.

Model: opus-5-5
This commit is contained in:
2026-10-02 00:20:42 +00:00
parent c9510a986c
commit 2b034ae65a
4 changed files with 35 additions and 3 deletions
+2 -1
View File
@@ -330,7 +330,8 @@ following precedence (highest to lowest):
monitoring targets are configured. A monitoring daemon with nothing to monitor monitoring targets are configured. A monitoring daemon with nothing to monitor
is a misconfiguration, so dnswatcher fails fast with a clear error message is a misconfiguration, so dnswatcher fails fast with a clear error message
rather than running silently. Set `DNSWATCHER_TARGETS` to a comma-separated list rather than running silently. Set `DNSWATCHER_TARGETS` to a comma-separated list
of DNS names before starting. of DNS names before starting. A name listed more than once, in any letter case
or with a trailing dot, is watched once.
**`/metrics` is rate limited.** Each client address may send it 30 requests a **`/metrics` is rate limited.** Each client address may send it 30 requests a
minute, failed logins included; beyond that it answers `429 Too Many Requests` minute, failed logins included; beyond that it answers `429 Too Many Requests`
+2
View File
@@ -19,6 +19,8 @@ trial run of the finished image: https://git.eeqj.de/sneak/dnswatcher/issues/149
# Completed Steps # Completed Steps
- 2026-10-02: a name listed more than once in `DNSWATCHER_TARGETS`, in any
letter case or with a trailing dot, is watched once (closes #207).
- 2026-10-01: a certificate within the expiry warning period is warned about on - 2026-10-01: a certificate within the expiry warning period is warned about on
every TLS check, where some checks used to skip it at random (closes #204). every TLS check, where some checks used to skip it at random (closes #204).
- 2026-10-01: a domain's NS set is its delegation from the parent zone's - 2026-10-01: a domain's NS set is its delegation from the parent zone's
+7 -2
View File
@@ -57,17 +57,22 @@ func ClassifyDNSName(name string) (DNSNameType, error) {
// ClassifyTargets splits a list of DNS names into apex domains and // ClassifyTargets splits a list of DNS names into apex domains and
// hostnames using the Public Suffix List. It returns an error if any // hostnames using the Public Suffix List. It returns an error if any
// name cannot be classified. // name cannot be classified. A name given more than once, in any letter
// case or with a trailing dot, is kept once.
func ClassifyTargets(targets []string) ([]string, []string, error) { func ClassifyTargets(targets []string) ([]string, []string, error) {
var domains, hostnames []string var domains, hostnames []string
seen := make(map[string]bool)
for _, t := range targets { for _, t := range targets {
normalized := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(t), ".")) normalized := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(t), "."))
if normalized == "" { if normalized == "" || seen[normalized] {
continue continue
} }
seen[normalized] = true
typ, classErr := ClassifyDNSName(normalized) typ, classErr := ClassifyDNSName(normalized)
if classErr != nil { if classErr != nil {
return nil, nil, classErr return nil, nil, classErr
+24
View File
@@ -1,6 +1,7 @@
package config_test package config_test
import ( import (
"slices"
"testing" "testing"
"sneak.berlin/go/dnswatcher/internal/config" "sneak.berlin/go/dnswatcher/internal/config"
@@ -93,6 +94,29 @@ func TestClassifyTargets(t *testing.T) {
} }
} }
func TestClassifyTargetsKeepsEachNameOnce(t *testing.T) {
t.Parallel()
domains, hostnames, err := config.ClassifyTargets([]string{
"example.com",
"Example.com.",
"www.example.com",
"EXAMPLE.COM",
"WWW.Example.com.",
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !slices.Equal(domains, []string{"example.com"}) {
t.Errorf("domains = %v, want [example.com]", domains)
}
if !slices.Equal(hostnames, []string{"www.example.com"}) {
t.Errorf("hostnames = %v, want [www.example.com]", hostnames)
}
}
func TestClassifyTargetsRejectsPublicSuffix(t *testing.T) { func TestClassifyTargetsRejectsPublicSuffix(t *testing.T) {
t.Parallel() t.Parallel()