Files
dyn/README.md

300 lines
8.3 KiB
Markdown

# DWS Dynamic DNS Service
A self-hosted Dynamic DNS (DDNS) provider built with Go, featuring a minimalist web UI and NIC V2 (DynDNS2) protocol support for router compatibility.
## Overview
This service allows users to claim subdomains under `space.dws.rip` and update them via the standard DynDNS2 protocol, compatible with UniFi, pfSense, EdgeRouters, and other consumer routers.
## Architecture
```
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐
│ Router │ ──────► │ DWS Bridge │ ──────► │ Technitium DNS │
│ (DynDNS2) │ HTTP │ (Go) │ API │ (Authoritative)│
└─────────────┘ └──────────────┘ └─────────────────┘
┌──────────────┐
│ SQLite │
│ (token→sub) │
└──────────────┘
```
## Features
- **Token-based authentication** - No user accounts, just secure tokens
- **NIC V2 Protocol** - Compatible with most routers
- **Automatic wildcard records** - `*.your-space.space.dws.rip` also works
- **Rate limiting** - 10 req/min per IP, 1 req/min per token
- **Clean web UI** - Simple space claiming interface
- **Router config examples** - UniFi, pfSense, cURL snippets
## Quick Start
### Prerequisites
- Docker and Docker Compose
- Running Technitium DNS server with API access
- DNS delegation for `space.dws.rip` pointing to Technitium
### DNS Setup
In your parent `dws.rip` zone:
```text
space.dws.rip. IN NS dns.dws.rip.
dns.dws.rip. IN A <TECHNITIUM_IP>
```
### Configuration
Create a `.env` file:
```bash
# Required: Technitium DNS API endpoint
TECHNITIUM_URL=https://dns.dws.rip
# Authentication (choose one method)
# Method 1: API Token (recommended)
TECHNITIUM_TOKEN=your-api-token-here
# Method 2: Username/Password
TECHNITIUM_USERNAME=admin
TECHNITIUM_PASSWORD=your-password
# Optional: Domain configuration
BASE_DOMAIN=dws.rip
SPACE_SUBDOMAIN=space
# Optional: Rate limiting
RATE_LIMIT_PER_IP=10
RATE_LIMIT_PER_TOKEN=1
# Optional: Trusted proxies (comma-separated)
TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12
```
### Deployment
#### Docker Compose (Recommended for single node)
```bash
# Clone and start
git clone https://git.dws.rip/DWS/dyn.git
cd dyn
docker-compose up -d
# View logs
docker-compose logs -f
# Stop
docker-compose down
```
#### Kubernetes / K3s
For production deployments on Kubernetes or K3s clusters:
```bash
# Clone repository
git clone https://git.dws.rip/DWS/dyn.git
cd dyn
# Create your secrets file
cp k8s/overlays/production/secrets.example.yaml k8s/overlays/production/secrets.yaml
# Edit secrets.yaml with your actual Technitium credentials
# Deploy with kustomize
kubectl apply -k k8s/overlays/production
# Or with kubectl 1.14+ (built-in kustomize)
kubectl apply -k k8s/overlays/production
# Check deployment status
kubectl get pods -n dyn-ddns
kubectl logs -n dyn-ddns -l app=dyn-ddns
# Delete deployment
kubectl delete -k k8s/overlays/production
```
**Kustomize Overlays:**
- `k8s/overlays/production` - Production setup (2 replicas, higher resource limits)
- `k8s/overlays/staging` - Staging environment (1 replica, relaxed rate limits)
**Requirements:**
- Cert-manager (for TLS certificates via Let's Encrypt)
- Traefik or NGINX ingress controller
- Persistent storage class (for SQLite database)
See [k8s/README.md](k8s/README.md) for detailed Kubernetes deployment documentation.
## API Endpoints
### Web UI
- `GET /` - Claim space interface
### API
- `GET /api/check?subdomain=<name>` - Check subdomain availability
- `POST /api/claim` - Claim a subdomain (returns token once)
### DynDNS2 Protocol
- `GET /api/nic/update?hostname=<fqdn>&myip=<ip>`
- Auth: Basic auth with username `none` and password = token
- Returns: `good <ip>`, `nochg <ip>`, `badauth`, `nohost`, `911`
## Router Configuration
### UniFi / UDM
```
Service: dyndns
Hostname: your-space.space.dws.rip
Username: none
Password: <your-token>
Server: dyn.dws.rip/api/nic/update
```
### pfSense / OPNsense
```
Service Type: Custom
Update URL: https://dyn.dws.rip/api/nic/update?hostname=%h&myip=%i
Username: none
Password: <your-token>
```
### EdgeRouter
```bash
set service dns dynamic interface eth0 service custom-dyn host-name your-space.space.dws.rip
set service dns dynamic interface eth0 service custom-dyn login none
set service dns dynamic interface eth0 service custom-dyn password <your-token>
set service dns dynamic interface eth0 service custom-dyn protocol dyndns2
set service dns dynamic interface eth0 service custom-dyn server dyn.dws.rip
```
### Manual (cURL)
```bash
curl -u "none:<your-token>" \
"https://dyn.dws.rip/api/nic/update?hostname=your-space.space.dws.rip&myip=1.2.3.4"
```
## Database Schema
```sql
CREATE TABLE spaces (
token TEXT PRIMARY KEY,
subdomain TEXT UNIQUE NOT NULL,
last_ip TEXT,
updated_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
```
## Security
- **32-byte random tokens** - Generated with `crypto/rand`
- **Token displayed once** - No recovery mechanism (claim new space if lost)
- **Rate limiting** - Prevents brute force and abuse
- **No PII stored** - Just token-to-subdomain mapping
- **Input validation** - Subdomains validated (alphanumeric + hyphen only)
## Environment Variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `TECHNITIUM_URL` | Yes | - | Technitium API URL |
| `TECHNITIUM_TOKEN` | No* | - | API token for auth |
| `TECHNITIUM_USERNAME` | No* | - | Basic auth username |
| `TECHNITIUM_PASSWORD` | No* | - | Basic auth password |
| `BASE_DOMAIN` | No | `dws.rip` | Root domain |
| `SPACE_SUBDOMAIN` | No | `space` | Subdomain for user spaces |
| `DATABASE_PATH` | No | `./dyn.db` | SQLite database path |
| `SERVER_PORT` | No | `8080` | HTTP server port |
| `RATE_LIMIT_PER_IP` | No | `10` | Requests per minute per IP |
| `RATE_LIMIT_PER_TOKEN` | No | `1` | Updates per minute per token |
| `TRUSTED_PROXIES` | No | - | Comma-separated CIDRs |
*Either `TECHNITIUM_TOKEN` OR both `TECHNITIUM_USERNAME` and `TECHNITIUM_PASSWORD` required.
## Development
```bash
# Local development
go mod tidy
go run cmd/server/main.go
# Build
go build -o server cmd/server/main.go
# Run all tests
go test ./...
# Run tests with verbose output
go test ./... -v
# Run specific test package
go test ./internal/handlers -v
go test ./internal/database -v
go test ./tests/integration -v
# Run with coverage
go test ./... -cover
```
## Testing
The project includes comprehensive tests:
### Unit Tests
- **config**: Configuration loading and validation (11 tests)
- **database**: Database operations and models (9 tests)
- **handlers**: Handler logic, custom filters, and validation (36 tests)
### Integration Tests
Full end-to-end tests using:
- **Mock Technitium Server**: Simulates DNS API without external dependencies
- **SQLite**: In-memory/temp database for isolated testing
- **Gin Test Server**: HTTP testing without network
Integration tests cover:
- Space claiming workflow
- Subdomain availability checking
- Profanity filtering
- Custom DWS/Tanishq Dubey trademark filtering
- DynDNS2 protocol updates
- Full end-to-end workflows
### Test Files
```
internal/config/config_test.go # Config tests
internal/database/db_test.go # Database tests
internal/handlers/handlers_test.go # Handler unit tests
internal/handlers/validation.go # Custom validators
internal/testutil/mock_technitium.go # Mock DNS server
tests/integration/integration_test.go # Integration tests
```
### Running Integration Tests
```bash
# Run all integration tests
go test ./tests/integration -v
# Run specific integration test
go test ./tests/integration -v -run TestIntegration_FullWorkflow
# Run with timeout
go test ./tests/integration -v -timeout 30s
```
## License
MIT
## Credits
Inspired by [benjaminbear/docker-ddns-server](https://github.com/benjaminbear/docker-ddns-server)