This commit is contained in:
Hrankin, Aleksandr (contracted)
2026-02-19 11:34:13 +00:00
commit f243f440c3
191 changed files with 6183 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
# example dns path in Debian13
App → glibc resolver → /etc/resolv.conf (127.0.0.53) → systemd-resolved → 192.168.0.1 (Proxmox)
# before role running
```bash
sudo systemctl disable --now systemd-resolved
sudo rm -f /etc/resolv.conf
echo -e "nameserver 1.1.1.1\nnameserver 8.8.8.8" | sudo tee /etc/resolv.conf
docker compose down
docker compose up -d
```
```bash
# pdns-auth web/api через nginx
curl -i -H 'Host: auth.infra.hran' http://127.0.0.1/
# recursor web/api через nginx
curl -i -H 'Host: recursor.infra.hran' http://127.0.0.1/
# dnsdist web через nginx
curl -i -H 'Host: dnsdist.infra.hran' http://127.0.0.1/
curl -i -u 'admin:CHANGE_ME_DNSDIST_WEB_PASSWORD' -H 'Host: dnsdist.infra.hran' http://127.0.0.1/
# windows
C:\Windows\System32\drivers\etc\hosts
127.0.0.1 auth.infra.hran
127.0.0.1 recursor.infra.hran
127.0.0.1 dnsdist.infra.hran:8084
# check from browser
http://dnsdist.infra.hran:8080/
http://auth.infra.hran:8080/
http://recursor.infra.hran:8080/
```

View File

@@ -0,0 +1,37 @@
- name: ensure directory structure exists
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "root"
group: "root"
mode: "0755"
loop:
- "{{ dns_stack_root }}"
- "{{ dns_stack_root }}/postgres/initdb"
- "{{ dns_stack_root }}/pdns-auth"
- "{{ dns_stack_root }}/pdns-recursor"
- "{{ dns_stack_root }}/dnsdist"
- "{{ dns_stack_root }}/nginx"
- name: render stack files
ansible.builtin.template:
src: "{{ item.src }}"
dest: "{{ dns_stack_root }}/{{ item.dest }}"
owner: "root"
group: "root"
mode: "0644"
loop:
- { src: "docker-compose.yml.j2", dest: "docker-compose.yml" }
- { src: ".env.j2", dest: ".env", mode: "0600" }
- {
src: "postgres/initdb/01-pdns-schema.sql.j2",
dest: "postgres/initdb/01-pdns-schema.sql",
}
- { src: "pdns-auth/pdns.conf.j2", dest: "pdns-auth/pdns.conf" }
- {
src: "pdns-recursor/recursor.conf.j2",
dest: "pdns-recursor/recursor.conf",
}
- { src: "dnsdist/dnsdist.conf.j2", dest: "dnsdist/dnsdist.conf" }
- { src: "nginx/nginx.conf.j2", dest: "nginx/nginx.conf" }
register: rendered

View File

@@ -0,0 +1,41 @@
addLocal("0.0.0.0:53")
addLocal("[::]:53")
-- ACL для клиентов, которым вообще можно отвечать
addACL("127.0.0.0/8") -- localhost на IPv4 (машина сама себе).
addACL("10.0.0.0/8") -- приватные сети RFC1918 (часто VPN/корп сеть).
addACL("172.16.0.0/12") -- приватные 172.16172.31 (сюда попадает и 172.30.x, docker-сеть).
addACL("192.168.0.0/16") -- типичная домашняя LAN.
addACL("::1/128") -- localhost на IPv6.
addACL("fc00::/7") -- IPv6 ULA (аналог приватных)
addACL("fe80::/10") --IPv6 link-local (адреса “на линке”, часто у интерфейса).
newServer({
address="172.30.0.11:5300",
pool="auth",
name="pdns-auth"
})
newServer({
address="172.30.0.12:5301",
pool="recursor",
name="pdns-recursor"
})
-- Авторитативные зоны -> в pool auth, остальное -> recursor
local authZones = newSuffixMatchNode()
authZones:add("infra.hran.")
pc = newPacketCache(100000, {maxTTL=86400, minTTL=0, temporaryFailureTTL=60})
getPool("recursor"):setCache(pc)
getPool("auth"):setCache(pc)
addAction(SuffixMatchNodeRule(authZones), PoolAction("auth"))
addAction(AllRule(), PoolAction("recursor"))
webserver("0.0.0.0:8084")
setWebserverConfig({
password="CHANGE_ME_DNSDIST_WEB_PASSWORD",
apiKey="CHANGE_ME_DNSDIST_KEY",
acl="127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, ::1/128, fc00::/7, fe80::/10"
})

