pdns-server 安裝

2024-08-09 工作雜記 powerdns

以下是我初次建立 powerdns 服務的紀錄

環境

  • OS: Debian 12
  • DB: postgresql-14
  • pdns-server version: 4.7

事前準備

  1. 安裝 postgresql
  2. 建立資料庫
sudo su - postgres
psql
CREATE DATABASE pdnsdb;
CREATE USER pdns WITH PASSWORD 'pdnsPassword';
GRANT CONNECT ON DATABASE pdnsdb TO pdns;
ALTER DATABASE pdnsdb OWNER TO pdns;

建置 table schema,可以在說明文件中找到: /usr/share/doc/pdns-backend-pgsql/schema.pgsql.sql

並執行下列指令

psql -h <host> -U <user> <database> -f /usr/share/pdns-backend-pgsql/schema/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       BIGINT DEFAULT NULL,
  account               VARCHAR(40) DEFAULT NULL,
  options               TEXT DEFAULT NULL,
  catalog               TEXT DEFAULT NULL,
  CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
);

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',
  CONSTRAINT domain_exists
  FOREIGN KEY(domain_id) REFERENCES domains(id)
  ON DELETE CASCADE,
  CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
);

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 recordorder ON records (domain_id, ordername text_pattern_ops);


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,
  CONSTRAINT domain_exists
  FOREIGN KEY(domain_id) REFERENCES domains(id)
  ON DELETE CASCADE,
  CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
);

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 REFERENCES domains(id) ON DELETE CASCADE,
  kind                  VARCHAR(32),
  content               TEXT
);

CREATE INDEX domainidmetaindex ON domainmetadata(domain_id);


CREATE TABLE cryptokeys (
  id                    SERIAL PRIMARY KEY,
  domain_id             INT REFERENCES domains(id) ON DELETE CASCADE,
  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),
  CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
);

CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);

安裝事前作業

  • 停用 systemd-resolved
sudo systemctl disable --now systemd-resolved

設定穩定版本來源

設定參考官方說明: PowerDNS repositories

  • /etc/apt/sources.list.d/pdns-stable.list
# PowerDNS Authoritative Server - stable branch
deb [signed-by=/etc/apt/keyrings/auth-49-pub.asc arch=amd64] http://repo.powerdns.com/debian bookworm-auth-49 main

# PowerDNS Recursor - stable branch
deb [signed-by=/etc/apt/keyrings/rec-51-pub.asc arch=amd64] http://repo.powerdns.com/debian bookworm-rec-51 main
  • /etc/apt/preferences.d/auth-49
Package: auth*
Pin: origin repo.powerdns.com
Pin-Priority: 600
  • /etc/apt/preferences.d/rec-51
Package: rec*
Pin: origin repo.powerdns.com
Pin-Priority: 600

安裝 PowerDNS

sudo install -d /etc/apt/keyrings; curl https://repo.powerdns.com/FD380FBB-pub.asc | sudo tee /etc/apt/keyrings/auth-49-pub.asc
sudo install -d /etc/apt/keyrings; curl https://repo.powerdns.com/FD380FBB-pub.asc | sudo tee /etc/apt/keyrings/rec-51-pub.asc
apt install -y pdns-server pdns-backend-pgsql pdns-recursor
cp /usr/share/doc/pdns-backend-pgsql/examples/gpgsql.conf /etc/powerdns/pdns.d/gpgsql.conf
chmod 0640 /etc/powerdns/pdns.d/gpgsql.conf
chgrp pdns /etc/powerdns/pdns.d/gpgsql.conf
  • 設定文件: /etc/powerdns/recursor.conf
#################################
# api-key	Static pre-shared authentication key for access to the REST API
#
# api-key=
api-key=<api-key>

#################################
# webserver     Start a webserver (for REST API)
#
# webserver=no
webserver=yes

#################################
# webserver-port        Port of webserver to listen on
#
# webserver-port=8082
webserver-port=8082

#################################
# webserver-address     IP Address of webserver to listen on
#
# webserver-address=127.0.0.1
webserver-address=0.0.0.0

#################################
# webserver-allow-from  Webserver access is only allowed from these subnets
#
# webserver-allow-from=127.0.0.1,::1
webserver-allow-from=0.0.0.0/0

#################################
# allow-from	If set, only allow these comma separated netmasks to recurse
#
# allow-from=127.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fc00::/7, fe80::/10
#設定允許遞迥查詢內部網段
allow-from=127.0.0.1, 192.168.0.0/24  

#################################
# forward-zones	Zones for which we forward queries, comma separated domain=ip pairs
#
# forward-zones=
#轉送查詢的網域與伺服器,格式是 網域=伺服器ip,多個網域以逗點分隔
forward-zones=.=127.0.0.1:54  

#################################
# forward-zones-recurse Zones for which we forward queries with recursion bit, comma separated domain=ip pairs
#
# forward-zones-recurse=
#內部查詢不到會查到外部查詢
forward-zones-recurse=.=8.8.8.8, .=168.95.1.1 

#################################
# local-address	IP addresses to listen on, separated by spaces or commas. Also accepts ports.
#
# local-address=127.0.0.1
local-address=0.0.0.0

#################################
# local-port	port to listen on
#
# local-port=53
local-port=53

#################################
# logging-facility      Facility to log messages as. 0 corresponds to local0
#
# logging-facility=
logging-facility=0

#################################
# max-negative-ttl      maximum number of seconds to keep a negative cached entry in memory
#
# max-negative-ttl=3600
max-negative-ttl=3600

