Files
dnswatcher/TODO.md
sneak 9054db8d86
All checks were successful
check / check (push) Successful in 33s
build: update golangci-lint to v2.12.2 with org-standard v2 config
Pin golangci-lint to commit c0d3ddc9cf3faa61a4e378e879ece580256d76e5
(v2.12.2) in Dockerfile and script/bootstrap.

Set .golangci.yml to the org-standard v2-schema config already used
across the org's repos (owner-authorized; the same file is being
landed as canonical via a prompts-repo PR). Lint settings live under
linters.settings, so the lll, funlen, cyclop, and dupl thresholds are
actually applied. The informational gomodguard deprecation warning
this config can emit under v2.12 is accepted.

Fix all findings surfaced by the now-active thresholds:

- goconst: shared constants for repeated status, priority, and DNS
  fixture strings in watcher.go and the notify, state, and watcher
  tests
- dupl: consolidate duplicated ntfy/slack HTTP-error tests and
  SendNotification endpoint-error tests behind shared helpers
- lll: wrap long test table entries and comments; shorten one inline
  nolint justification
2026-08-07 20:56:58 +00:00

7.0 KiB

Workflow

  • branch (from main)
  • do the work in Next Step
  • move Next Step to the top of Completed Steps
  • move the top item of Future Steps into Next Step
  • commit (TODO.md changes in the same commit as the work)
  • merge to main if the branch is not protected, otherwise open a PR
  • push

Status

pre-1.0. No git tags. Core resolver work in flight on feature/resolver (dirty: internal/resolver/resolver_test.go). Local checkout has diverged from origin: origin/main is 8 commits ahead (watcher orchestrator, unified TARGETS) and origin/feature/resolver already contains the full iterative resolver implementation with hermetic mocked tests.

Next Step

Policy scaffold commit: add LICENSE, REPO_POLICIES.md, .editorconfig, .dockerignore, and .gitea/workflows/check.yml, and add the missing fmt-check, docker, and hooks targets to the Makefile. One commit, then confirm make check still passes.

