PowerDNSとPowerDNS Adminで内部DNSを整備した話〜PowerDNS Admin編〜


:::note info
この記事ではまず権威DNSの構築をしています。
フルリゾルバの構築は18日目、PowerDNS Adminの設定は19日目をご覧ください。

権威DNSの記事: <https://qiita.com/yuito_it_/items/3e74cf14ca0b2bc1e434>
フルリゾルバの記事: <https://qiita.com/yuito_it_/items/1b8afe0889c65e2fb478>
:::

## はじめに

今回はPowerDNSで立てた内部DNSのWebダッシュボードを構築していこうと思います。

### PowerDNSとは

PowerDNSはGPLライセンスの下で公開されているDNSサーバーです。
そしてこれは、権威DNSサーバーもフルリゾルバサーバーも構築できます。
今回はこれを用いて、まずは権威DNSを作成していきます。

<https://www.powerdns.com/>

### PowerDNS Adminとは

今回構築するPowerDNS Adminですが、こちらは、PowerDNS権威サーバーのWebダッシュボードを構築するためのソフトウェアです。

## 環境

DBにはPostgreSQLを使用しています。

```sh
cat /etc/os-release

PRETTY_NAME="Ubuntu 24.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.1 LTS (Noble Numbat)"
VERSION_CODENAME=noble
```

<https://github.com/PowerDNS-Admin/PowerDNS-Admin>

## 必要なパッケージのインストール

まずは必要なパッケージをインストールしていきます。

```sh
sudo apt install -y python3-dev git libsasl2-dev libldap2-dev python3-venv libmariadb-dev pkg-config build-essential curl libpq-dev python3-psycopg2
```

nodejsに関しては、**v14系**が推奨のようです。

```sh
curl -sL https://deb.nodesource.com/setup_14.x | sudo bash -
sudo apt install -y nodejs
```

yarnもインストールします。

```sh
curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | sudo tee /usr/share/keyrings/yarnkey.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update && sudo apt install -y yarn
```

## DBをセットアップする

PowerDNS Adminがユーザー管理などをするためのDBをセットアップします。

```sh
sudo su - postgres
createuser powerdnsadmin
createdb -E UTF8 -l en_US.UTF-8 -O powerdnsadmin -T template0 powerdnsadmindb 'The database for PowerDNS-Admin'
psql postgres=# ALTER ROLE powerdnsadmin WITH PASSWORD '任意のパスワード';
```

## PowerDNSをダウンロードする

では、クローンしましょう。

```sh
git clone https://github.com/PowerDNS-Admin/PowerDNS-Admin.git /opt/web/powerdns-admin
cd /opt/web/powerdns-admin
```

そのままの環境で動かすわけにはいかないので、venvを構築します。

```sh
python3 -mvenv ./venv
```

これで、venvまで構築しました。
では、pipを使って必要なパッケージをインストールしましょう。

```sh
source ./venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
```

## 設定する

次は設定します。
`configs/development.py`を`configs/production.py`にコピーして、編集します。
該当の行を編集してください。

```python:configs/production.py
SECRET_KEY = 'ここは適当に英数字を打つ'

SQLA_DB_USER = 'powerdnsadmin'
SQLA_DB_PASSWORD = 'password(DBのパスワード)'
SQLA_DB_HOST = '*********'
SQLA_DB_NAME = 'powerdnsadmindb'
SQLALCHEMY_TRACK_MODIFICATIONS = True

## コメントアウトを外すだけで良い
SQLALCHEMY_DATABASE_URI = 'postgresql://{}:{}@{}/{}'.format(
    urllib.parse.quote_plus(SQLA_DB_USER),
    urllib.parse.quote_plus(SQLA_DB_PASSWORD),
    SQLA_DB_HOST,
    SQLA_DB_NAME
)
```

## 一旦起動してみる

設定ファイルの位置を設定

```sh
export FLASK_CONF=../configs/production.py
```

DB migration

```sh
export FLASK_APP=powerdnsadmin/__init__.py
flask db upgrade
```

アセットファイルの生成

```sh
yarn install --pure-lockfile
flask assets build
```

起動

```sh
./run.py
```

これで、9191番ポートにアクセスして表示されていれば成功です。
まだ登録はしないでおくことにしました。

## gunicornとNginxを使ってsystemdでデーモン化する

これをすると、SSL化できたり再起動したときに自動で立ち上がってくるようにできます。

### 必要なパッケージをインストールする

追加で必要なのは、nginxとgunicornだけですね。

```sh
sudo apt install nginx
pip install gunicorn
```

### サービスの設定

次にsystemdに追加するサービスの設定です。

これがサービスの本体ですね。

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

[Service]
PIDFile=/run/powerdns-admin/pid
User=pdns
Group=pdns
WorkingDirectory=/opt/web/powerdns-admin
Environment="FLASK_CONF=../configs/production.py"
ExecStartPre=+mkdir -p /run/powerdns-admin/
ExecStartPre=+chown pdns:pdns -R /run/powerdns-admin/
ExecStart=/usr/local/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
```

次にSocketを作成します。

```toml:/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 -
```

### nginxの設定

nginxの設定ファイルを編集します。

```/etc/nginx/sites-enabled/pdns-admin.conf
server {
  listen *:80;
  server_name               pdns.unipro.infra; # ドメインを入力

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

}
```

これは、socketに対してリバースプロキシとして転送しています。
socketにアクセスすると自動的にserviceがstartされるという仕組みです。
効率がいいですね。

SSL化に関しては今回は割愛します。

### 実行してみる

ソケットなどを起動します。

これで完了です。

```sh
sudo systemctl deamon-reload
sudo systemctl enable --now nginx
sudo systemctl enable --now powerdns-admin.socket
```

### 使ってみる

先ほど設定したドメインの80番にアクセスします。
普通に登録します。
最初に登録したユーザーが管理者権限を持ちます。

## まとめ

今回は3回に分けて、PowerDNSに関することをご紹介しました。
PowerDNSはDNSサーバーソフトの中ではとても使いやすいと思うので、ぜひ使ってみてください！！

## 参考文献

https://github.com/PowerDNS-Admin/PowerDNS-Admin/blob/master/docs/wiki/README.md