#################################
# setgid        If set, change group id to this gid for more security
#
setgid=pdns

#################################
# setuid        If set, change user id to this uid for more security
#
setuid=pdns

#################################
# config-dir	Location of configuration directory (recursor.conf)
#
config-dir=/etc/powerdns

#################################
# hint-file	If set, load root hints from this file
#
# hint-file=
hint-file=/usr/share/dns/root.hints

#################################
# include-dir	Include *.conf files from this directory
#
# include-dir=
include-dir=/etc/powerdns/recursor.d

#################################
# lua-config-file	More powerful configuration options
#
lua-config-file=/etc/powerdns/recursor.lua

#################################
# quiet	Suppress logging of questions and answers
#
quiet=yes
  • 設定文件: /etc/powerdns/pdns.conf
# Autogenerated configuration file template

#################################
# ignore-unknown-settings       Configuration settings to ignore if they are unknown
#
# ignore-unknown-settings=

#################################
# 8bit-dns      Allow 8bit dns queries
#
# 8bit-dns=no

#################################
# allow-axfr-ips        Allow zonetransfers only to these subnets
#
# allow-axfr-ips=127.0.0.0/8,::1

#################################
# allow-dnsupdate-from  A global setting to allow DNS updates from these IP ranges.
#
# allow-dnsupdate-from=127.0.0.0/8,::1

#################################
# allow-notify-from     Allow AXFR NOTIFY from these IP ranges. If empty, drop all incoming notifies.
#
# allow-notify-from=0.0.0.0/0,::/0

#################################
# allow-unsigned-autoprimary    Allow autoprimaries to create zones without TSIG signed NOTIFY
#
# allow-unsigned-autoprimary=yes

#################################
# allow-unsigned-notify Allow unsigned notifications for TSIG secured zones
#
# allow-unsigned-notify=yes

#################################
# allow-unsigned-supermaster    Allow supermasters to create zones without TSIG signed NOTIFY
#
# allow-unsigned-supermaster=yes

#################################
# also-notify   When notifying a zone, also notify these nameservers
#
# also-notify=

#################################
# any-to-tcp    Answer ANY queries with tc=1, shunting to TCP
#
# any-to-tcp=yes

#################################
# api   Enable/disable the REST API (including HTTP listener)
#
api=yes

#################################
# api-key       Static pre-shared authentication key for access to the REST API
#
api-key=<api-key>

#################################
# autosecondary Act as an autosecondary (formerly superslave)
#
# autosecondary=no

#################################
# axfr-fetch-timeout    Maximum time in seconds for inbound AXFR to start or be idle after starting
#
# axfr-fetch-timeout=10

#################################
# axfr-lower-serial     Also AXFR a zone from a master with a lower serial
#
# axfr-lower-serial=no

#################################
# cache-ttl     Seconds to store packets in the PacketCache
#
# cache-ttl=20

#################################
# carbon-instance       If set overwrites the instance name default
#
# carbon-instance=auth

#################################
# carbon-interval       Number of seconds between carbon (graphite) updates
#
# carbon-interval=30

#################################
# carbon-namespace      If set overwrites the first part of the carbon string
#
# carbon-namespace=pdns

#################################
# carbon-ourname        If set, overrides our reported hostname for carbon stats
#
# carbon-ourname=

#################################
# carbon-server If set, send metrics in carbon (graphite) format to this server IP address
#
# carbon-server=

#################################
# chroot        If set, chroot to this directory for more security
#
# chroot=

#################################
# config-dir    Location of configuration directory (pdns.conf)
#
# config-dir=/etc/powerdns

#################################
# config-name   Name of this virtual configuration - will rename the binary image
#
# config-name=

#################################
# consistent-backends   Assume individual zones are not divided over backends. Send only ANY lookup operations to the backend to reduce the number of lookups
#
# consistent-backends=yes

#################################
# control-console       Debugging switch - don't use
#
# control-console=no

#################################
# daemon        Operate as a daemon

daemon=yes

#################################
# default-api-rectify   Default API-RECTIFY value for zones
#
# default-api-rectify=yes

#################################
# default-ksk-algorithm Default KSK algorithm
#
# default-ksk-algorithm=ecdsa256

#################################
# default-ksk-size      Default KSK size (0 means default)
#
# default-ksk-size=0

#################################
# default-publish-cdnskey       Default value for PUBLISH-CDNSKEY
#
# default-publish-cdnskey=

#################################
# default-publish-cds   Default value for PUBLISH-CDS
#
# default-publish-cds=

#################################
# default-soa-content   Default SOA content
#
# default-soa-content=a.misconfigured.dns.server.invalid hostmaster.@ 0 10800 3600 604800 3600

#################################
# default-soa-edit      Default SOA-EDIT value
#
# default-soa-edit=

#################################
# default-soa-edit-signed       Default SOA-EDIT value for signed zones
#
# default-soa-edit-signed=

#################################
# default-ttl   Seconds a result is valid if not set otherwise
#
# default-ttl=3600

#################################
# default-zsk-algorithm Default ZSK algorithm
#
# default-zsk-algorithm=

#################################
# default-zsk-size      Default ZSK size (0 means default)
#
# default-zsk-size=0

#################################
# direct-dnskey Fetch DNSKEY, CDS and CDNSKEY RRs from backend during DNSKEY or CDS/CDNSKEY synthesis
#
# direct-dnskey=no

#################################
# disable-axfr  Disable zonetransfers but do allow TCP queries
#
# disable-axfr=no

