====== PowerDNS via Apache2 with PostgreSQL on Debian and PowerDNS-Admin ======
How to setup a [[https://www.powerdns.com/|PowerDNS]] Authoritative Server with [[https://github.com/ngoduykhanh/PowerDNS-Admin|PowerDNS-Admin]] as management web interface.
This guide expects an existing [[https://www.postgresql.org/|PostgreSQL]] 13 (should work on older versions) and [[https://httpd.apache.org/|Apach2]] installation on a [[https://www.debian.org/|Debian]] 10 server.
Used software:
* Debian 10.6
* PowerDNS Authoritative Server 4.3.1
* PostgreSQL 13
* Python 3.7.3
* uWSGI 2.0.19.1
* Apache 2.4.46
===== Install PowerDNS =====
==== Configure APT Repository ====
Create a file with the repositories of PowerDNS (see [[https://repo.powerdns.com/]]).
vim /etc/apt/sources.list.d/pdns.list
deb http://repo.powerdns.com/debian buster-rec-43 main
deb http://repo.powerdns.com/debian buster-auth-43 main
deb http://repo.powerdns.com/debian buster-dnsdist-15 main
Add the key for this repositories.
curl https://repo.powerdns.com/FD380FBB-pub.asc | sudo apt-key add -
Install PowerDNS Authoritative Server with PostgreSQL backend.
apt install pdns-backend-pgsql
==== Prepare PostgreSQL Database ====
Open psql.
su - postgres
psql
Create the user u_powerdns and the database db_powerdns. Change the example password with your own generated one (eg. [[https://pwgen.ch/]]).
CREATE USER u_powerdns WITH PASSWORD 'iC0iB9kQ5hR4oG5uW2nD2nV0gK6vN2eSoM2eI8kT0gA9rF2pS3wW7mO4sJ4aT5tN';
CREATE DATABASE db_powerdns OWNER u_powerdns;
\q
Edit pg_hba.conf to configure the connections for the new database.
vim /etc/postgresql/13/main/pg_hba.conf
Add following line to provide access to the database db_powerdns via unix socket.
local db_powerdns u_powerdns peer
Reload the PostgreSQL cluster.
pg_ctlcluster 13 main reload
==== Import Schema ====
Get the database schema for the related PowerDNS Authoritative Server version from GitHub and import it.
su - postgres
wget https://raw.githubusercontent.com/PowerDNS/pdns/rel/auth-4.3.x/modules/gpgsqlbackend/schema.pgsql.sql
psql -U u_powerdns -f schema.pgsql.sql db_powerdns
==== Configure PowerDNS Authoritative Server ====
Edit the PowerDNS configuration file (you may want to create a backup before).
vim /etc/powerdns/pdns.conf
Replace the database password with the one you generated before. [[https://pwgen.ch/|Generate]] your own API key (used for PowerDNS-Admin). Change the other settings according to your needs.
# https://doc.powerdns.com/authoritative/settings.html
#webserver-address=127.0.0.1,::1
setuid=pdns
setgid=pdns
# https://doc.powerdns.com/authoritative/backends/generic-postgresql.html
launch=gpgsql
gpgsql-host=/var/run/postgresql
#gpgsql-port=
gpgsql-dbname=db_powerdns
gpgsql-user=u_powerdns
gpgsql-password=iC0iB9kQ5hR4oG5uW2nD2nV0gK6vN2eSoM2eI8kT0gA9rF2pS3wW7mO4sJ4aT5tN
gpgsql-dnssec=yes
loglevel=4
master=yes
slave=no
webserver=yes
api=yes
api-key=aF3kD4eJ0hB1uI1jV8vR2yC0eK8lP9mO
daemon=no
guardian=no
default-publish-cdnskey=1
default-publish-cds=2,4
webserver-port=8081
webserver-allow-from=127.0.0.1,::1
#allow-axfr-ips=0.0.0.0/0,::/0
#allow-notify-from=0.0.0.0/0,::/0
enable-lua-records=1
version-string=anonymous
default-soa-edit=INCEPTION-INCREMENT
===== Install PowerDNS-Admin =====
==== Prepare PostgreSQL Database ====
Open psql.
su - postgres
psql
Create the user u_powerdns and the database db_powerdns. Change the example password with your own generated one (eg. [[https://pwgen.ch/]]).
CREATE USER u_powerdnsadmin WITH PASSWORD 'hW2bD6rM7vK0vB8fN7dJ8iI1lH3eJ5eQiD7qE7kH6gT1rE9vV2gT2hH3vL2lD7jW';
CREATE DATABASE db_powerdnsadmin OWNER u_powerdnsadmin;
\q
Edit pg_hba.conf to configure the connections for the new database.
vim /etc/postgresql/13/main/pg_hba.conf
Add following line to provide access to the database db_powerdns via unix socket.
local db_powerdnsadmin u_powerdnsadmin peer
Reload the PostgreSQL cluster.
pg_ctlcluster 13 main reload
==== Install NodeJS ====
Install NodeJS according to [[https://github.com/nodesource/distributions/blob/master/README.md]].
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - &&\
apt install -y nodejs
==== Install Yarn ====
Install Yarn according to [[https://classic.yarnpkg.com/en/docs/install/#debian-stable]].
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
apt update
apt install yarn
==== Setup PowerDNS-Admin from GitHub ====
Create the local directories and clonde the GitHub repository of [[https://github.com/ngoduykhanh/PowerDNS-Admin/|PowerDNS-Admin]].
mkdir -p /opt/python/powerdns-admin/
git clone https://github.com/ngoduykhanh/PowerDNS-Admin.git /opt/python/powerdns-admin/app
cd /opt/python/powerdns-admin/app
Install the requirements.
apt install libmariadb-dev build-essential python3-dev libldap2-dev libsasl2-dev pkg-config libxmlsec1-dev
This requirements are expected by this python libraries:
* python-ldap: build-essential python3-dev libldap2-dev libsasl2-dev slapd ldap-utils tox lcov valgrind
* SQLAlchemy: mysqlclient, libmariadbclient-dev
Create the config file /opt/python/powerdns-admin/app/configs/config.py.
vim /opt/python/powerdns-admin/app/configs/config.py
Generate new values for the variables SALT and SECRET_KEY (32 chars).
Generate a new salt.
/opt/python/powerdns-admin/venv/bin/python3 -c 'import bcrypt; print(bcrypt.gensalt().decode("utf-8"))'
Example.
# /opt/python/powerdns-admin/venv/bin/python3 -c 'import bcrypt; print(bcrypt.gensalt().decode("utf-8"))'
$2b$12$E0Dn1LmXonAUiCP8sM0htu
Generate a new secret key:
tr -dc _A-Z-a-z-0-9
# tr -dc _A-Z-a-z-0-9
This config.py is based on /opt/python/powerdns-admin/app/powerdnsadmin/default_config.py.
Details about Unix domain connections of SQLAlchemy you can find in their documentation: [[https://docs.sqlalchemy.org/en/13/dialects/postgresql.html#unix-domain-connections|Documentation]]
import os
basedir = os.path.abspath(os.path.abspath(os.path.dirname(__file__)))
### BASIC APP CONFIG
SALT = "$2b$12$E0Dn1LmXonAUiCP8sM0htu"
SECRET_KEY = "iz7g4zpfvbnK_eb0lWZeFEuXn5UV93Yz"
BIND_ADDRESS = "0.0.0.0"
PORT = 9191
HSTS_ENABLED = False
OFFLINE_MODE = False
### DATABASE CONFIG
SQLA_DB_DRIVER = "postgresql" # mysql, postgresql
SQLA_DB_USER = "u_powerdnsadmin"
SQLA_DB_PASSWORD = ''
#SQLA_DB_HOST = '127.0.0.1'
SQLA_DB_HOST = ""
SQLA_DB_NAME = "db_powerdnsadmin"
SQLALCHEMY_TRACK_MODIFICATIONS = True
### DATABASE - MySQL
SQLALCHEMY_DATABASE_URI = f"{SQLA_DB_DRIVER}://{SQLA_DB_USER}:{SQLA_DB_PASSWORD}@{SQLA_DB_HOST}/{SQLA_DB_NAME}"
### DATABASE - SQLite
# SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db')
# SAML Authnetication
SAML_ENABLED = False
SAML_ASSERTION_ENCRYPTED = True
Create and activate a venv for PowerDNS-Admin.
python3 -m venv /opt/python/powerdns-admin/venv
. /opt/python/powerdns-admin/venv/bin/activate
**VENV** Change to the required directory for the next steps.
cd /opt/python/powerdns-admin/app/
**VENV** Install all the requirements from pip.
pip install wheel
pip install -r /opt/python/powerdns-admin/app/requirements.txt
pip install psycopg2-binary
**VENV** Define the flask variables.
export FLASK_CONF="/opt/python/powerdns-admin/app/configs/config.py"
export FLASK_APP="/opt/python/powerdns-admin/app/powerdnsadmin/__init__.py"
**VENV** Initialize the database.
flask db upgrade
**VENV** Install the JavaScript components.
yarn install --pure-lockfile
**VENV** Build webassets (JavaScript and CSS files).
flask assets build
**VENV** Quit the venv.
deactivate
Set the permissions.
chown -R www-data:www-data /opt/python/powerdns-admin/
==== Setup uWSGI ====
Create and set the permissions for the directory which uWSGI will use for the pid file and the unix sockets.
Create the uWSGI configuration directory.
mkdir /etc/uwsgi/
Create the uWSGI configuration file for the PowerDNS-Admin service.
vim /etc/uwsgi/powerdns-admin.ini
The number of processes shouldn't be higher than the numbers of CPUs in the system.
[uwsgi]
strict = true
vacuum = true
single-interpreter = true
die-on-term = true
need-app = true
uid = www-data
gid = www-data
uwsgi-socket = /run/uwsgi_%n/service.sock
wsgi-file = /opt/python/%n/app/wsgi.py
processes = 1
threads = 4
master = true
max-requests = 1000
auto-procname = true
procname-prefix-spaced = %n
venv = /opt/python/%n/venv/
buffer-size = 8192
;disable-logging = true
;log-4xx = true
;log-5xx = true
Create the start file for the application.
vim /opt/python/powerdns-admin/app/wsgi.py
#!/usr/bin/env python3
import sys
APP_PATH = "/opt/python/powerdns-admin/app/"
CONFIG_PATH = f"{APP_PATH}configs/config.py"
sys.path.insert(0, APP_PATH)
from powerdnsadmin import create_app
application = create_app(CONFIG_PATH)
Create a service file for systemd.
vim /etc/systemd/system/uwsgi@powerdns-admin.service
[Unit]
Description=uWSGI service unit %i
After=syslog.target
After=postgresql.service
[Service]
User=www-data
Group=www-data
RuntimeDirectory=uwsgi_%i
PIDFile=/run/uwsgi_%i/service.pid
RemainAfterExit=yes
ExecStart=/usr/local/bin/uwsgi --ini /etc/uwsgi/%i.ini
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill -INT $MAINPID
Restart=always
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all
SuccessExitStatus=15 17 29 30
NoNewPrivileges=yes
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Let systemd re-read the service files.
systemctl daemon-reload
Install uwsgi globally on the system (not in a venv).
python3 -m pip install uwsgi
Start PowerDNS-Admin and let it start on the system startup.
systemctl start uwsgi@powerdns-admin.service
systemctl enable uwsgi@powerdns-admin.service
Enable the uWSGI extension for mod_proxy.
a2enmod proxy_uwsgi
Setup a VHost configuration. Change the name, certificate and the Content-Security-Policy according to your needs.
/etc/apache2/sites-available/pdnsadmin.example.com.conf
ServerName pdnsadmin.example.com
ServerAdmin hostmaster@example.com
Redirect / https://pdnsadmin.example.com/
ErrorLog ${APACHE_LOG_DIR}/pdnsadmin.example.com-error.log
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/pdnsadmin.example.com-access.log combined
ServerName pdnsadmin.example.com
ServerAdmin hostmaster@example.com
DocumentRoot /opt/python/powerdns-admin/app/powerdnsadmin/
AllowOverride None
Require all granted
ProxyPass "/static/" "!"
ProxyPass "/favicon.ico" "!"
ProxyPass "/.well-known/" "!"
ProxyPass "/" "unix:/run/uwsgi_powerdns-admin/service.sock|uwsgi://powerdns-admin/"
ErrorLog ${APACHE_LOG_DIR}/pdnsadmin.example.com-error.log
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/pdnsadmin.example.com-access.log combined
SSLEngine on
SSLCertificateFile /etc/dehydrated/certs/example.com/fullchain.pem
SSLCertificateKeyFile /etc/dehydrated/certs/example.com/privkey.pem
# enable HSTS
Header always set Strict-Transport-Security "max-age=15768000"
# allow frames, needed for settings page
Header always set X-Frame-Options SAMEORIGIN
# Content Security Policy (https://content-security-policy.com/)
Header set Content-Security-Policy: "default-src 'self' *.example.com 'unsafe-inline' 'unsafe-eval'"
Enable the new VHost.
a2ensite pdnsadmin.example.com
==== PowerDNS-Admin Configuration via Browser ====
* Open a browser and access this VHost (eg. [[https://pdnsadmin.example.com/]])
* Create an account
* Configure the PDNS API settings based on pdns.conf
* Settings, PDNS
* PDNS API URL: http://127.0.0.1:8081/
* PDNS API KEY: aF3kD4eJ0hB1uI1jV8vR2yC0eK8lP9mO
* PDNS VERSION: 4.3.1
==== Python Upgrade Notes ====
If you upgrade Python, for example while you upgrade Debian 10 to Debian 11, you have to update the virtual environment.
If you forget this, you can't start the systemd unit an may see an error like this:
Oct 18 18:49:14 ns1.example.com uwsgi[731953]: /usr/local/bin/uwsgi: error while loading shared libraries: libpython3.7m.so.1.0: cannot open shared object file: No such file or directory
Upgrade your venv.
python3 -m venv --upgrade --upgrade-deps /opt/python/powerdns-admin/venv/
And start the Systemd unit again.
systemctl start uwsgi@powerdns-admin.service
===== Appendix =====
* If you want to migrate from Bind to PowerDNS, you can find some notes here: [[os:linux:bind_to_powerdns|Bind to PowerDNS]]
* [[os:linux:powerdns-admin_update|How to Update PowerDNS-Admin]]