フルサービスリゾルバとしての PowerDNS Recursor

・LAN 内のゾーンについてのクエリは権威サーバーに転送する
・それ以外のクエリは反復問い合わせする
構成の例です。

## 環境
サーバーIPアドレス: `192.168.182.12`
AlmaLinux 9.5
PowerDNS Recursor 5.2.2
SELinux 有効
`192.168.182.10` 上に以下記事同等の権威サーバーが構築されていることを前提とします。

https://qiita.com/ahra_/items/77155e008869b72309e6

このサーバーの IP アドレスは `192.168.182.12` です。
## インストール
公式のリポから stable な最新版をインストールします。
```
# dnf install epel-release && dnf upgrade
# curl https://repo.powerdns.com/repo-files/el-rec-52.repo -o /etc/yum.repos.d/powerdns-rec-52.repo 
# dnf install pdns-recursor
```
## 設定

PowerDNS Recursor 5.00 から定義ファイルは YAML 形式になりました。
以下の定義ファイルを作成します。
DNSSEC を有効にしていない権威サーバーのゾーンに対してネガティブトラストアンカーを設定し、DNSEEC 検証を無効化しています。

:::note warn
PowerDNS Recursor 5.2.0 はRFC 5011 トラストアンカー自動更新に対応していないため、ルートゾーン KSK ロールオーバーの度にトラストアンカーを手動で更新する必要があります。
:::

```/etc/pdns-recursor/recursor.yml
dnssec:
    validation: validate # 常時 DNSSEC 検証を実行
    negative_trustanchors: 
    - name: example.com
    - name: 182.168.192.in-addr.arpa
    
incoming:
    allow_from: # ACL を設定 
    - '127.0.0.0/8'
    - '192.168.182.0/24'
    - '192.168.207.0/24'
    listen:
    - '127.0.0.1'
    - '192.168.182.12'

recursor:
    forward_zones: # このゾーンのクエリは権威サーバーに転送 
    - zone: example.com
      forwarders:
      - 192.168.182.10
    - zone: 182.168.192.in-addr.arpa
      forwarders:
      - 192.168.182.10

    version_string:
```