#################################
# disable-axfr-rectify  Disable the rectify step during an outgoing AXFR. Only required for regression testing.
#
# disable-axfr-rectify=no

#################################
# disable-syslog        Disable logging to syslog, useful when running inside a supervisor that logs stdout
#
# disable-syslog=no

#################################
# distributor-threads   Default number of Distributor (backend) threads to start
#
# distributor-threads=3

#################################
# dname-processing      If we should support DNAME records
#
# dname-processing=no

#################################
# dnssec-key-cache-ttl  Seconds to cache DNSSEC keys from the database
#
# dnssec-key-cache-ttl=30

#################################
# dnsupdate     Enable/Disable DNS update (RFC2136) support. Default is no.
#
# dnsupdate=no

#################################
# domain-metadata-cache-ttl     Seconds to cache zone metadata from the database
#
# domain-metadata-cache-ttl=

#################################
# edns-cookie-secret    When set, set a server cookie when responding to a query with a Client cookie (in hex)
#
# edns-cookie-secret=

#################################
# edns-subnet-processing        If we should act on EDNS Subnet options
#
# edns-subnet-processing=no

#################################
# enable-lua-records    Process LUA records for all zones (metadata overrides this)
#
# enable-lua-records=no

#################################
# entropy-source        If set, read entropy from this file
#
# entropy-source=/dev/urandom

#################################
# expand-alias  Expand ALIAS records
#
# expand-alias=no

#################################
# forward-dnsupdate     A global setting to allow DNS update packages that are for a Slave zone, to be forwarded to the master.
#
# forward-dnsupdate=yes

#################################
# forward-notify        IP addresses to forward received notifications to regardless of master or slave settings
#
# forward-notify=

#################################
# guardian      Run within a guardian process
#
# guardian=no

#################################
# include-dir   Include *.conf files from this directory
#
# include-dir=
include-dir=/etc/powerdns/pdns.d

#################################
# launch        Which backends to launch and order to query them in
#
# launch=
launch=

#################################
# load-modules  Load this module - supply absolute or relative path
#
# load-modules=

#################################
# local-address Local IP addresses to which we bind
#
local-address=0.0.0.0, ::

#################################
# local-address-nonexist-fail   Fail to start if one or more of the local-address's do not exist on this server
#
# local-address-nonexist-fail=yes

#################################
# local-port    The port on which we listen
#
local-port=54

#################################
# log-dns-details       If PDNS should log DNS non-erroneous details
#
log-dns-details=yes

#################################
# log-dns-queries       If PDNS should log all incoming DNS queries
#
log-dns-queries=yes

#################################
# log-timestamp Print timestamps in log lines
log-timestamp=yes

#################################
# logging-facility      Log under a specific facility
#
logging-facility=0

#################################
# loglevel      Amount of logging. Higher is more. Do not set below 3
#
loglevel=4

#################################
# lua-axfr-script       Script to be used to edit incoming AXFRs
#
# lua-axfr-script=

#################################
# lua-dnsupdate-policy-script   Lua script with DNS update policy handler
#
# lua-dnsupdate-policy-script=

#################################
# lua-health-checks-expire-delay        Stops doing health checks after the record hasn't been used for that delay (in seconds)
#
# lua-health-checks-expire-delay=3600

#################################
# lua-health-checks-interval    LUA records health checks monitoring interval in seconds
#
# lua-health-checks-interval=5

#################################
# lua-prequery-script   Lua script with prequery handler (DO NOT USE)
#
# lua-prequery-script=

#################################
# lua-records-exec-limit        LUA records scripts execution limit (instructions count). Values <= 0 mean no limit
#
# lua-records-exec-limit=1000

#################################
# master        Act as a primary
#
# master=no

#################################
# max-cache-entries     Maximum number of entries in the query cache
#
# max-cache-entries=1000000

#################################
# max-ent-entries       Maximum number of empty non-terminals in a zone
#
# max-ent-entries=100000

#################################
# max-generate-steps    Maximum number of $GENERATE steps when loading a zone from a file
#
# max-generate-steps=0

#################################
# max-include-depth     Maximum number of nested $INCLUDE directives while processing a zone file
#
# max-include-depth=20

#################################
# max-nsec3-iterations  Limit the number of NSEC3 hash iterations
#
# max-nsec3-iterations=100

#################################
# max-packet-cache-entries      Maximum number of entries in the packet cache
#
# max-packet-cache-entries=1000000

#################################
# max-queue-length      Maximum queuelength before considering situation lost
#
# max-queue-length=5000

#################################
# max-signature-cache-entries   Maximum number of signatures cache entries
#
# max-signature-cache-entries=

#################################
# max-tcp-connection-duration   Maximum time in seconds that a TCP DNS connection is allowed to stay open.
#
# max-tcp-connection-duration=0

#################################
# max-tcp-connections   Maximum number of TCP connections
#
# max-tcp-connections=20

#################################
# max-tcp-connections-per-client        Maximum number of simultaneous TCP connections per client
#
# max-tcp-connections-per-client=0

#################################
# max-tcp-transactions-per-conn Maximum number of subsequent queries per TCP connection
#
# max-tcp-transactions-per-conn=0

#################################
# module-dir    Default directory for modules
#


#################################
# negquery-cache-ttl    Seconds to store negative query results in the QueryCache
#
# negquery-cache-ttl=60

#################################
# no-shuffle    Set this to prevent random shuffling of answers - for regression testing
#
# no-shuffle=off

