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

どうも、やまもとやまです。
以前の記事ではPowerDNSのAPI操作の準備編として、PowerDNSの導入と簡単な確認および、検索系のAPIについて操作を行いました。
後編である今回は、登録や更新系の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

では早速やっていきましょう。

新規ゾーンを作成

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

まずはゾーンの作成を行うAPIです。
パラメータはserver_id(今回はPowerDNSのサーバー上で実行するのでlocalhost指定)と、クエリパラメータとしてzoneオブジェクトが必要です。
zoneオブジェクトには多数のパラメータがありますが、最低限以下があれば良いでしょう。

  • name:ゾーン名(「.」で終了する必要があります)
  • kind:ゾーン種別。今回はマスター登録なのでMaster指定です。
  • nameservers:ネームサーバー名。今回はスレーブの2台を指定します。

というわけで今回も手っ取り早いcurlで実行です。

# curl -sk -H 'X-API-Key: TestAPIKey' -H "Content-Type: application/json" -d '{"name": "example.jp.", "kind": "Master", "masters": [], "nameservers": ["slave1.future.local.", "slave2.future.local."]}' -X POST http://127.0.0.1:8081/api/v1/servers/localhost/zones

{"account": "", "api_rectify": false, "catalog": "", "dnssec": false, "edited_serial": 2024082701, "id": "example.jp.", "kind": "Master", "last_check": 0, "master_tsig_key_ids": [], "masters": [], "name": "example.jp.", "notified_serial": 0, "nsec3narrow": false, "nsec3param": "", "rrsets": [{"comments": [], "name": "example.jp.", "records": [{"content": "a.misconfigured.dns.server.invalid. hostmaster.example.jp. 2024082701 10800 3600 604800 3600", "disabled": false}], "ttl": 3600, "type": "SOA"}, {"comments": [], "name": "example.jp.", "records": [{"content": "slave1.future.local.", "disabled": false}, {"content": "slave2.future.local.", "disabled": false}], "ttl": 3600, "type": "NS"}], "serial": 2024082701, "slave_tsig_key_ids": [], "soa_edit": "", "soa_edit_api": "DEFAULT", "url": "/api/v1/servers/localhost/zones/example.jp."}

無事応答が返ってきました。うまくいってそうです。

# dig @localhost example.jp axfr

; <<>> DiG 9.16.23-RH <<>> @localhost example.jp axfr
; (2 servers found)
;; global options: +cmd
example.jp.             3600    IN      SOA     a.misconfigured.dns.server.invalid. hostmaster.example.jp. 2024082701 10800 3600 604800 3600
example.jp.             3600    IN      NS      slave1.future.local.
example.jp.             3600    IN      NS      slave2.future.local.
example.jp.             3600    IN      SOA     a.misconfigured.dns.server.invalid. hostmaster.example.jp. 2024082701 10800 3600 604800 3600
;; Query time: 3 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Tue Aug 27 19:30:34 JST 2024
;; XFR size: 4 records (messages 3, bytes 333)

正常に登録されていますね!

ゾーンにレコードを追加

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

ゾーンへレコードを追加したり変更する場合は、PATCHメソッドを利用します。
エンドポイント用のパラメータはserver_idとzone_idで、クエリパラメータとしては編集するレコードセットのオブジェクトが必要となります。
オブジェクト概要としては以下です。

  • rrsets:レコードセット(複数のrrsetの集合)
    rrsetの中身は以下です
    • name:ゾーン名
    • type:リソース名(AやMX等)
    • ttl:TTL値
    • changetype:REPLACEまたはDELETE
    • records:レコードの中身

例として、先ほど作成したゾーン(example.jp.)にシンプルなレコードを追加してみましょう。
そろそろcurlでの操作も(まだいけますが)面倒になってきますので、今回はPerlを利用します。
もちろんRestAPIですので、HTTP通信ができれば他の言語でも問題ありません。
以下、サンプルプログラムとなります(ファイル名は add_rrsets.pl とします)。
※サンプルのため手抜きですが、実際はエラーチェックやリクエストの結果確認等も必要です

#!/usr/bin/perl
#

use strict;
use LWP::UserAgent;

