From 4463c56490f3d5d1ad531352dcf352758b78cd1b Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Mon, 6 Jul 2026 21:20:44 +0200 Subject: [PATCH] TODO (#91) Reviewed-on: https://git.eeqj.de/sneak/dnswatcher/pulls/91 --- TODO.md | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..9942d09 --- /dev/null +++ b/TODO.md @@ -0,0 +1,143 @@ +# 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-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