#################################
# non-local-bind        Enable binding to non-local addresses by using FREEBIND / BINDANY socket options
#
# non-local-bind=no

#################################
# only-notify   Only send AXFR NOTIFY to these IP addresses or netmasks
#
# only-notify=0.0.0.0/0,::/0

#################################
# outgoing-axfr-expand-alias    Expand ALIAS records during outgoing AXFR
#
# outgoing-axfr-expand-alias=no

#################################
# overload-queue-length Maximum queuelength moving to packetcache only
#
# overload-queue-length=0

#################################
# prevent-self-notification     Don't send notifications to what we think is ourself
#
# prevent-self-notification=yes

#################################
# primary       Act as a primary
#
# primary=no

#################################
# proxy-protocol-from   A Proxy Protocol header is only allowed from these subnets, and is mandatory then too.
#
# proxy-protocol-from=

#################################
# proxy-protocol-maximum-size   The maximum size of a proxy protocol payload, including the TLV values
#
# proxy-protocol-maximum-size=512

#################################
# query-cache-ttl       Seconds to store query results in the QueryCache
#
# query-cache-ttl=20

#################################
# query-local-address   Source IP addresses for sending queries
#
# query-local-address=0.0.0.0 ::

#################################
# query-logging Hint backends that queries should be logged
#
query-logging=yes

#################################
# queue-limit   Maximum number of milliseconds to queue a query
#
# queue-limit=1500

#################################
# receiver-threads      Default number of receiver threads to start
#
# receiver-threads=1

#################################
# resolver      Use this resolver for ALIAS and the internal stub resolver
#
# resolver=no

#################################
# retrieval-threads     Number of AXFR-retrieval threads for slave operation
#
# retrieval-threads=2

#################################
# reuseport     Enable higher performance on compliant kernels by using SO_REUSEPORT allowing each receiver thread to open its own socket
#
# reuseport=no

#################################
# rng   Specify the random number generator to use. Valid values are auto,sodium,openssl,getrandom,arc4random,urandom.
#
# rng=auto

#################################
# secondary     Act as a secondary
#
# secondary=no

#################################
# secondary-check-signature-freshness   Check signatures in SOA freshness check. Sets DO flag on SOA queries. Outside some very problematic scenarios, say yes here.
#
# secondary-check-signature-freshness=yes

#################################
# secondary-do-renotify If this secondary should send out notifications after receiving zone transfers from a primary
#
# secondary-do-renotify=no

#################################
# security-poll-suffix  Zone name from which to query security update notifications
#
# security-poll-suffix=secpoll.powerdns.com.
security-poll-suffix=

#################################
# send-signed-notify    Send TSIG secured NOTIFY if TSIG key is configured for a zone
#
# send-signed-notify=yes

#################################
# server-id     Returned when queried for 'id.server' TXT or NSID, defaults to hostname - disabled or custom
#
# server-id=

#################################
# setgid        If set, change group id to this gid for more security
#
setgid=pdns

#################################
# setuid        If set, change user id to this uid for more security
#
setuid=pdns

#################################
# signing-threads       Default number of signer threads to start
#
# signing-threads=3

#################################
# slave Act as a secondary
#
# slave=no

#################################
# slave-cycle-interval  Schedule slave freshness checks once every .. seconds
#
# slave-cycle-interval=60

#################################
# slave-renotify        If we should send out notifications for secondaried updates
#
# slave-renotify=no

#################################
# socket-dir    Where the controlsocket will live, /var/run/pdns when unset and not chrooted. Set to the RUNTIME_DIRECTORY environment variable when that variable has a value (e.g. under systemd).
#
# socket-dir=

#################################
# superslave    Act as a autosecondary
#
# superslave=no

#################################
# svc-autohints Transparently fill ipv6hint=auto ipv4hint=auto SVC params with AAAA/A records for the target name of the record (if within the same zone)
#
# svc-autohints=no

#################################
# tcp-control-address   If set, PowerDNS can be controlled over TCP on this address
#
# tcp-control-address=

#################################
# tcp-control-port      If set, PowerDNS can be controlled over TCP on this address
#
# tcp-control-port=53000

#################################
# tcp-control-range     If set, remote control of PowerDNS is possible over these networks only
#
# tcp-control-range=127.0.0.0/8, 10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fe80::/10

#################################
# tcp-control-secret    If set, PowerDNS can be controlled over TCP after passing this secret
#
# tcp-control-secret=

#################################
# tcp-fast-open Enable TCP Fast Open support on the listening sockets, using the supplied numerical value as the queue size
#
# tcp-fast-open=0

#################################
# tcp-idle-timeout      Maximum time in seconds that a TCP DNS connection is allowed to stay open while being idle
#
# tcp-idle-timeout=5

#################################
# traceback-handler     Enable the traceback handler (Linux only)
#
# traceback-handler=yes

#################################
# trusted-notification-proxy    IP address of incoming notification proxy
#
# trusted-notification-proxy=

#################################
# udp-truncation-threshold      Maximum UDP response size before we truncate
#
# udp-truncation-threshold=1232

#################################
# upgrade-unknown-types Transparently upgrade known TYPExxx records. Recommended to keep off, except for PowerDNS upgrades until data sources are cleaned up
#
# upgrade-unknown-types=no

#################################
# version-string        PowerDNS version in packets - full, anonymous, powerdns or custom
#
# version-string=full

#################################
# webserver     Start a webserver for monitoring (api=yes also enables the HTTP listener)
#
webserver=yes