my $url_zone = "http://127.0.0.1:8081/api/v1/servers/localhost/zones";
my $template_rrsets = << '_EOT_';
{"rrsets":[
{"name": "###DOMAIN###.", "type": "A", "ttl": 600, "changetype": "REPLACE", "records": [{"content": "###IPADDR###", "disabled": false}]},
{"name": "mail.###DOMAIN###.", "type": "A", "ttl": 600, "changetype": "REPLACE", "records": [{"content": "###IPADDR###", "disabled": false}]},
{"name": "###DOMAIN###.", "type": "MX", "ttl": 600, "changetype": "REPLACE", "records": [{"content": "10 mail.###DOMAIN###.", "disabled": false}]},
{"name": "www.###DOMAIN###.", "type": "CNAME", "ttl": 600, "changetype": "REPLACE", "records": [{"content": "###DOMAIN###.", "disabled": false}]}
]}
_EOT_

# variable
my $url;
my $req;
my $res;
my $content_req;
my $domain = 'example.jp';
my $ipaddr = '192.168.1.200';
my $KEY = 'TestAPIKey';

my $ua = LWP::UserAgent->new;

$url = "$url_zone"."/"."$domain".".";
$content_req = $template_rrsets;
$content_req =~ s/###DOMAIN###/$domain/g;
$content_req =~ s/###IPADDR###/$ipaddr/g;

$req = HTTP::Request->new(PATCH => $url);
$req->header("X-API-Key" => $KEY);
$req->header( 'Content-Type' => 'application/json' );
$req->content($content_req);

$res = $ua->request($req);

では実行してみます。

# perl add_rrsets.pl

何も出力されませんが、正常に実行されています。
ゾーン情報を確認してみましょう。

# curl -sk -H 'X-API-Key: TestAPIKey' -X GET http://127.0.0.1:8081/api/v1/servers/localhost/zones/example.jp.|jq

{
  "account": "",
  "api_rectify": false,
  "catalog": "",
  "dnssec": false,
  "edited_serial": 2024082702,
  "id": "example.jp.",
  "kind": "Master",
  "last_check": 0,
  "master_tsig_key_ids": [],
  "masters": [],
  "name": "example.jp.",
  "notified_serial": 0,
  "nsec3narrow": false,
  "nsec3param": "",
  "rrsets": [
    {
      "comments": [],
      "name": "www.example.jp.",
      "records": [
        {
          "content": "example.jp.",
          "disabled": false
        }
      ],
      "ttl": 600,
      "type": "CNAME"
    },
    {
      "comments": [],
      "name": "mail.example.jp.",
      "records": [
        {
          "content": "192.168.1.200",
          "disabled": false
        }
      ],
      "ttl": 600,
      "type": "A"
    },
    {
      "comments": [],
      "name": "example.jp.",
      "records": [
        {
          "content": "10 mail.example.jp.",
          "disabled": false
        }
      ],
      "ttl": 600,
      "type": "MX"
    },
    {
      "comments": [],
      "name": "example.jp.",
      "records": [
        {
          "content": "a.misconfigured.dns.server.invalid. hostmaster.example.jp. 2024082702 10800 3600 604800 3600",
          "disabled": false
        }
      ],
      "ttl": 3600,
      "type": "SOA"
    },
    {
      "comments": [],
      "name": "example.jp.",
      "records": [
        {
          "content": "slave1.future.local.",
          "disabled": false
        },
        {
          "content": "slave2.future.local.",
          "disabled": false
        }
      ],
      "ttl": 3600,
      "type": "NS"
    },
    {
      "comments": [],
      "name": "example.jp.",
      "records": [
        {
          "content": "192.168.1.200",
          "disabled": false
        }
      ],
      "ttl": 600,
      "type": "A"
    }
  ],
  "serial": 2024082702,
  "slave_tsig_key_ids": [],
  "soa_edit": "",
  "soa_edit_api": "DEFAULT",
  "url": "/api/v1/servers/localhost/zones/example.jp."
}

正常にレコード追加ができていますね!
もちろん名前解決も可能です。

# dig @localhost example.jp axfr

