PowerDNSのAPIを試してみる(前編)

どうも、やまもとやまです。
DNS権威サーバーの実装にはBINDやPowerDNS、NSD等があります。
今回取り上げるのはPowerDNSですが、PowerDNSではREST APIを利用できるので、バックエンドのデータベースを直接操作しなくてもスクリプト等からゾーンやレコードの操作が可能になっています。
というわけで、どんなことができるのか試してみましょう。

環境

OS:AlmaLinux9
PowerDNS:4.8.4
MariaDB:10.5.22

PowerDNSはepelレポジトリから、MariaDBはAlmaLinux9標準のものを利用します。
また、構成としては以下のようなマスター1台、スレーブ2台の構成を想定しています。

master.future.local 192.168.1.100
slave1.future.local 192.168.1.101
slave2.future.local 192.168.1.102

PowerDNS導入

OSは最小インストールした状態とします。
まずはPowerDNSを導入しましょう。
MariaDBも導入し、起動しておきます。

# dnf install epel-release
# dnf install pdns pdns-backend-mysql pdns-tools
# dnf install mariadb-server
# systemctl enable mariadb.service
# systemctl start mariadb.service

MariaDB初期設定

初期設定を行います。
rootユーザのパスワードを変更し、不要なテストDB削除、匿名ユーザ削除を実施します。

# mysql_secure_installation

続いて、PowerDNS用のデータベースを作成します。

# mysql
> create database pdns DEFAULT CHARACTER SET utf8;
> create user 'pdnsuser'@'localhost' IDENTIFIED BY 'PASSWORD';
> GRANT ALL PRIVILEGES ON `pdns`.* TO 'pdnsuser'@'localhost';
> flush privileges;
> exit

最後に、PowerDNS用のテーブルデータをインポートします。

# mysql -updnsuser -p pdns < /usr/share/doc/pdns/schema.mysql.sql

PowerDNS初期設定

MariaDBに続き、PowerDNSの初期設定を行います。
以下のファイルを編集してやりましょう。
この設定については、マスターとスレーブで設定が異なりますのでご注意ください。

/etc/pdns/pdns.conf

設定可能な項目は多岐にわたるため詳細は省きますが(公式ドキュメント等もご参照ください)、最低限以下の項目あたりは設定が必要になると思います。

  • allow-axfr-ips
  • allow-notify-from
  • api=yes
  • api-key=TestAPIKey
  • launch=gmysql
  • gmysql-host=localhost
  • gmysql-user=pdnsuser
  • gmysql-dbname=pdns
  • gmysql-password=PASSWORD
  • master=yes
    ※スレーブの場合はslave=yes

必要な設定ができたら起動します。

# systemctl enable pdns.service
# systemctl start pdns.service

取り急ぎ、これでPowerDNSが利用できる状態になりました。
UI操作がしたいという場合は、Apache/Nginx等のWebサーバー(必要に応じてPHPも・・・)、PowerDNS-AdminやPoweradmin等のUIツールを入れると良いでしょう。

名前解決を試してみる

とはいえ、まだインストール直後でゾーンが作成されていないです。
なので、検索しても失敗します。

$ dig @192.168.1.100 example.com

; <<>> DiG 9.16.23-RH <<>> @192.168.1.100 example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 55727
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com.                   IN      A

想定通り、名前解決に失敗しますね。
あとはゾーンの作成とレコード設定を行えば検索できるようになります。

試しにNSとSOAだけの以下のゾーンを追加します。
SOAはデフォルト設定値のままですが、pdns.confでデフォルト設定も変更可能です。
※Web UIがある場合はそちらから、ない場合はpdnsデータベースのdomainsテーブルにゾーンを、recordsテーブルに各レコードを登録

example.com.         IN        SOA       a.misconfigured.dns.server.invalid. hostmaster.example.com. 2024070501 10800 3600 604800 3600
example.com.         IN        NS          slave1.future.local.
example.com.         IN        NS          slave2.future.local.

この状態で名前解決を試してみましょう。

$ dig @192.168.1.100 example.com ns

; <<>> DiG 9.16.23-RH <<>> @192.168.1.100 example.com ns
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 5079
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com.                   IN      NS

;; ANSWER SECTION:
example.com.            3600    IN      NS      slave1.future.local.
example.com.            3600    IN      NS      slave2.future.local.

今度は正常に検索できましたね!

なお、スレーブ側でautosecondary設定を入れ、supermasterを指定することで、マスター側でゾーンを作成すると自動でスレーブ側にも作成し転送することが可能です。
便利な機能なのですが、ゾーン削除の場合は自動で削除されないのは玉に瑕。

PowerDNSのAPIについて

