How to setup a PowerDNS Authoritative Server with PowerDNS-Admin as management web interface.
This guide expects an existing PostgreSQL 13 (should work on older versions) and Apach2 installation on a Debian 10 server.
Used software:
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
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
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
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. 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
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 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 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
Create the local directories and clonde the GitHub repository of 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:
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 </dev/urandom | head -c32; echo;
# tr -dc _A-Z-a-z-0-9 </dev/urandom | head -c32; echo;
iz7g4zpfvbnK_eb0lWZeFEuXn5UV93Yz
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: 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/
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
<VirtualHost *:80> 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 </VirtualHost> <IfModule mod_ssl.c> <VirtualHost *:443> ServerName pdnsadmin.example.com ServerAdmin hostmaster@example.com DocumentRoot /opt/python/powerdns-admin/app/powerdnsadmin/ <Directory "/opt/python/powerdns-admin/app/powerdnsadmin/static"> AllowOverride None Require all granted </Directory> 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 <IfModule mod_headers.c> # 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'" </IfModule> </VirtualHost> </IfModule>
Enable the new VHost.
a2ensite pdnsadmin.example.com
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