View File

@@ -0,0 +1,142 @@
services:
postgres:
image: postgres:16
container_name: dnsstack-postgres
restart: unless-stopped
environment:
TZ: Europe/Kyiv
POSTGRES_DB: pdns
POSTGRES_USER: pdns
POSTGRES_PASSWORD: CHANGE_ME_POSTGRES_PASSWORD
volumes:
- /opt/dns-stack/postgres/data:/var/lib/postgresql/data
- ./postgres/initdb:/docker-entrypoint-initdb.d:ro
networks:
dnsnet:
ipv4_address: "172.30.0.10"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB} -h 127.0.0.1 -p 5432"]
interval: 2s
timeout: 3s
retries: 30
start_period: 10s
logging:
driver: "json-file"
options:
tag: "dnsstack.postgres"
max-size: "20m"
max-file: "10"
pdns-auth:
image: powerdns/pdns-auth-50:latest
container_name: dnsstack-pdns-auth
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
TZ: Europe/Kyiv
volumes:
- ./pdns-auth/pdns.conf:/etc/powerdns/pdns.conf:ro
networks:
dnsnet:
ipv4_address: "172.30.0.11"
expose:
- "5300"
- "8083"
ulimits:
nofile:
soft: 10064
hard: 10064
logging:
driver: "json-file"
options:
tag: "dnsstack.pdns-auth"
max-size: "20m"
max-file: "10"
pdns-recursor:
image: powerdns/pdns-recursor-53:latest
container_name: dnsstack-pdns-recursor
restart: unless-stopped
environment:
TZ: Europe/Kyiv
volumes:
- ./pdns-recursor/recursor.conf:/etc/powerdns/recursor.conf:ro
networks:
dnsnet:
ipv4_address: "172.30.0.12"
expose:
- "5301"
- "8082"
ulimits:
nofile:
soft: 10064
hard: 10064
logging:
driver: "json-file"
options:
tag: "dnsstack.pdns-recursor"
max-size: "20m"
max-file: "10"
dnsdist:
image: powerdns/dnsdist-20:latest
container_name: dnsstack-dnsdist
restart: unless-stopped
depends_on:
- pdns-auth
- pdns-recursor
environment:
TZ: Europe/Kyiv
volumes:
- ./dnsdist/dnsdist.conf:/etc/dnsdist/dnsdist.conf:ro
networks:
dnsnet:
ipv4_address: "172.30.0.2"
ports:
- "53:53/udp"
- "53:53/tcp"
expose:
- "8084"
ulimits:
nofile:
soft: 65535
hard: 65535
logging:
driver: "json-file"
options:
tag: "dnsstack.dnsdist"
max-size: "50m"
max-file: "10"
nginx:
image: nginx:1.27-alpine
container_name: dnsstack-nginx
restart: unless-stopped
depends_on:
- pdns-auth
- pdns-recursor
- dnsdist
environment:
TZ: Europe/Kyiv
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
networks:
dnsnet:
ipv4_address: "172.30.0.3"
ports:
- "80:80/tcp"
logging:
driver: "json-file"
options:
tag: "dnsstack.nginx"
max-size: "20m"
max-file: "10"
networks:
dnsnet:
driver: bridge
ipam:
config:
- subnet: "172.30.0.0/24"

View File

@@ -0,0 +1,53 @@
worker_processes auto;
events { worker_connections 1024; }
http {
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
# auth.infra.hran -> pdns-auth:8083
server {
listen 80;
server_name auth.infra.hran;
location / {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://pdns-auth:8083;
}
}
# recursor.infra.hran -> pdns-recursor:8082
server {
listen 80;
server_name recursor.infra.hran;
location / {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://pdns-recursor:8082;
}
}
# dnsdist.infra.hran -> dnsdist:8084
server {
listen 80;
server_name dnsdist.infra.hran;
location / {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://dnsdist:8084;
}
}
}

View File

@@ -0,0 +1,21 @@
local-address=0.0.0.0,::
local-port=5300
launch=gpgsql
gpgsql-host=postgres
gpgsql-port=5432
gpgsql-dbname=pdns
gpgsql-user=pdns
gpgsql-password=CHANGE_ME_POSTGRES_PASSWORD
api=yes
api-key=CHANGE_ME_PDNS_API_KEY
webserver=yes
webserver-address=0.0.0.0
webserver-port=8083
webserver-allow-from=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
disable-axfr=yes
version-string=anonymous
loglevel=4

View File