#################################
# webserver-address     IP Address of webserver/API to listen on
#
webserver-address=0.0.0.0

#################################
# webserver-allow-from  Webserver/API access is only allowed from these subnets
#
webserver-allow-from=0.0.0.0/0

#################################
# webserver-hash-plaintext-credentials  Whether to hash passwords and api keys supplied in plaintext, to prevent keeping the plaintext version in memory at runtime
#
# webserver-hash-plaintext-credentials=no

#################################
# webserver-loglevel    Amount of logging in the webserver (none, normal, detailed)
#
# webserver-loglevel=normal

#################################
# webserver-max-bodysize        Webserver/API maximum request/response body size in megabytes
#
# webserver-max-bodysize=2

#################################
# webserver-password    Password required for accessing the webserver
#
# webserver-password=

#################################
# webserver-port        Port of webserver/API to listen on
#
webserver-port=8081

#################################
# webserver-print-arguments     If the webserver should print arguments
#
# webserver-print-arguments=no

#################################
# write-pid     Write a PID file
#
# write-pid=yes

#################################
# xfr-cycle-interval    Schedule primary/secondary SOA freshness checks once every .. seconds
#
# xfr-cycle-interval=60

#################################
# xfr-max-received-mbytes       Maximum number of megabytes received from an incoming XFR
#
# xfr-max-received-mbytes=100

#################################
# zone-cache-refresh-interval   Seconds to cache list of known zones
#
# zone-cache-refresh-interval=300

#################################
# zone-metadata-cache-ttl       Seconds to cache zone metadata from the database
#
# zone-metadata-cache-ttl=60
  • 設定文件: /etc/powerdns/pdns.d/gpgsql.conf
# See https://doc.powerdns.com/authoritative/backends/generic-postgresql.html
launch+=gpgsql

#################################
# gpgsql-dbname Backend database name to connect to
#
# gpgsql-dbname=
gpgsql-dbname=pdnsdb

#################################
# gpgsql-dnssec Enable DNSSEC processing
#
# gpgsql-dnssec=no
gpgsql-dnssec=yes

#################################
# gpgsql-extra-connection-parameters    Extra parameters to add to connection string
#
# gpgsql-extra-connection-parameters=

#################################
# gpgsql-host   Database backend host to connect to
#
# gpgsql-host=
gpgsql-host=127.0.0.1

#################################
# gpgsql-password       Database backend password to connect with
#
# gpgsql-password=
gpgsql-password=pdnsPassword

#################################
# gpgsql-port   Database backend port to connect to
#
# gpgsql-port=
gpgsql-port=5432

#################################
# gpgsql-prepared-statements    Use prepared statements instead of parameterized queries
#
# gpgsql-prepared-statements=yes

#################################
# gpgsql-user   Database backend user to connect as
#
# gpgsql-user=
gpgsql-user=pdns
systemctl enable pdns ; systemctl restart pdns

設定 log

編輯 system daemon

systemctl edit --full pdns

移除 --disable-syslog --log-timestamp=no

  • /etc/rsyslog.d/pdns.conf
local0.info                       -/var/log/pdns/pdns.info
local0.warn                       -/var/log/pdns/pdns.warn
local0.err                        /var/log/pdns/pdns.err
mkdir -p /var/log/pdns
systemctl restart pdns rsyslog

安裝 PowerDNS-Admin

設定資料庫

CREATE DATABASE pdnsadmindb;
CREATE USER pdnsadmin WITH PASSWORD 'pdnsPassword';
GRANT CONNECT ON DATABASE pdnsadmindb TO pdnsadmin;
ALTER DATABASE pdnsadmindb OWNER TO pdnsadmin;

執行安裝

sudo -i
# 預先安裝必要套件
apt install -y python3.11-venv python3-psycopg2 libmariadb-dev libsasl2-dev libldap2-dev libssl-dev libxml2-dev libxslt1-dev libxmlsec1-dev libffi-dev pkg-config apt-transport-https virtualenv build-essential python3-venv libpq-dev python3-dev

# 安裝NodeJS
sudo apt-get install -y ca-certificates curl gnupg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
export NODE_MAJOR=20
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
apt install -y nodejs

# 安裝 yarn
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update
sudo apt install -y yarn

# 安裝 PowerDNS-Admin
wget -O /tmp/PowerDNS-Admin.tar.gz https://github.com/PowerDNS-Admin/PowerDNS-Admin/archive/refs/tags/v0.4.2.tar.gz
mkdir -p /opt/web/
tar -zxf /tmp/PowerDNS-Admin.tar.gz -C /opt/web/powerdns-admin
ln -s /opt/web/PowerDNS-Admin-0.4.2 /opt/web/powerdns-admin
cd /opt/web/powerdns-admin
python3 -mvenv ./venv
source ./venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
cp /opt/web/powerdns-admin/configs/development.py /opt/web/powerdns-admin/configs/production.py
  • 設定文件: /opt/web/powerdns-admin/configs/production.py
import os
import urllib.parse
basedir = os.path.abspath(os.path.dirname(__file__))

### BASIC APP CONFIG
SALT = '$2b$12$yLUMTIfl21FKJQpTkRQXCu'
SECRET_KEY = 'e951e5a1f4b94151b360f47edf596dd2'
BIND_ADDRESS = '0.0.0.0'
PORT = 9191
SERVER_EXTERNAL_SSL = os.getenv('SERVER_EXTERNAL_SSL', None)