:::note info
組み込み以外のルートヒントファイルを使用する場合は以下のようにします。
```diff:/etc/pdns-recursor/recursor.yml
（前略）
recursor:
+    hint_file: '/etc/pdns-recursor/recursor.d/named.root'
（後略）
```
:::
## 起動
通信許可を設定し、PowerDNS Recursor を起動します。
```
# firewall-cmd --add-service=dns --permanent
# firewall-cmd --reload
# systemctl enable pdns-recursor
# systemctl start pdns-recursor
```
## 確認
以下の通り解決できます。
```192.168.182.10 上のレコード
# dig @192.168.182.12 dns1.example.com.

; <<>> DiG 9.18.30-0ubuntu0.22.04.2-Ubuntu <<>> @192.168.182.12 dns1.example.com.
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 38337
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

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

;; ANSWER SECTION:
dns1.example.com.       86064   IN      A       192.168.182.10

;; Query time: 10 msec
;; SERVER: 192.168.182.12#53(192.168.182.12) (UDP)
;; WHEN: Thu May 01 11:53:50 JST 2025
;; MSG SIZE  rcvd: 61

# dig @192.168.182.12 -x 192.168.182.12

; <<>> DiG 9.18.30-0ubuntu0.22.04.2-Ubuntu <<>> @192.168.182.12 -x 192.168.182.12
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 31820
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;12.182.168.192.in-addr.arpa.   IN      PTR

;; ANSWER SECTION:
12.182.168.192.in-addr.arpa. 86374 IN   PTR     dns-r.example.com.

;; Query time: 0 msec
;; SERVER: 192.168.182.12#53(192.168.182.12) (UDP)
;; WHEN: Thu May 01 11:54:06 JST 2025
;; MSG SIZE  rcvd: 87
```
```:自身で反復問い合わせするレコード
# dig @192.168.182.12 google.com.

; <<>> DiG 9.18.30-0ubuntu0.22.04.2-Ubuntu <<>> @192.168.182.12 google.com.
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 9691
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

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

;; ANSWER SECTION:
google.com.             300     IN      A       172.217.175.14

;; Query time: 60 msec
;; SERVER: 192.168.182.12#53(192.168.182.12) (UDP)
;; WHEN: Thu May 01 11:55:08 JST 2025
;; MSG SIZE  rcvd: 55
```
DNSSEC 検証についても正しく成功・失敗していることがわかります。
```:正しく DNSSEC が設定されているドメイン
# delv @192.168.182.12 jprs.co.jp. +vtrace
;; fetch: jprs.co.jp/A
;; validating jprs.co.jp/A: starting
;; validating jprs.co.jp/A: attempting positive response validation
;; fetch: jprs.co.jp/DNSKEY
;; validating jprs.co.jp/DNSKEY: starting
;; validating jprs.co.jp/DNSKEY: attempting positive response validation
;; fetch: jprs.co.jp/DS
;; validating jprs.co.jp/DS: starting
;; validating jprs.co.jp/DS: attempting positive response validation
;; fetch: jp/DNSKEY
;; validating jp/DNSKEY: starting
;; validating jp/DNSKEY: attempting positive response validation
;; fetch: jp/DS
;; validating jp/DS: starting
;; validating jp/DS: attempting positive response validation
;; fetch: ./DNSKEY
;; validating ./DNSKEY: starting
;; validating ./DNSKEY: attempting positive response validation
;; validating ./DNSKEY: verify rdataset (keyid=20326): success
;; validating ./DNSKEY: marking as secure (DS)
;; validating jp/DS: in fetch_callback_dnskey
;; validating jp/DS: keyset with trust secure
;; validating jp/DS: resuming validate
;; validating jp/DS: verify rdataset (keyid=53148): success
;; validating jp/DS: marking as secure, noqname proof not needed
;; validating jp/DNSKEY: in fetch_callback_ds
;; validating jp/DNSKEY: dsset with trust secure
;; validating jp/DNSKEY: verify rdataset (keyid=35821): success
;; validating jp/DNSKEY: marking as secure (DS)
;; validating jprs.co.jp/DS: in fetch_callback_dnskey
;; validating jprs.co.jp/DS: keyset with trust secure
;; validating jprs.co.jp/DS: resuming validate
;; validating jprs.co.jp/DS: verify rdataset (keyid=783): success
;; validating jprs.co.jp/DS: marking as secure, noqname proof not needed
;; validating jprs.co.jp/DNSKEY: in fetch_callback_ds
;; validating jprs.co.jp/DNSKEY: dsset with trust secure
;; validating jprs.co.jp/DNSKEY: verify rdataset (keyid=63574): success
;; validating jprs.co.jp/DNSKEY: marking as secure (DS)
;; validating jprs.co.jp/A: in fetch_callback_dnskey
;; validating jprs.co.jp/A: keyset with trust secure
;; validating jprs.co.jp/A: resuming validate
;; validating jprs.co.jp/A: verify rdataset (keyid=43611): success
;; validating jprs.co.jp/A: marking as secure, noqname proof not needed
; fully validated
jprs.co.jp.             300     IN      A       117.104.133.165
jprs.co.jp.             300     IN      RRSIG   A 8 3 300 20250528011241 20250428011241 43611 jprs.co.jp. QFc/yz2CMLQ2yegyXMr59NjGCqR324jgvxtORFyouTzEA01qUT7BBUiT DeuE2A+VVa1NwJgz9ZKJbiLJLKOqSKuzwZ2DRZzY0qL9dvhA7/v5MAzO yZfDPPExND0hnC9G+NUYdZHrSpYQGFrAGTsAps9pWf1vZ3Ys8KFWktdx vnY=
```
```:意図的に不正な設定がされているドメイン
# delv @192.168.182.12 dnssec-failed.mufj.jp. +vtrace
;; fetch: dnssec-failed.mufj.jp/A
;; resolution failed: SERVFAIL
```
## 補足
フォワーダーとして使用する場合は以下のようにします。（以下は Google Public DNS に転送する例）
```diff:/etc/pdns-recursor/recursor.yml
（前略）
recursor:
    forward_zones: 
    - zone: example.com
      forwarders:
      - 192.168.182.10
    - zone: 182.168.192.in-addr.arpa
      forwarders:
      - 192.168.182.10

+   forward_zones_recurse: # 再帰問い合わせの転送先の指定
+   - zone: .
+     forwarders:
+     - 8.8.8.8

    version_string:
```