さて、準備ができたところで、APIを試してみましょう。
ところで、PowerDNSのAPIではどのようなことができるのでしょうか?
詳細は以下の公式ドキュメントに記載されていますのでご参照ください。
https://doc.powerdns.com/authoritative/http-api/index.html

ゾーン関連で言えば、RESTの各メソッドで以下のようなことを実施できます。

  • 全ゾーンのリストを出力
  • 新規ゾーンを作成
  • 指定ゾーンの情報を出力
  • 指定ゾーン(および関連情報)を削除
  • 指定ゾーンのレコードを編集

他にもありますが、取り急ぎ基本的な操作はこれで可能です。

API操作時はエンドポイントおよび、pdns.confで設定したAPIKEYの指定が必要となります。

全ゾーンのリストを出力

エンドポイント:/servers/{server_id}/zones
メソッド:GET

まずはシンプルにゾーンのリストを出力するAPIです。
パラメータはserver_idのみ(今回はPowerDNSのサーバー上で実行するのでlocalhost指定)ですので、curlで簡単に試すことができます。
curlの実行時、X-API-Keyヘッダで認証キー(APIKEY)を渡せばAPIが利用可能です。

# curl -sk -H 'X-API-Key: TestAPIKey' -X GET http://127.0.0.1:8081/api/v1/servers/localhost/zones
[{"account": "", "catalog": "", "dnssec": false, "edited_serial": 2024070501, "id": "example.com.", "kind": "Master", "last_check": 0, "masters": [], "name": "example.com.", "notified_serial": 0, "serial": 2024070501, "url": "/api/v1/servers/localhost/zones/example.com."}]

example.comというゾーンのみ登録されている状態なので、1行分の回答となっていますね。
データはjsonで返ってきますが、読みづらいのでjqします。

# curl -sk -H 'X-API-Key: TestAPIKey' -X GET http://127.0.0.1:8081/api/v1/servers/localhost/zones|jq .
[
  {
    "account": "",
    "catalog": "",
    "dnssec": false,
    "edited_serial": 2024070501,
    "id": "example.com.",
    "kind": "Master",
    "last_check": 0,
    "masters": [],
    "name": "example.com.",
    "notified_serial": 0,
    "serial": 2024070501,
    "url": "/api/v1/servers/localhost/zones/example.com."
  }
]

これなら分かりやすいですね。
各項目の詳細はドキュメント類も参照いただければと思いますが、各ゾーンの概要情報が出力されています。

指定ゾーンの情報を出力

エンドポイント:/servers/{server_id}/zones/{zone_id}
メソッド:GET

では次に、指定したゾーンの情報を出力してみましょう。
これもGETメソッドなので、簡単にcurlで試すことができます。
パラメータのzone_idはゾーン名のFQDNです。

# curl -sk -H 'X-API-Key: TestAPIKey' -X GET http://127.0.0.1:8081/api/v1/servers/localhost/zones/example.com.|jq .
{
  "account": "",
  "api_rectify": false,
  "catalog": "",
  "dnssec": false,
  "edited_serial": 2024070501,
  "id": "example.com.",
  "kind": "Master",
  "last_check": 0,
  "master_tsig_key_ids": [],
  "masters": [],
  "name": "example.com.",
  "notified_serial": 0,
  "nsec3narrow": false,
  "nsec3param": "",
  "rrsets": [
    {
      "comments": [],
      "name": "example.com.",
      "records": [
        {
          "content": "a.misconfigured.dns.server.invalid. hostmaster.example.com. 2024070501 10800 3600 604800 3600",
          "disabled": false
        }
      ],
      "ttl": 3600,
      "type": "SOA"
    },
    {
      "comments": [],
      "name": "example.com.",
      "records": [
        {
          "content": "slave1.future.local.",
          "disabled": false
        },
        {
          "content": "slave2.future.local.",
          "disabled": false
        }
      ],
      "ttl": 3600,
      "type": "NS"
    }
  ],
  "serial": 2024070501,
  "slave_tsig_key_ids": [],
  "soa_edit": "",
  "soa_edit_api": "DEFAULT",
  "url": "/api/v1/servers/localhost/zones/example.com."
}

はい、無事いけましたね。
レコードがSOAとNSだけのゾーンですが、jqで表示するとそれなりに長くなりますね。。
対象ゾーンの概要に加え、全レコード情報も返ってきています。
rrsetsの項目がレコード一覧で、レコード名とレコードタイプごとに括られています。

というわけで

今回はここまで。
まずはPowerDNSの導入と簡単な確認まで行いました。
また、検索系のAPIについて実際の動作を試してみました。
次回はいよいよ、登録、更新系のAPIでゾーン操作を行ってみます。
それではまた!