### DATABASE CONFIG
SQLA_DB_USER = 'pdnsadmin'
SQLA_DB_PASSWORD = 'pdnsPassword'
SQLA_DB_HOST = '127.0.0.1'
SQLA_DB_NAME = 'pdnsadmindb'
SQLALCHEMY_TRACK_MODIFICATIONS = True
SQLA_DB_PORT = 5432

#CAPTCHA Config
CAPTCHA_ENABLE = True
CAPTCHA_LENGTH = 6
CAPTCHA_WIDTH = 160
CAPTCHA_HEIGHT = 60
CAPTCHA_SESSION_KEY = 'captcha_image'

#Server side sessions tracking
#Set to TRUE for CAPTCHA, or enable another stateful session tracking system
SESSION_TYPE = 'sqlalchemy'

### DATABASE - MySQL
## Don't forget to uncomment the import in the top
#SQLALCHEMY_DATABASE_URI = 'mysql://{}:{}@{}/{}'.format(
#    urllib.parse.quote_plus(SQLA_DB_USER),
#    urllib.parse.quote_plus(SQLA_DB_PASSWORD),
#    SQLA_DB_HOST,
#    SQLA_DB_NAME
#)

### DATABASE - PostgreSQL
## Don't forget to uncomment the import in the top
SQLALCHEMY_DATABASE_URI = 'postgresql://{}:{}@{}:{}/{}'.format(
   urllib.parse.quote_plus(SQLA_DB_USER),
   urllib.parse.quote_plus(SQLA_DB_PASSWORD),
   SQLA_DB_HOST,
   SQLA_DB_PORT,
   SQLA_DB_NAME
)

### DATABASE - SQLite
# SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db')

### SMTP config
# MAIL_SERVER = 'localhost'
# MAIL_PORT = 25
# MAIL_DEBUG = False
# MAIL_USE_TLS = False
# MAIL_USE_SSL = False
# MAIL_USERNAME = None
# MAIL_PASSWORD = None
# MAIL_DEFAULT_SENDER = ('PowerDNS-Admin', '[email protected]')

# SAML Authnetication
SAML_ENABLED = False
# SAML_DEBUG = True
# SAML_PATH = os.path.join(os.path.dirname(__file__), 'saml')
# ##Example for ADFS Metadata-URL
# SAML_METADATA_URL = 'https://<hostname>/FederationMetadata/2007-06/FederationMetadata.xml'
# #Cache Lifetime in Seconds
# SAML_METADATA_CACHE_LIFETIME = 1

# # SAML SSO binding format to use
# ## Default: library default (urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect)
# #SAML_IDP_SSO_BINDING = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'

# ## EntityID of the IdP to use. Only needed if more than one IdP is
# ##   in the SAML_METADATA_URL
# ### Default: First (only) IdP in the SAML_METADATA_URL
# ### Example: https://idp.example.edu/idp
# #SAML_IDP_ENTITY_ID = 'https://idp.example.edu/idp'
# ## NameID format to request
# ### Default: The SAML NameID Format in the metadata if present,
# ###   otherwise urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
# ### Example: urn:oid:0.9.2342.19200300.100.1.1
# #SAML_NAMEID_FORMAT = 'urn:oid:0.9.2342.19200300.100.1.1'

# Following parameter defines RequestedAttributes section in SAML metadata
# since certain iDPs require explicit attribute request. If not provided section
# will not be available in metadata.
#
# Possible attributes:
# name (mandatory), nameFormat, isRequired, friendlyName
#
# NOTE: This parameter requires to be entered in valid JSON format as displayed below
# and multiple attributes can given
#
# Following example:
#
# SAML_SP_REQUESTED_ATTRIBUTES = '[ \
# {"name": "urn:oid:0.9.2342.19200300.100.1.3", "nameFormat": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "isRequired": true, "friendlyName": "email"}, \
# {"name": "mail", "isRequired": false, "friendlyName": "test-field"} \
# ]'
#
# produces following metadata section:
# <md:AttributeConsumingService index="1">
# <md:RequestedAttribute Name="urn:oid:0.9.2342.19200300.100.1.3" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" FriendlyName="email" isRequired="true"/>
# <md:RequestedAttribute Name="mail" FriendlyName="test-field"/>
# </md:AttributeConsumingService>


# ## Attribute to use for Email address
# ### Default: email
# ### Example: urn:oid:0.9.2342.19200300.100.1.3
# #SAML_ATTRIBUTE_EMAIL = 'urn:oid:0.9.2342.19200300.100.1.3'

# ## Attribute to use for Given name
# ### Default: givenname
# ### Example: urn:oid:2.5.4.42
# #SAML_ATTRIBUTE_GIVENNAME = 'urn:oid:2.5.4.42'

# ## Attribute to use for Surname
# ### Default: surname
# ### Example: urn:oid:2.5.4.4
# #SAML_ATTRIBUTE_SURNAME = 'urn:oid:2.5.4.4'

# ## Attribute to use for username
# ### Default: Use NameID instead
# ### Example: urn:oid:0.9.2342.19200300.100.1.1
# #SAML_ATTRIBUTE_USERNAME = 'urn:oid:0.9.2342.19200300.100.1.1'

# ## Attribute to get admin status from
# ### Default: Don't control admin with SAML attribute
# ### Example: https://example.edu/pdns-admin
# ### If set, look for the value 'true' to set a user as an administrator
# ### If not included in assertion, or set to something other than 'true',
# ###  the user is set as a non-administrator user.
# #SAML_ATTRIBUTE_ADMIN = 'https://example.edu/pdns-admin'