; <<>> DiG 9.16.23-RH <<>> @localhost example.jp axfr
; (2 servers found)
;; global options: +cmd
example.jp.             3600    IN      SOA     a.misconfigured.dns.server.invalid. hostmaster.example.jp. 2024082702 10800 3600 604800 3600
example.jp.             600     IN      A       192.168.1.200
example.jp.             600     IN      MX      10 mail.example.jp.
example.jp.             3600    IN      NS      slave1.future.local.
example.jp.             3600    IN      NS      slave2.future.local.
mail.example.jp.        600     IN      A       192.168.1.200
www.example.jp.         600     IN      CNAME   example.jp.
example.jp.             3600    IN      SOA     a.misconfigured.dns.server.invalid. hostmaster.example.jp. 2024082702 10800 3600 604800 3600
;; Query time: 3 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Tue Aug 27 19:43:48 JST 2024
;; XFR size: 8 records (messages 3, bytes 404)

さて、最後にレコードの編集をしてみます。

先ほどのexample.jpのAレコードで、IPアドレスを変更することを考えます。
ただし、条件として192.168.1.200の場合のみ192.168.1.222に変更したいとします。
そんな場合のサンプルが以下になります(change_record.pl)。
foreachでゾーン内の各リソースレコードをチェックし、条件にマッチするかを確認していますが、Perlで書くと若干分かりづらい気がしなくもないですね。。
例によってエラー処理等は適宜必要です。

#!/usr/bin/perl
#

use strict;
use LWP::UserAgent;
use JSON;

my $url_zone = "http://127.0.0.1:8081/api/v1/servers/localhost/zones";
my $template_rrsets = << '_EOT_';
{"rrsets":[
{"name": "###DOMAIN###.", "type": "A", "ttl": 600, "changetype": "REPLACE", "records": [{"content": "###IPADDR###", "disabled": false}]}
]}
_EOT_

# variable
my $url;
my $req;
my $res;
my $content;
my $content_req;
my $data;
my $rr;
my $domain = 'example.jp';
my $ipaddr = '192.168.1.200';
my $ipaddr_new = '192.168.1.222';
my $KEY = 'TestAPIKey';

my $ua = LWP::UserAgent->new;

$url = "$url_zone"."/"."$domain".".";
$req = HTTP::Request->new(GET => $url);
$req->header("X-API-Key" => $KEY);
$res = $ua->request($req);
$content = $res->content;
$data = decode_json($content);
$rr = $data->{'rrsets'};

foreach my $item (@{$rr}) {
  if ( $item->{'name'} eq $domain."." && $item->{'type'} eq 'A' ) {
    my $records = $item->{'records'};
    if ( $#$records == 0 )  {
      if ( "$records->[0]->{'content'}" eq $ipaddr ) {
        $content_req = $template_rrsets;
        $content_req =~ s/###DOMAIN###/$domain/g;
        $content_req =~ s/###IPADDR###/$ipaddr_new/g;

        $req = HTTP::Request->new(PATCH => $url);
        $req->header("X-API-Key" => $KEY);
        $req->header( 'Content-Type' => 'application/json' );
        $req->content($content_req);

        $res = $ua->request($req);
      }
      exit;
    }
  }
}

では実行します。

# perl change_record.pl

今回も特にメッセージはありませんが、サンプルなので暖かい目で見守ってください。。
AXFRしてみると、正常に変更されていることが確認できます。

# dig @localhost example.jp axfr

; <<>> DiG 9.16.23-RH <<>> @localhost example.jp axfr
; (2 servers found)
;; global options: +cmd
example.jp.             3600    IN      SOA     a.misconfigured.dns.server.invalid. hostmaster.example.jp. 2024082703 10800 3600 604800 3600
example.jp.             600     IN      A       192.168.1.222
example.jp.             600     IN      MX      10 mail.example.jp.
example.jp.             3600    IN      NS      slave1.future.local.
example.jp.             3600    IN      NS      slave2.future.local.
mail.example.jp.        600     IN      A       192.168.1.200
www.example.jp.         600     IN      CNAME   example.jp.
example.jp.             3600    IN      SOA     a.misconfigured.dns.server.invalid. hostmaster.example.jp. 2024082703 10800 3600 604800 3600
;; Query time: 2 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Tue Aug 27 20:14:39 JST 2024
;; XFR size: 8 records (messages 3, bytes 404)

めでたしめでたし!

というわけで

今回は応用編として、APIでゾーンの登録からレコードの追加、編集のサンプルをご説明しました。
早足でやや説明不足な感は否めませんが、利用のイメージはつかめるのではないかと思います。
使いこなせば色々できるので便利ですね。
それではまた!