Each component README now links to its corresponding Redmine tickets (closed for completed work, open for pending items) for bidirectional traceability between code and system of record.
186 lines
7.5 KiB
Markdown
186 lines
7.5 KiB
Markdown
# Technitium DNS Cluster Setup
|
|
|
|
> **Redmine:** [#357](https://projects.knownelement.com/issues/357) (deployment, closed)
|
|
|
|
Replicates the production Technitium DNS Server from `tailscale-router` to the
|
|
`pfv-netinfra-01/02` pair and configures them as a primary/secondary cluster
|
|
with automatic zone transfers.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
tailscale-router (PRODUCTION — READ ONLY)
|
|
└─ tsys-dns container (technitium/dns-server)
|
|
└─ 124 zones (knel.net + reverse DNS)
|
|
└─ Users + 2FA in auth.config
|
|
│
|
|
docker cp (export)
|
|
│
|
|
▼
|
|
┌─ pfv-netinfra-01 (192.168.3.252) ──── PRIMARY ──────────┐
|
|
│ tsys-dns container (Technitium on :5300) │
|
|
│ pihole container (Pi-hole on :53 → Technitium :5300) │
|
|
│ All zones are Primary │
|
|
│ Zone transfer allowed from 192.168.3.253 │
|
|
└──────────────────────────────────────────────────────────┘
|
|
│
|
|
AXFR / IXFR + NOTIFY (DNS zone transfer, port 5300)
|
|
│
|
|
▼
|
|
┌─ pfv-netinfra-02 (192.168.3.253) ─── SECONDARY ────────┐
|
|
│ tsys-dns container (Technitium on :5300) │
|
|
│ pihole container (Pi-hole on :53 → Technitium :5300) │
|
|
│ All zones are Secondary (AXFR from 01) │
|
|
└──────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### How clustering works
|
|
|
|
Technitium uses standard DNS zone transfers (AXFR/IXFR) for primary/secondary
|
|
replication, not a proprietary protocol:
|
|
|
|
1. **Primary (01)** holds all zones as authoritative primary zones.
|
|
2. **Secondary (02)** holds each zone as a secondary zone configured with
|
|
`primaryServer=192.168.3.252:5300`.
|
|
3. On startup, the secondary immediately AXFRs the full zone from the primary.
|
|
4. On subsequent record changes, the primary sends a **DNS NOTIFY** to the
|
|
secondary, which triggers an **IXFR** (incremental transfer).
|
|
5. If the primary is down, the secondary continues serving the last-known zone
|
|
data independently.
|
|
|
|
### Credentials and 2FA
|
|
|
|
The production `auth.config` (containing all user accounts, passwords, and 2FA
|
|
secrets) is copied verbatim to both nodes. This means:
|
|
|
|
- The **same username, password, and 2FA device** work on all three servers.
|
|
- The web console is at `http://<host>:5380/` on each node.
|
|
- No credential changes are needed.
|
|
|
|
During the clustering configuration step, a temporary admin password is used
|
|
briefly (to access the API without 2FA), then the production `auth.config` is
|
|
restored. See "Security notes" below.
|
|
|
|
## Prerequisites
|
|
|
|
- SSH key access to all hosts as `localuser` with passwordless sudo.
|
|
- The `remote-dns.sh` wrapper must be able to reach all hosts via Tailscale FQDN.
|
|
- Docker + Docker Compose on netinfra-01/02 (already installed).
|
|
- The production Technitium on tailscale-router must be running.
|
|
|
|
## Usage
|
|
|
|
```bash
|
|
cd dns-cluster-setup/
|
|
|
|
# Step-by-step (recommended for first run):
|
|
./setup.sh export # 1. Export config from tailscale-router (READ-ONLY)
|
|
./setup.sh deploy01 # 2. Deploy to netinfra-01 as primary
|
|
./setup.sh deploy02 # 3. Deploy to netinfra-02 as secondary clone
|
|
./setup.sh cluster # 4. Configure clustering (01→02 zone transfers)
|
|
./setup.sh verify # 5. Run all verification tests
|
|
|
|
# Or all at once:
|
|
./setup.sh all
|
|
```
|
|
|
|
### Configuration overrides
|
|
|
|
All defaults can be overridden via environment variables:
|
|
|
|
| Variable | Default | Description |
|
|
|---|---|---|
|
|
| `PRIMARY_IP` | `192.168.3.252` | netinfra-01 LAN IP |
|
|
| `SECONDARY_IP` | `192.168.3.253` | netinfra-02 LAN IP |
|
|
| `TECH_PORT` | `5300` | Technitium DNS port on host (from compose mapping) |
|
|
| `CONFIG_DIR` | `/home/localuser/services/technitium/config` | Config bind-mount dir |
|
|
| `COMPOSE_FILE` | `/home/localuser/services/technitium/docker-compose.yml` | Compose file |
|
|
| `TEMP_ADMIN_PW` | `KnelClusterSetup!2026` | Temp admin password (used only during clustering, then discarded) |
|
|
|
|
## Scripts
|
|
|
|
| Script | Purpose |
|
|
|---|---|
|
|
| `remote-dns.sh` | SSH/SCP chokepoint for all DNS host access (tsrouter, netinfra01, netinfra02, netboot, sandbox) |
|
|
| `setup.sh` | Master orchestrator: export → deploy → cluster → verify |
|
|
| `verify.sh` | Comprehensive 10-section verification suite |
|
|
| `discover*.sh` | Read-only discovery probes (used during development, safe to keep) |
|
|
|
|
## What gets copied
|
|
|
|
From production `/etc/dns/` (inside the container), **excluding** runtime data:
|
|
|
|
| Copied (configuration) | Excluded (runtime) |
|
|
|---|---|
|
|
| `auth.config` (users, passwords, 2FA) | `cache.bin` (DNS cache) |
|
|
| `dns.config` (server settings) | `stats/` (query statistics) |
|
|
| `webservice.config` (web console) | `logs/` (log files) |
|
|
| `allowed.config` (zone transfer ACL) | |
|
|
| `blocked.config` (blocked domains) | |
|
|
| `blocklist.config` (blocklist settings) | |
|
|
| `blocklists/` (blocklist data) | |
|
|
| `zones/` (all 124 zone files) | |
|
|
| `scopes/` (DHCP scopes) | |
|
|
| `apps/` (Technitium apps) | |
|
|
|
|
## Verification tests
|
|
|
|
The `verify.sh` script runs 10 categories of tests:
|
|
|
|
1. **Container health** — both Technitium containers are Up
|
|
2. **API responds** — web console API is reachable on both nodes
|
|
3. **Zone count** — primary matches production; secondary matches primary
|
|
4. **Forward DNS** — known knel.net records resolve identically on both nodes
|
|
5. **External DNS** — both nodes can resolve external domains (github.com)
|
|
6. **Zone transfer (AXFR)** — secondary can AXFR knel.net from primary
|
|
7. **Reverse DNS** — PTR zones have SOA records on both nodes
|
|
8. **Production untouched** — container still running, zone count unchanged
|
|
9. **Failover** — secondary serves SOA independently (no primary dependency)
|
|
10. **Credentials** — `auth.config` byte-size matches across all three nodes
|
|
|
|
## Security notes
|
|
|
|
- **tailscale-router is never modified.** The only operation is `docker cp`
|
|
(read) to export the config. No writes, no restarts, no config changes.
|
|
- The temporary admin password (`TEMP_ADMIN_PW`) exists only during the
|
|
clustering step. After configuration, the production `auth.config` (with 2FA)
|
|
is restored. The temp password is never persisted.
|
|
- The export tarball (`.export/technitium-production-config.tar.gz`) contains
|
|
production credentials. It is in `.gitignore` and should be deleted after
|
|
setup: `rm -rf dns-cluster-setup/.export/`
|
|
- Each node's existing config is backed up to `config.backup-<timestamp>` before
|
|
replacement, so the change is reversible.
|
|
|
|
## Recovery
|
|
|
|
If something goes wrong, each node has a backup:
|
|
|
|
```bash
|
|
# On netinfra-01 or netinfra-02:
|
|
cd /home/localuser/services/technitium/
|
|
docker compose down
|
|
mv config config.failed
|
|
mv config.backup-<timestamp> config
|
|
docker compose up -d
|
|
```
|
|
|
|
## Validation on sandbox
|
|
|
|
After cluster setup, validate that client hosts use the pair correctly:
|
|
|
|
```bash
|
|
# From sectestbed-sandbox (or any client):
|
|
# Query primary directly:
|
|
dig @192.168.3.252 pfv-netinfra-01.knel.net
|
|
|
|
# Query secondary directly:
|
|
dig @192.168.3.253 pfv-netinfra-01.knel.net
|
|
|
|
# Both should return the same answer.
|
|
```
|
|
|
|
The KNELServerBuild provisioning code (`provisioning/ConfigFiles/NTP/ntp.conf`
|
|
and `provisioning/ConfigFiles/Resolv/resolv.conf`) points clients at both
|
|
servers for DNS and NTP redundancy. See `docs/server-build/tailscale.md` for the
|
|
full DNS architecture analysis.
|