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);

View File

@@ -0,0 +1,9 @@
---
- name: restart dhcpcd
ansible.builtin.shell: |
set -euo pipefail
dhcpcd -k eth0 || true
sleep 1
dhcpcd -f /etc/dhcpcd.conf eth0
args:
executable: /bin/bash

View File

@@ -0,0 +1,4 @@
```bash
cat /etc/resolv.conf
getent hosts ntp-edge.infra.hran
```

View File

@@ -0,0 +1,9 @@
---
- name: render dhcpcd.conf (DNS override)
ansible.builtin.template:
src: dhcpcd.conf.j2
dest: /etc/dhcpcd.conf
owner: root
group: root
mode: "0644"
notify: restart dhcpcd

View File

@@ -0,0 +1,45 @@
# A sample configuration for dhcpcd.
# See dhcpcd.conf(5) for details.
# Allow users of this group to interact with dhcpcd via the control socket.
#controlgroup wheel
# Inform the DHCP server of our hostname for DDNS.
hostname
# Use the hardware address of the interface for the Client ID.
#clientid
# or
# Use the same DUID + IAID as set in DHCPv6 for DHCPv4 ClientID as per RFC4361.
# Some non-RFC compliant DHCP servers do not reply with this set.
# In this case, comment out duid and enable clientid above.
duid
# Persist interface configuration when dhcpcd exits.
persistent
# vendorclassid is set to blank to avoid sending the default of
# dhcpcd-<version>:<os>:<machine>:<platform>
vendorclassid
# A list of options to request from the DHCP server.
option domain_name_servers, domain_name, domain_search
option classless_static_routes
# Respect the network MTU. This is applied to DHCP routes.
option interface_mtu
# Request a hostname from the network
option host_name
# Most distributions have NTP support.
#option ntp_servers
# A ServerID is required by RFC2131.
require dhcp_server_identifier
# Generate SLAAC address using the Hardware Address of the interface
#slaac hwaddr
# OR generate Stable Private IPv6 Addresses based from the DUID
slaac private
static domain_name_servers=192.168.0.100 1.1.1.1 8.8.8.8