## Attribute to get admin status for groups with the IdP
# ### Default: Don't set administrator group with SAML attributes
#SAML_GROUP_ADMIN_NAME = 'GroupName'

## Attribute to get operator status for groups with the IdP
# ### Default: Don't set operator group with SAML attributes
#SAML_GROUP_OPERATOR_NAME = 'GroupName'

# ## Attribute to get account names from
# ### Default: Don't control accounts with SAML attribute
# ### If set, the user will be added and removed from accounts to match
# ###  what's in the login assertion. Accounts that don't exist will
# ###  be created and the user added to them.
# SAML_ATTRIBUTE_ACCOUNT = 'https://example.edu/pdns-account'

# ## Attribute name that aggregates group names
# ### Default: Don't collect IdP groups from SAML group attributes
# ### In Okta, you can assign administrators by group using "Group Attribute Statements."
# ### In this case, the SAML_ATTRIBUTE_GROUP will be the attribute name for a collection of
# ###  groups passed in the SAML assertion.  From there, you can specify a SAML_GROUP_ADMIN_NAME.
# ### If the user is a member of this group, and that group name is included in the collection,
# ###   the user will be set as an administrator.
# #SAML_ATTRIBUTE_GROUP = 'https://example.edu/pdns-groups'
# #SAML_GROUP_ADMIN_NAME = 'PowerDNSAdmin-Administrators'

# SAML_SP_ENTITY_ID = 'http://<SAML SP Entity ID>'
# SAML_SP_CONTACT_NAME = '<contact name>'
# SAML_SP_CONTACT_MAIL = '<contact mail>'

# Configures the path to certificate file and it's respective private key file
# This pair is used for signing metadata, encrypting tokens and all other signing/encryption
# tasks during communication between iDP and SP
# NOTE: if this two parameters aren't explicitly provided, self-signed certificate-key pair
# will be generated in "PowerDNS-Admin" root directory
# ###########################################################################################
# CAUTION: For production use, usage of self-signed certificates it's highly discouraged.
# Use certificates from trusted CA instead
# ###########################################################################################
# SAML_CERT = '/etc/pki/powerdns-admin/cert.crt'
# SAML_KEY = '/etc/pki/powerdns-admin/key.pem'

# Configures if SAML tokens should be encrypted.
# SAML_SIGN_REQUEST = False
# #Use SAML standard logout mechanism retreived from idp metadata
# #If configured false don't care about SAML session on logout.
# #Logout from PowerDNS-Admin only and keep SAML session authenticated.
# SAML_LOGOUT = False
# #Configure to redirect to a different url then PowerDNS-Admin login after SAML logout
# #for example redirect to google.com after successful saml logout
# #SAML_LOGOUT_URL = 'https://google.com'

# #SAML_ASSERTION_ENCRYPTED = True

# Some IdPs, like Okta, do not return Attribute Statements by default
# Set the following to False if you are using Okta and not manually configuring Attribute Statements
# #SAML_WANT_ATTRIBUTE_STATEMENT = True

# Remote authentication settings

# Whether to enable remote user authentication or not
# Defaults to False
# REMOTE_USER_ENABLED=True

# If set, users will be redirected to this location on logout
# Ignore or set to None to avoid redirecting altogether
# Warning: if REMOTE_USER environment variable is still set after logging out and not cleared by
# some external module, not defining a custom logout URL might trigger a loop
# that will just log the user back in right after logging out
# REMOTE_USER_LOGOUT_URL=https://my.sso.com/cas/logout

# An optional list of remote authentication tied cookies to be removed upon logout
# REMOTE_USER_COOKIES=['MOD_AUTH_CAS', 'MOD_AUTH_CAS_S']
export FLASK_CONF=../configs/production.py
export FLASK_APP=powerdnsadmin/__init__.py
flask db upgrade

操作結果

(venv) root@pl-dns-01:/opt/web/powerdns-admin# export FLASK_CONF=../configs/production.py
(venv) root@pl-dns-01:/opt/web/powerdns-admin# export FLASK_APP=powerdnsadmin/__init__.py
(venv) root@pl-dns-01:/opt/web/powerdns-admin# flask db upgrade
INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> 787bdba9e147, Init DB
INFO  [alembic.runtime.migration] Running upgrade 787bdba9e147 -> 59729e468045, Add view column to setting table
INFO  [alembic.runtime.migration] Running upgrade 59729e468045 -> 1274ed462010, Change setting.value data type
INFO  [alembic.runtime.migration] Running upgrade 1274ed462010 -> 4a666113c7bb, Adding Operator Role
INFO  [alembic.runtime.migration] Running upgrade 4a666113c7bb -> 31a4ed468b18, Remove all setting in the DB
INFO  [alembic.runtime.migration] Running upgrade 31a4ed468b18 -> 654298797277, Upgrade DB Schema
INFO  [alembic.runtime.migration] Running upgrade 654298797277 -> 0fb6d23a4863, Remove user avatar
INFO  [alembic.runtime.migration] Running upgrade 0fb6d23a4863 -> 856bb94b7040, Add comment column in domain template record table
INFO  [alembic.runtime.migration] Running upgrade 856bb94b7040 -> b0fea72a3f20, Update domain serial columns type
INFO  [alembic.runtime.migration] Running upgrade b0fea72a3f20 -> 3f76448bb6de, Add user.confirmed column
INFO  [alembic.runtime.migration] Running upgrade 3f76448bb6de -> 0d3d93f1c2e0, Add domain_id to history table
INFO  [alembic.runtime.migration] Running upgrade 0d3d93f1c2e0 -> 0967658d9c0d, add apikey account mapping table
INFO  [alembic.runtime.migration] Running upgrade 0967658d9c0d -> fbc7cf864b24, update history detail quotes
INFO  [alembic.runtime.migration] Running upgrade fbc7cf864b24 -> 6ea7dc05f496, Fix typo in history detail
INFO  [alembic.runtime.migration] Running upgrade 6ea7dc05f496 -> f41520e41cee, update domain type length
INFO  [alembic.runtime.migration] Running upgrade f41520e41cee -> b24bf17725d2, Add unique index to settings table keys

產生資料檔

yarn install --pure-lockfile
flask assets build

操作結果

(venv) root@pl-dns-01:/opt/web/powerdns-admin# yarn install --pure-lockfile
yarn install v1.22.22
[1/4] Resolving packages...
warning Resolution field "@fortawesome/[email protected]" is incompatible with requested version "@fortawesome/fontawesome-free@^5.15.4"
[2/4] Fetching packages...
info There appears to be trouble with your network connection. Retrying...
[3/4] Linking dependencies...
warning " > [email protected]" has unmet peer dependency "[email protected] - 3".
warning " > [email protected]" has unmet peer dependency "popper.js@^1.16.1".
warning "admin-lte > [email protected]" has incorrect peer dependency "bootstrap@^3.1.1".
warning " > [email protected]" has unmet peer dependency "jquery@^1.7 || ^2.0 || ^3.1".
warning "admin-lte > [email protected]" has unmet peer dependency "moment-timezone@^0.5.31".
warning "admin-lte > [email protected]" has unmet peer dependency "[email protected]".
warning "admin-lte > bootstrap-colorpicker > [email protected]" has unmet peer dependency "@popperjs/core@^2.11.6".
[4/4] Building fresh packages...
Done in 29.55s.
(venv) root@pl-dns-01:/opt/web/powerdns-admin# flask assets build
Building bundle: generated/login.js
[2024-12-22 22:51:03,754] [script.py:167] INFO - Building bundle: generated/login.js
Building bundle: generated/validation.js
[2024-12-22 22:51:03,763] [script.py:167] INFO - Building bundle: generated/validation.js
Building bundle: generated/login.css
[2024-12-22 22:51:03,765] [script.py:167] INFO - Building bundle: generated/login.css
Building bundle: generated/main.js
[2024-12-22 22:51:03,812] [script.py:167] INFO - Building bundle: generated/main.js
Building bundle: generated/main.css
[2024-12-22 22:51:03,857] [script.py:167] INFO - Building bundle: generated/main.css
(venv) root@pl-dns-01:/opt/web/powerdns-admin# 

記下當前版本 PowerDNS 當前版本,等等設定會使用到

sudo pdns_control version

試營運

./run.py

URL: http://192.168.56.100:9191/login

  • 預設使沒有登入帳號的,需要先註冊一個帳號

離開 Python 虛擬環境

deactivate

建立 system-daemon

  • /etc/systemd/system/powerdns-admin.service
[Unit]
Description=PowerDNS-Admin
Requires=powerdns-admin.socket
After=network.target

[Service]
Environment="FLASK_CONF=../configs/production.py"
PIDFile=/run/powerdns-admin/pid
User=pdns
Group=pdns
WorkingDirectory=/opt/web/powerdns-admin
ExecStart=/opt/web/powerdns-admin/venv/bin/gunicorn --pid /run/powerdns-admin/pid --bind unix:/run/powerdns-admin/socket 'powerdnsadmin:create_app()'
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target
  • /etc/systemd/system/powerdns-admin.socket
[Unit]
Description=PowerDNS-Admin socket

[Socket]
ListenStream=/run/powerdns-admin/socket

[Install]
WantedBy=sockets.target
  • /etc/tmpfiles.d/powerdns-admin.conf
d /run/powerdns-admin 0755 pdns pdns -
sudo systemctl daemon-reload && sudo systemctl start powerdns-admin.socket && sudo systemctl enable powerdns-admin.socket
sudo chown -R pdns:pdns /run/powerdns-admin
sudo chown -R pdns:pdns /opt/web/PowerDNS-Admin-0.4.2
sudo systemctl restart powerdns-admin

安裝 nginx

apt install nginx
  • /etc/nginx/conf.d/http.conf
server {
  listen *:80;
  server_name               192.168.56.100;

  index                     index.html index.htm index.php;
  root                      /opt/web/powerdns-admin;
  access_log                /var/log/nginx/powerdns-admin.local.access.log combined;
  error_log                 /var/log/nginx/powerdns-admin.local.error.log;

  client_max_body_size              10m;
  client_body_buffer_size           128k;
  proxy_redirect                    off;
  proxy_connect_timeout             90;
  proxy_send_timeout                90;
  proxy_read_timeout                90;
  proxy_buffers                     32 4k;
  proxy_buffer_size                 8k;
  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_headers_hash_bucket_size    64;

  location ~ ^/static/  {
    include  /etc/nginx/mime.types;
    root /opt/web/powerdns-admin/powerdnsadmin;

    location ~*  \.(jpg|jpeg|png|gif)$ {
      expires 365d;
    }

    location ~* ^.+.(css|js)$ {
      expires 7d;
    }
  }

  location / {
    proxy_pass            http://unix:/run/powerdns-admin/socket;
    proxy_read_timeout    120;
    proxy_connect_timeout 120;
    proxy_redirect        off;
  }

}

參考資料