@@ -0,0 +1,46 @@
# PowerDNS Recursor 5.1+ YAML config
incoming:
listen:
- "0.0.0.0:5301"
- "[::]:5301"
allow_from:
- "127.0.0.0/8"
- "10.0.0.0/8"
- "172.16.0.0/12"
- "192.168.0.0/16"
- "::1/128"
- "fc00::/7"
- "fe80::/10"
outgoing:
source_address:
- "0.0.0.0"
- "::"
webservice:
webserver: true
address: "0.0.0.0"
port: 8082
api_key: "CHANGE_ME_RECURSOR_API_KEY"
allow_from:
- "127.0.0.0/8"
- "10.0.0.0/8"
- "172.16.0.0/12"
- "192.168.0.0/16"
- "::1/128"
- "fc00::/7"
- "fe80::/10"
logging:
loglevel: 6
quiet: false
recursor:
version_string: "anonymous"
forward_zones_recurse:
- zone: "."
forwarders:
- "1.1.1.1"
- "8.8.8.8"

View File

@@ -0,0 +1,103 @@
-- PowerDNS Generic PostgreSQL schema (gpgsql)
-- Source: PowerDNS pdns/modules/gpgsqlbackend/schema.pgsql.sql
CREATE TABLE domains (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
master VARCHAR(128) DEFAULT NULL,
last_check INT DEFAULT NULL,
type TEXT NOT NULL,
notified_serial INT DEFAULT NULL,
account VARCHAR(40) DEFAULT NULL,
options TEXT DEFAULT NULL,
catalog VARCHAR(255) DEFAULT NULL
);
CREATE UNIQUE INDEX name_index ON domains(name);
CREATE INDEX catalog_idx ON domains(catalog);
CREATE TABLE records (
id BIGSERIAL PRIMARY KEY,
domain_id INT DEFAULT NULL,
name VARCHAR(255) DEFAULT NULL,
type VARCHAR(10) DEFAULT NULL,
content VARCHAR(65535) DEFAULT NULL,
ttl INT DEFAULT NULL,
prio INT DEFAULT NULL,
disabled BOOL DEFAULT 'f',
ordername VARCHAR(255),
auth BOOL DEFAULT 't'
);
CREATE INDEX rec_name_index ON records(name);
CREATE INDEX nametype_index ON records(name, type);
CREATE INDEX domain_id ON records(domain_id);
CREATE INDEX ordername ON records(ordername);
CREATE TABLE supermasters (
ip INET NOT NULL,
nameserver VARCHAR(255) NOT NULL,
account VARCHAR(40) NOT NULL,
PRIMARY KEY (ip, nameserver)
);
CREATE TABLE comments (
id SERIAL PRIMARY KEY,
domain_id INT NOT NULL,
name VARCHAR(255) NOT NULL,
type VARCHAR(10) NOT NULL,
modified_at INT NOT NULL,
account VARCHAR(40) DEFAULT NULL,
comment VARCHAR(65535) NOT NULL
);
CREATE INDEX comments_domain_id_idx ON comments(domain_id);
CREATE INDEX comments_name_type_idx ON comments(name, type);
CREATE INDEX comments_order_idx ON comments(domain_id, modified_at);
CREATE TABLE domainmetadata (
id SERIAL PRIMARY KEY,
domain_id INT NOT NULL,
kind VARCHAR(32),
content TEXT
);
CREATE INDEX domainmetadata_idx ON domainmetadata(domain_id, kind);
CREATE TABLE cryptokeys (
id SERIAL PRIMARY KEY,
domain_id INT NOT NULL,
flags INT NOT NULL,
active BOOL,
published BOOL DEFAULT TRUE,
content TEXT
);
CREATE INDEX domainidindex ON cryptokeys(domain_id);
CREATE TABLE tsigkeys (
id SERIAL PRIMARY KEY,
name VARCHAR(255),
algorithm VARCHAR(50),
secret VARCHAR(255)
);
CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);
CREATE TABLE luarecords (
id SERIAL PRIMARY KEY,
domain_id INT NOT NULL,
name VARCHAR(255) NOT NULL,
type VARCHAR(10) NOT NULL,
content VARCHAR(65535) NOT NULL,
ttl INT NOT NULL,
prio INT DEFAULT NULL,
disabled BOOL DEFAULT 'f',
ordername VARCHAR(255),
auth BOOL DEFAULT 't'
);
CREATE INDEX luarecord_name_index ON luarecords(name);
CREATE INDEX luarecord_nametype_index ON luarecords(name, type);
CREATE INDEX luarecord_domain_id ON luarecords(domain_id);
CREATE INDEX luarecord_ordername ON luarecords(ordername);