Skip to content

Chore/handover cleanup - #3

Closed
cvk77 wants to merge 13 commits into
markussiebert:mainfrom
cvk77:chore/handover-cleanup
Closed

cvk77 wants to merge 13 commits into
markussiebert:mainfrom
cvk77:chore/handover-cleanup

Conversation

@cvk77

@cvk77 cvk77 commented Oct 18, 2026

Copy link
Copy Markdown

No description provided.

cvk77 and others added 13 commits October 18, 2026 11:20
The zone cache map was read and written from concurrent request
handlers without synchronization. Guard it with a sync.RWMutex and
run the test suite with the race detector as part of the CI task.

Co-authored-by: Claude <noreply@anthropic.com>
Provider.UpdateRecord now reports whether a change was sent, so the
handler can answer "nochg <ip>" as documented instead of always
"good". GetRecord returns provider.ErrRecordNotFound for missing
records.

Co-authored-by: Claude <noreply@anthropic.com>
The hosted zone was looked up by the last two labels of the hostname,
which breaks for multi-label TLDs (example.co.uk) and delegated
subdomain zones. Pick the public hosted zone with the longest name
that is a suffix of the hostname instead, ignore private zones, and
allow pinning the zone via ROUTE53_ZONE_ID.

Also normalize Route53's \052 escape for wildcard names so the
unchanged check works for *.example.com records.

Co-authored-by: Claude <noreply@anthropic.com>
- Parse addresses with net/netip: zone IDs are rejected and
  IPv4-mapped IPv6 addresses (::ffff:a.b.c.d) are set as A records.
  The record type is derived from Addr.Is4() instead of a string check.
- Accept myipv6 in addition to myip; if both are given, A and AAAA are
  set and the response is "good <ip4>,<ip6>".
- Reject loopback, unspecified, multicast, link-local, RFC 1918, CGNAT,
  ULA and documentation addresses with 911 unless ALLOW_PRIVATE_IPS=true.
- Only honor X-Forwarded-For if the peer is listed in TRUSTED_PROXIES,
  read it right to left and accept entries with ports and brackets.
- Optionally delete the record of the other address family after a
  single-family update (CLEANUP_STALE_RECORD=true). Providers gain
  DeleteRecord, which only deletes if the value still matches.

Co-authored-by: Claude <noreply@anthropic.com>
DOMAIN was required but never checked in the server path, so any
authenticated client could update any hostname the provider manages.

- ALLOWED_HOSTNAMES (comma-separated, "*.suffix" patterns allowed)
  rejects all other hostnames with 403 "nohost".
- DOMAIN is now only required by the update command. If set, the
  server uses it as the zone for hostnames within it instead of
  assuming the last two labels.
- Accept DNS_PROVIDER=route53 as an alias for aws_route53, as used in
  the documentation and the Home Assistant add-on schema.

Co-authored-by: Claude <noreply@anthropic.com>
Compare SHA-256 digests of username and password with
subtle.ConstantTimeCompare and evaluate both to avoid timing side
channels. Reject further requests with 429 after 10 failed logins
from the same client address within 15 minutes (standard library only,
no new dependency). The client address honors TRUSTED_PROXIES.

Co-authored-by: Claude <noreply@anthropic.com>
- .env.local.example used DNS_PROVIDER=netcup and AUTH_PASSWORD_HASH;
  the code expects netcup_ccp and a plain AUTH_PASSWORD.
- Home Assistant add-on: allow aws_route53 (route53 kept as alias),
  make domain optional, replace the bcrypt hash instructions and
  document that LAN clients must send myip.

Co-authored-by: Claude <noreply@anthropic.com>
The README referenced k8s/deployment.yaml, k8s/deployment-route53.yaml
and k8s/service.yaml, which did not exist. Add them together with a
secret example. The deployments use /health as readiness and liveness
probe, run as non-root with a read-only root filesystem, and the
Route53 variant includes an IRSA ServiceAccount.

Co-authored-by: Claude <noreply@anthropic.com>
- Route53 is registered as aws_route53 and must be selected explicitly
  (default is netcup_ccp); DOMAIN is optional except for the update
  command; zone discovery, ROUTE53_ZONE_ID and two IAM policy variants
- Complete environment variable table, response codes incl. nohost,
  401 and 429; note that PORT is read but not applied
- Passwords are plain text, not bcrypt hashes
- New sections on IPv6/dual stack, rejected addresses and IPv6/CGNAT
- UniFi: recommend /nic/update?hostname=%h without myip; the inadyn.conf
  edit is only needed for the /%h path format and may not persist
- Reference the new k8s/ manifests, TLS and security notes

Co-authored-by: Claude <noreply@anthropic.com>
AGENTS.md claimed no tests exist, although both providers have tests,
and pointed to a repomix/ folder that is gitignored and not part of
the repository. Also document test:race, the extended provider
interface and the commit trailer for Claude Code sessions.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
The Route53 build constraint negated a non-existent "netcup" tag
instead of "netcup_ccp", so -tags netcup_ccp still compiled the
Route53 provider. Use netcup_ccp, drop the legacy // +build lines and
give the provider test files matching constraints so single-provider
builds compile their tests. The new test:tags task (part of ci)
verifies each variant only contains its own provider.

Co-authored-by: Claude <noreply@anthropic.com>
The server always used the --port flag, whose default of 8053 shadowed
PORT, so PORT (and the Home Assistant port option, which is exported
as PORT) had no effect. The flag no longer has a default; it overrides
PORT only when given. PORT is also range-checked now.

Co-authored-by: Claude <noreply@anthropic.com>
@cvk77

cvk77 commented Oct 18, 2026

Copy link
Copy Markdown
Author

Opened against the wrong base by mistake, sorry

@cvk77 cvk77 closed this Oct 18, 2026
@cvk77
cvk77 deleted the chore/handover-cleanup branch October 18, 2026 09:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant