321 lines
8.9 KiB
Markdown
321 lines
8.9 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
|
|
|
|
# Run containerized integration tests with podman/docker-compose
|
|
make test-compose
|
|
|
|
# Or using the shell script directly
|
|
bash tests/integration/compose_test.sh
|
|
|
|
# Run Go-based compose tests (requires RUN_COMPOSE_TESTS=true)
|
|
RUN_COMPOSE_TESTS=true go test ./tests/integration -v -run TestComposeIntegration
|
|
```
|
|
|
|
### Using Make
|
|
```bash
|
|
make help # Show all available targets
|
|
make build # Build the binary
|
|
make test # Run all tests
|
|
make test-unit # Run unit tests only
|
|
make test-integration # Run integration tests
|
|
make test-compose # Run containerized tests
|
|
make run # Build and run the server
|
|
make dev # Run in development mode
|
|
```
|
|
|
|
## License
|
|
|
|
MIT
|
|
|
|
## Credits
|
|
|
|
Inspired by [benjaminbear/docker-ddns-server](https://github.com/benjaminbear/docker-ddns-server)
|