Completed Steps

  • 2026-08-07: golangci-lint bumped to v2.12.2 (commit-pinned installs in Dockerfile and script/bootstrap); .golangci.yml set to the org-standard v2-schema config used across the org's repos (owner-authorized; same file is being landed as canonical via prompts PR #24), with settings under linters.settings so the lll/funlen/cyclop/dupl thresholds apply; fixed the resulting goconst, dupl, and lll findings; the informational gomodguard deprecation warning under this config is accepted
  • 2026-07-07 Adopted scripts-to-rule-them-all: script/ entrypoints, Makefile shims, README Entrypoints section
  • 2026-02-20: iterative DNS resolver implemented; tests made hermetic with mocked DNS (origin/feature/resolver, unmerged)
  • 2026-02-20: CI actions and go install refs pinned to commit SHAs; Gitea Actions workflow for make check (origin/ci/make-check, unmerged)
  • 2026-02-20: watcher monitoring orchestrator merged to main (#8)
  • 2026-02-20: DOMAINS/HOSTNAMES unified into single TARGETS config (#11)
  • 2026-02-19: TCP port connectivity checker, made concurrent with port validation; gosec G704 SSRF findings fixed without suppression (feature branches, unmerged)
  • 2026-02-19: TLS certificate inspector with no-peer-certificates error path and IP SANs (feature branch, unmerged)
  • 2026-02-19: gosec SSRF and formatting fixes on main
  • 2026-02-19: initial scaffold with per-nameserver DNS monitoring model

Future Steps

Compliance:

  • Add README sections required by policy (Description, Getting Started, Rationale, Design, TODO, License, Author) if any are missing
  • Pin Dockerfile base images by sha256 and ensure the Docker build runs make check

Branch reconciliation:

  • Sync local checkout with origin: local main is 8 commits behind origin/main; local feature/resolver has diverged from origin/feature/resolver, which already implements the resolver
  • Merge in-flight branches to main once green: feature/resolver, ci/make-check, feature/portcheck-implementation, feature/tlscheck-implementation

Resolver (plan from untracked TODO.md; largely implemented on origin/feature/resolver, verify each item before closing):

  • Add github.com/miekg/dns dependency
  • roots.go: hardcoded IANA root server list (a through m, IPv4/IPv6), rootServers() returning ip:53 strings
  • query.go: low-level query(ctx, server, name, qtype): UDP with TCP fallback on truncation, RD=0, context respected, 5s per-query timeout, returns raw *dns.Msg
  • trace.go: iterative delegation chasing from roots: referral detection (NOERROR, empty answer, NS in authority), glue extraction with bailiwick check, out-of-bailiwick NS resolved with recursion guard, delegation depth limit (20), retry across nameservers on failure, do not chase CNAMEs inside trace
  • FindAuthoritativeNameservers: NS set via trace, sorted, FQDN normalized, trailing dot handled; must pass its 9 tests
  • QueryNameserver: resolve NS host to IPs, query A/AAAA/CNAME/MX/TXT/ SRV/CAA/NS, build NameserverResponse with status mapping (OK, NXDomain, NoData, Error), documented record formatting, sorted values, lame delegation detection; must pass its 16 tests
  • QueryAllNameservers: find NS set for parent domain (public suffix list), query all NS in parallel with bounded concurrency, return map even when all fail, context cancellation; must pass its 4 tests
  • LookupNS: thin wrapper over FindAuthoritativeNameservers, sorted, identical results; must pass its 3 tests
  • ResolveIPAddresses: collect A/AAAA from all NS, follow CNAME chains with MaxCNAMEDepth, dedupe, sort, NXDOMAIN returns empty slice with nil error; must pass its 9 tests
  • All 39 resolver tests pass, make check green, merge to main

Watcher (internal/watcher/watcher.go):

  • Scheduling loop in Run(ctx): initial check on startup, separate tickers for DNS/port and TLS intervals, persist state via state.Save() after each cycle, clean shutdown on context cancel
  • Domain check: LookupNS, compare to stored state, store silently on first run, notify with old/new NS lists on change
  • Hostname check: QueryAllNameservers, compare per-NS records; notify on record changes, NS failure, NS recovery, inconsistency detected, inconsistency resolved, empty response; store silently on first run
  • Port check: ResolveIPAddresses, check ports 80 and 443 per IP, notify on open/closed transitions, handle new and disappeared IPs
  • TLS check: for each open IP:443, CheckCertificate; notify on expiry warning, certificate change (CN/issuer/SANs), TLS failure/recovery

Port checker (internal/portcheck/portcheck.go):

  • Tests against known-open ports and RFC documentation IPs
  • CheckPort: net.DialTimeout (5s), context respected; (true, nil) open, (false, nil) closed/timeout/refused, error only for unexpected failures

TLS checker (internal/tlscheck/tlscheck.go):

  • Tests against known public HTTPS servers, verify fields populated
  • CheckCertificate: tls.Dial to specific IP:443 with hostname as SNI; extract subject CN, issuer CN and org, NotAfter, SANs; error on handshake failure

Notification service (internal/notify/notify.go, Slack/Mattermost/ntfy backends exist):

  • Structured notification types: DNS change, port change, TLS expiry, TLS change, NS failure, NS recovery, NS inconsistency
  • Per-backend formatting: Slack/Mattermost attachment colors (red failures/expiry, yellow warnings, green recoveries, blue info); ntfy priorities (urgent failures, high warnings, default changes, low recoveries); include hostname, nameserver, old/new values, timestamps

HTTP API handlers:

  • Wire *state.State and *watcher.Watcher into handler params
  • GET /api/v1/status: full state snapshot as JSON
  • GET /api/v1/domains: domain states with NS records and last-checked
  • GET /api/v1/hostnames: hostname states with per-NS record data

Infrastructure notes (from untracked TODO.md):

  • Module path sneak.berlin/go/dnswatcher differs from the git.eeqj.de remote intentionally; do not "fix" it
  • Dependencies: github.com/miekg/dns, golang.org/x/net/publicsuffix
  • Resolver tests originally used live DNS against *.dns.sneak.cloud (required records documented in the test file header); origin now has mocked hermetic tests, keep them hermetic