feat(domains): migrate user namespaces to wildcard CNAMEs
Build and publish Docker image / build-and-publish (push) Successful in 8s

This commit is contained in:
wfalkowski committed 2026-10-20 18:10:23 +00:00
1 parent 82fa50e9d5
commit 2768f5f97d
6 files changed
+503 -296

No files matched your search

+53
View File
@@ -0,0 +1,53 @@
# Domain System Plan
## Current user namespace
The domain manager provisions two DNS records in the existing `anchorhost.eu` zone for each username:
- `u-<username>.anchorhost.eu CNAME anchorhost.eu.`
- `*.u-<username>.anchorhost.eu CNAME anchorhost.eu.`
Users claim individual hostnames such as `play.u-<username>.anchorhost.eu` in the application database. The wildcard record makes each single-label hostname resolve without another DNS write. Claims remain necessary for ownership, duplicate prevention, and service linking. DNS resolution alone does not create a Traefik route.
The DNS API refuses to replace a conflicting record at the namespace owner. This matters for accounts that still have an NS delegation from the previous design: an NS delegation and a CNAME cannot coexist at the same name. Those namespaces require an explicit migration.
## Automatic domain for every service
### 1. Allocate a stable service hostname
When a service is created, derive a DNS-safe label from its display name and reserve it under the owner's namespace. Enforce a unique constraint on the normalized full hostname; on collision, add a short stable suffix. Persist the assigned hostname against the service record, and release it only when the service is permanently deleted. Renames should update the display name without silently changing the public hostname.
### 2. Publish a service-ready state
Only publish a hostname after the service, network endpoint, and platform route are ready. Show the assigned address and readiness state in the dashboard. If provisioning fails, retain the reservation for retry and surface a useful error rather than leaving a hostname that resolves to a dead endpoint.
### 3. Route web services
For HTTP/HTTPS services, create a Traefik `IngressRoute` from the assigned `Host()` name to the service's Kubernetes Service and declared HTTP port. Validate ownership and namespace before creating or deleting routes. Provision TLS using a certificate that covers the user namespace; wildcard certificates require DNS-01 with a provider credential, while HTTP-01 cannot issue wildcard certificates. Keep the DNS provider token out of user workloads and API responses.
### 4. Handle game ports by protocol
DNS does not carry a TCP/UDP port. For Minecraft Java, publish an `_minecraft._tcp.<service-host>` SRV record with the allocated external port and point it at the service hostname; Java clients can then connect without users typing the port. Verify client behavior and ensure the SRV target has an address record. Minecraft Bedrock and arbitrary TCP/UDP services need protocol-specific support or a proxy that can multiplex traffic. Traefik TCP SNI routing only applies when clients send usable TLS SNI; it is not a general raw TCP port multiplexer. Until a compatible proxy or a direct port is available, continue showing the allocated port.
### 5. Lifecycle and safety
- Create routes and DNS metadata idempotently, then verify ready Service endpoints before marking a domain active.
- Remove only resources whose labels/annotations and database ownership identify the service being deleted.
- On stop, preserve the hostname but show the service as offline; on delete, remove route and SRV metadata before releasing the name.
- Rate-limit hostname claims and validate every label, hostname length, user ownership, and service ownership server-side.
- Record provisioning failures and cleanup outcomes without logging provider tokens or other secrets.
## Migration from per-user delegated zones
The one-time migration is implemented by `scripts/migrate_delegated_dns.py`. It defaults to a read-only dry run, checks for conflicts, copies child-zone RRsets into the parent zone, removes parent DS/NS delegation records, creates the namespace and wildcard CNAMEs, and updates existing platform claim metadata. It leaves child zones intact as a rollback source. Run it from the backend environment and review the complete dry-run output before passing `--apply`.
Do not remove existing NS or DS records as part of an ordinary domain claim. The migration is idempotent and stops before writing if it finds conflicting records or child-zone apex data that cannot coexist with the required CNAME. After cutover, verify DNSSEC and public resolution before retiring any child zone. Resolver caches may retain old delegation or record data for the previous TTL.
## Acceptance checks before enabling automatic service names
- A new account's base and wildcard names resolve to the platform edge after DNS cache expiry.
- Existing names under a legacy NS delegation are detected and never overwritten.
- A duplicate service label cannot be claimed by another service or user.
- HTTP hostnames reach only the owning service and receive a valid certificate.
- Minecraft Java SRV lookup returns the allocated external port; other protocols retain an explicit port unless a tested proxy supports portless access.
- Service stop, restart, rename, and deletion leave DNS, route, and database state consistent.