Zones, address redirects, inline rules, lists and groups are cluster objects now (see the previous commits and deploy/MIGRATION.md), so the mechanisms that held them are gone: * internal/kubezones and the resolver's service-account access to a ConfigMap, the address-overrides read/write path and its poller, the zone-file poller and the Local zones editor with its /api/local-zones routes. * localzone.Managed, the directory of console-written master files, and the blocklist.managed_zones key. The resolver now writes no zone data at all; its zone hooks only list. The shared half of that file (the record validator, the renderer and the round-trip check) stays as zonestate.go, because DNSZone rendering is built on it. Its tests are ported to RenderZone, which keeps the coverage of every editable record type, wildcards, relative owners, the SOA/NS rule and the delegation rule. * Tests of the removed write paths: the contract test now builds its zone from a DNSZone, and a new test pins that every zone write route on the resolver is refused and changes nothing. Standalone installs keep editing master files on disk and SIGHUP, and the YAML-backed domain, client, upstream and settings editors, which have not moved. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
229 lines
6.4 KiB
YAML
229 lines
6.4 KiB
YAML
# fancy-dns — example configuration.
|
|
#
|
|
# Every value below is a default; delete what you do not need. The admin API
|
|
# rewrites this file when you save from the console, so keep it in git and
|
|
# review the diff.
|
|
|
|
version: 0
|
|
|
|
server:
|
|
# In-flight upstream resolutions. Excess queries get SERVFAIL rather than
|
|
# queueing without bound.
|
|
max_concurrent: 1024
|
|
timeouts:
|
|
udp: 2s
|
|
tcp: 4s
|
|
tls: 4s
|
|
query: 5s
|
|
upstream: 2s
|
|
list_fetch: 60s
|
|
|
|
dns:
|
|
udp: ["0.0.0.0:53", "[::]:53"]
|
|
tcp: ["0.0.0.0:53", "[::]:53"]
|
|
# Advertised EDNS0 buffer size for our own answers.
|
|
max_udp_size: 1232
|
|
serve_stale: 0s
|
|
edns0:
|
|
# Forward the client's EDNS-Client-Subnet so upstream picks a nearby
|
|
# anycast location.
|
|
client_subnet: true
|
|
ipv4_bits: 24
|
|
ipv6_bits: 56
|
|
preserve: true
|
|
dot:
|
|
enabled: false
|
|
listen: ["0.0.0.0:853"]
|
|
cert_file: /etc/fancy-dns/tls/fullchain.pem
|
|
key_file: /etc/fancy-dns/tls/privkey.pem
|
|
min_version: "1.2"
|
|
doh:
|
|
enabled: false
|
|
listen: ["0.0.0.0:443"]
|
|
base_path: /dns-query
|
|
cert_file: /etc/fancy-dns/tls/fullchain.pem
|
|
key_file: /etc/fancy-dns/tls/privkey.pem
|
|
min_version: "1.2"
|
|
allow_post: true
|
|
transport:
|
|
mode: auto
|
|
|
|
upstream:
|
|
# parallel: race them, take the first good answer. random: try N at random.
|
|
# failover: walk the list in order.
|
|
policy: parallel
|
|
max_attempts: 3
|
|
healthcheck_interval: 30s
|
|
breaker_threshold: 5
|
|
breaker_cooldown: 30s
|
|
servers:
|
|
- addr: 9.9.9.9:53
|
|
proto: udp
|
|
health_name: dns.quad9.net
|
|
- addr: 149.112.112.112:53
|
|
proto: udp
|
|
health_name: dns.quad9.net
|
|
- addr: 1.1.1.1:53
|
|
proto: tls
|
|
server_name: cloudflare-dns.com
|
|
- addr: 9.9.9.9:853
|
|
proto: tls
|
|
server_name: dns.quad9.net
|
|
|
|
# The memory envelope. A cgroup kill is an outage; a cache miss is not, so the
|
|
# runtime gets a soft ceiling to work towards instead of finding out the hard
|
|
# way. Set limit to your container's memory limit, minus the reserve.
|
|
memory:
|
|
# Soft heap ceiling (GOMEMLIMIT). 0 = unbounded. The Go heap excludes
|
|
# goroutine stacks, runtime metadata and the bbolt mmap for the cache
|
|
# snapshot and the query log, which is what the reserve is for.
|
|
limit: "0"
|
|
reserve: "256MiB"
|
|
# Byte budget for the in-memory LRU. 0 derives it from the limit (60%).
|
|
# cache.size alone does not bound memory: one cached ANY or TXT answer can
|
|
# be tens of kilobytes, so 500k entries is anywhere from 150MB to several GB
|
|
# depending on what the traffic asked for.
|
|
cache_max_bytes: "0"
|
|
|
|
cache:
|
|
enabled: true
|
|
size: 200000
|
|
shards: 32
|
|
min_ttl: 0s
|
|
max_ttl: 1h
|
|
# Keep expired answers around this long so a dead upstream costs latency,
|
|
# not availability.
|
|
serve_stale: 30m
|
|
prefetch: true
|
|
negative_ttl: 30m
|
|
persist:
|
|
enabled: true
|
|
path: data/cache.db
|
|
interval: 30s
|
|
max_entries: 500000
|
|
load_on_start: true
|
|
prewarm: []
|
|
|
|
blocklist:
|
|
enabled: true
|
|
# nxdomain | zero | redirect | drop | refused
|
|
default_action: nxdomain
|
|
response_ip: 0.0.0.0
|
|
response_ipv6: "::"
|
|
response_ttl: 60s
|
|
# RPZ-style policy zones and/or local authoritative data, as master files.
|
|
local_zones: []
|
|
# Inline rules. "*.example.com" covers subdomains, "**.example.com" covers
|
|
# the apex too, a bare name matches only itself.
|
|
block_entries: []
|
|
allow_entries: []
|
|
lists:
|
|
- name: haas
|
|
format: plain
|
|
url: https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
|
|
path: data/lists/hosts
|
|
enabled: true
|
|
refresh: 24h
|
|
categories: [ads, tracking]
|
|
- name: easylist
|
|
format: adblock
|
|
url: https://easylist.to/easylist/easylist.txt
|
|
path: data/lists/easylist.txt
|
|
enabled: false
|
|
refresh: 24h
|
|
categories: [ads]
|
|
- name: local-rpz
|
|
format: rpz
|
|
path: zones/local.rpz.zone
|
|
enabled: true
|
|
refresh: 0s
|
|
|
|
acl:
|
|
# allow | deny | block_only
|
|
default_policy: allow
|
|
default_rate_limit: 0
|
|
groups:
|
|
- name: guests
|
|
comment: Blocklists apply, but the kitchen-sink allowlist does not.
|
|
match:
|
|
cidrs: ["192.168.90.0/24"]
|
|
policy: block_only
|
|
rate_limit: 50
|
|
- name: printers
|
|
comment: No recursion at all; they only need DHCP.
|
|
match:
|
|
cidrs: ["192.168.91.0/24"]
|
|
policy: deny
|
|
|
|
# One JSON line on stdout per query worth looking at, for whatever collects the
|
|
# pod's logs (Loki, a Grafana agent, `kubectl logs`). Nothing is stored here;
|
|
# the metrics count everything. The default is blocked queries and failures.
|
|
access_log:
|
|
enabled: true
|
|
log_blocked: true # blocklist hits and redirects
|
|
log_errors: true # SERVFAIL, drops, ACL refusals
|
|
log_cached: false # also cache hits
|
|
# Fraction of ordinary answers to log as well: 0 none, 1 all. For a short
|
|
# debugging session; leave at 0 otherwise.
|
|
sample_rate: 0
|
|
buffer: 4096 # lines that may wait for the log stream before dropping
|
|
|
|
cluster:
|
|
enabled: false
|
|
# node_id: dns-1
|
|
# advertise: http://10.0.0.10:8080
|
|
peer_ttl: 15s
|
|
valkey:
|
|
addrs: ["valkey:6379"]
|
|
prefix: fancydns
|
|
pool_size: 10
|
|
dial_timeout: 2s
|
|
read_timeout: 2s
|
|
# Only the lease holder fetches lists, so N instances do not hit a
|
|
# mirror N times.
|
|
leader_ttl: 30s
|
|
|
|
api:
|
|
enabled: true
|
|
listen: ["127.0.0.1:8080"]
|
|
# tls_listen: ["0.0.0.0:8443"]
|
|
base_path: /api
|
|
ui: true
|
|
allow_config_write: true
|
|
allow_blocklist_write: true
|
|
# Only these proxies may set X-Forwarded-For; anything else is ignored, so a
|
|
# client cannot spoof its identity past the ACL or the rate limiter.
|
|
trusted_proxies: []
|
|
cors_origins: []
|
|
# Optional buttons on the console's Status page. The resolver keeps no traffic
|
|
# history or query log of its own; these point at where they live.
|
|
# links:
|
|
# grafana: https://grafana.example/d/fancy-dns
|
|
# logs: https://grafana.example/explore?left=...
|
|
auth:
|
|
# none | apikey | basic | oidc
|
|
mode: apikey
|
|
api_keys: ["CHANGE-ME-long-random-string"]
|
|
read_only_api_keys: []
|
|
trust_loopback: true
|
|
session_ttl: 12h
|
|
# For mode: oidc, configure a registered public PKCE client and the API
|
|
# audience included in its access tokens. Users need the admin role to write.
|
|
oidc_issuer: https://users.k6n.net
|
|
oidc_audience: https://fancy-dns.example.net
|
|
oidc_client_id: fancy-dns
|
|
oidc_write_roles: [admin]
|
|
rate_limit:
|
|
per_minute: 600
|
|
failures: 10
|
|
lockout: 5m
|
|
|
|
metrics:
|
|
enabled: true
|
|
path: /metrics
|
|
protect: true
|
|
|
|
log:
|
|
level: info
|
|
format: text
|