Fullmoon System

Kea DDNS構築:BIND 9・TSIGによる正引きと逆引きの自動更新

EdwardMoon

Kea DDNSは、DHCPでアドレスを貸し出す際、ホスト名のAレコードとIPのPTRレコードをBIND 9へ自動登録する構成です。DHCPv4がゾーンファイルを直接変更するのではなく、Name Change RequestをKea D2へ送り、D2がRFC 2136の動的更新をTSIGで署名してBIND 9 primaryへ転送します。

例では10.20.30.0/24と内部ドメインlab.example.internalを使います。ISC DHCPのddns-update-style設定をそのままKeaへ移しても動きません。役割分担、鍵の保護、正引き・逆引きゾーンの一致、DHCIDの競合処理、サービスの起動順序をそろえる必要があります。コマンドと設定はコードブロックに分けています。

Kea DDNSでDHCPリースをBIND 9の正引き・逆引きDNSへ反映する流れ
DHCPv4がリース変更をD2へ伝え、D2がTSIGで署名した更新をBIND 9の正引き・逆引きゾーンへ反映します。

構成要素とデータの流れ

この演習では、Rocky Linux 9の10.20.30.53という1台にKea DHCPv4、D2、BINDを同居させます。鍵の生成、ローカルのnsupdate、サービス操作はすべてこのサーバーで行い、クライアント検証だけを別の試験端末で行います。本番でDHCPとDNSを分離する場合は、TSIG秘密情報を保護された管理経路でD2ホストへ配布し、D2のDNS宛先とファイアウォールを調整してください。

構成要素例のアドレス役割
Kea DHCPv410.20.30.53アドレスリース、ホスト名方針、D2へのNCR送信
Kea D2127.0.0.1:53001NCRをDNS UPDATEへ変換し、TSIGで署名
BIND 9 primary10.20.30.53:53正引き・逆引きdynamic zoneの権威サーバー
クライアント10.20.30.100~200DHCPREQUESTで名前またはFQDNを提供
  • 正引きゾーンlab.example.internal.には、ホスト名からIPv4を求めるAレコードが作成される。
  • 逆引きゾーン30.20.10.in-addr.arpa.には、IPv4からFQDNを求めるPTRレコードが作成される。
  • DHCIDは、別のDHCPクライアントが同じ名前を上書きする競合を減らすために使う。
  • TSIGは更新メッセージの認証と完全性を提供するが、DNS照会の応答自体は暗号化しない。

公式のKea DHCP-DDNS ServerKea DHCPv4のDDNS設定BIND 9の動的更新ポリシーを併せて確認してください。

手順1:事前確認とパッケージの確認

KeaとBIND 9は、サポートされる配布パッケージか承認済みISCリポジトリから導入します。無闇にリポジトリを追加せず、候補バージョンと署名を先に確認してください。以下はEL9系のパッケージ名の例です。

# Kea DHCP構築記事と同じISC 3.0 LTSリポジトリを先に準備します。
curl --fail --location --proto '=https' --tlsv1.2 \
  https://dl.cloudsmith.io/public/isc/kea-3-0/setup.rpm.sh \
  --output /tmp/isc-kea-3-0-setup.rpm.sh
less /tmp/isc-kea-3-0-setup.rpm.sh
sudo bash /tmp/isc-kea-3-0-setup.rpm.sh

sudo dnf --showduplicates list isc-kea-dhcp4 isc-kea-dhcp-ddns bind bind-utils
sudo dnf install -y isc-kea-dhcp4 isc-kea-dhcp-ddns bind bind-utils policycoreutils-python-utils
rpm -q isc-kea-dhcp4 isc-kea-dhcp-ddns bind bind-utils
kea-dhcp4 -V
named -V
systemctl show kea-dhcp-ddns -p User -p Group -p ExecStart

ホスト名、時刻、インターフェース、既存のDHCP・DNSリスナーとの競合を先に確認します。

hostnamectl
chronyc tracking
ip -br address
sudo ss -luntp | grep -E ':(53|67|53001)\b' || true
getent passwd named
systemctl show kea-dhcp-ddns -p User -p Group

手順2:TSIG秘密鍵を生成し、権限を分離する

BIND 9とD2は同じTSIG secretを使う必要があります。画面やシェル履歴へ表示せず、rootで生成してから、BIND用keyブロックとKea用secretファイルをそれぞれ最小権限で配置します。末尾のドットを含め、鍵名を両設定で完全に一致させてください。

sudo install -d -o root -g named -m 0750 /etc/named/keys
sudo sh -c 'umask 077; tsig-keygen -a hmac-sha256 kea-ddns.lab.example.internal. > /etc/named/keys/kea-ddns.key'
sudo chown root:named /etc/named/keys/kea-ddns.key
sudo chmod 0640 /etc/named/keys/kea-ddns.key

Kea 2.5.8以降では、設定へsecretを直接書く代わりにsecret-fileを使用できます。

# D2の実行アカウントとグループを確認します。systemdのUserが空ならrootです。
D2_USER=$(systemctl show kea-dhcp-ddns -p User --value)
D2_USER=${D2_USER:-root}
D2_GROUP=$(systemctl show kea-dhcp-ddns -p Group --value)
D2_GROUP=${D2_GROUP:-$(id -gn "$D2_USER")}
sudo install -d -o root -g "$D2_GROUP" -m 0750 /etc/kea/secrets
sudo install -o root -g "$D2_GROUP" -m 0640 /dev/null /etc/kea/secrets/kea-ddns.secret
sudo awk -F'"' '/secret/{print $2}' /etc/named/keys/kea-ddns.key \
  | sudo tee /etc/kea/secrets/kea-ddns.secret >/dev/null
sudo restorecon -Rv /etc/kea/secrets
sudo stat -c '%U:%G %a %n' /etc/named/keys/kea-ddns.key /etc/kea/secrets/kea-ddns.secret

TSIG secretを記事、チケット、Git、コマンド出力へコピーしないでください。漏えいが疑われたら新しい鍵を作り、計画した順序でBINDとD2を切り替えてから旧鍵を失効させます。

手順3:BIND 9のdynamic zoneを設定する

/etc/named.confでTSIG鍵ファイルをincludeし、正引き・逆引きのprimary zoneを定義します。

include "/etc/named/keys/kea-ddns.key";

zone "lab.example.internal" IN {
    type primary;
    file "dynamic/db.lab.example.internal";
    allow-update { key "kea-ddns.lab.example.internal."; };
};

zone "30.20.10.in-addr.arpa" IN {
    type primary;
    file "dynamic/db.10.20.30";
    allow-update { key "kea-ddns.lab.example.internal."; };
};

allow-updateはその鍵にゾーン全体の更新権限を与えます。名前やレコードを細かく制限する場合はupdate-policyを設計しますが、同じゾーンで両方を併用しないでください。

/etc/named.confの既存optionsブロック内で以下を変更し、optionsを重複追加しないでください。このサーバーは内部クライアントの再帰DNSにも使うため、照会と再帰の許可範囲を演習ネットワークに限定します。

listen-on port 53 { 127.0.0.1; 10.20.30.53; };
listen-on-v6 port 53 { ::1; };
allow-query { localhost; 10.20.30.0/24; };
recursion yes;
allow-recursion { localhost; 10.20.30.0/24; };

次の2つのゾーンを現在の作業ディレクトリにdb.lab.example.internaldb.10.20.30として保存し、その後のinstallで配置します。

正引きゾーンの初期ファイル

$TTL 300
@   IN SOA dns01.lab.example.internal. hostmaster.lab.example.internal. (
        2026072101  ; serial
        3600        ; refresh
        900         ; retry
        604800      ; expire
        300 )       ; minimum
    IN NS  dns01.lab.example.internal.
dns01 IN A 10.20.30.53

逆引きゾーンの初期ファイル

$TTL 300
@   IN SOA dns01.lab.example.internal. hostmaster.lab.example.internal. (
        2026072101  ; serial
        3600        ; refresh
        900         ; retry
        604800      ; expire
        300 )       ; minimum
    IN NS  dns01.lab.example.internal.
53  IN PTR dns01.lab.example.internal.
sudo install -d -o named -g named -m 0770 /var/named/dynamic
sudo install -o named -g named -m 0660 db.lab.example.internal /var/named/dynamic/
sudo install -o named -g named -m 0660 db.10.20.30 /var/named/dynamic/
sudo restorecon -Rv /etc/named/keys /var/named/dynamic
sudo named-checkconf /etc/named.conf
sudo named-checkzone lab.example.internal /var/named/dynamic/db.lab.example.internal
sudo named-checkzone 30.20.10.in-addr.arpa /var/named/dynamic/db.10.20.30

dynamic zoneのジャーナルはnamedが作成します。SELinuxを無効にしたり/var/named全体を0777にしたりせず、パッケージ既定のコンテキストを使い、dynamicディレクトリだけを書き込み可能に保ちます。

sudo systemctl enable --now named
sudo systemctl --no-pager --full status named
sudo journalctl -u named -b --no-pager | tail -n 100
dig @127.0.0.1 SOA lab.example.internal +norecurse
dig @127.0.0.1 SOA 30.20.10.in-addr.arpa +norecurse

手順4:BINDの動的更新を単独で試験する

Keaを接続する前にnsupdateでBINDとTSIGだけを試すと、障害箇所を切り分けられます。一時的なA・PTRを追加し、確認してから削除します。

sudo nsupdate -k /etc/named/keys/kea-ddns.key <<'EOF'
server 127.0.0.1
zone lab.example.internal.
update add ddns-test.lab.example.internal. 300 A 10.20.30.250
send
zone 30.20.10.in-addr.arpa.
update add 250.30.20.10.in-addr.arpa. 300 PTR ddns-test.lab.example.internal.
send
EOF
dig @127.0.0.1 ddns-test.lab.example.internal A +short
dig @127.0.0.1 -x 10.20.30.250 +short
sudo journalctl -u named --since '-5 minutes' --no-pager
sudo nsupdate -k /etc/named/keys/kea-ddns.key <<'EOF'
server 127.0.0.1
update delete ddns-test.lab.example.internal. A
send
update delete 250.30.20.10.in-addr.arpa. PTR
send
EOF

手順5:Kea D2を設定する

/etc/kea/kea-dhcp-ddns.confを以下のように構成します。DNSサーバーのアドレスはBIND 9 primaryを指す必要があります。

{
  "DhcpDdns": {
    "ip-address": "127.0.0.1",
    "port": 53001,
    "dns-server-timeout": 1000,
    "ncr-protocol": "UDP",
    "ncr-format": "JSON",
    "tsig-keys": [
      {
        "name": "kea-ddns.lab.example.internal.",
        "algorithm": "HMAC-SHA256",
        "secret-file": "/etc/kea/secrets/kea-ddns.secret"
      }
    ],
    "forward-ddns": {
      "ddns-domains": [
        {
          "name": "lab.example.internal.",
          "key-name": "kea-ddns.lab.example.internal.",
          "dns-servers": [ { "ip-address": "10.20.30.53", "port": 53 } ]
        }
      ]
    },
    "reverse-ddns": {
      "ddns-domains": [
        {
          "name": "30.20.10.in-addr.arpa.",
          "key-name": "kea-ddns.lab.example.internal.",
          "dns-servers": [ { "ip-address": "10.20.30.53", "port": 53 } ]
        }
      ]
    },
    "loggers": [
      { "name": "kea-dhcp-ddns", "severity": "INFO" }
    ]
  }
}
sudo kea-dhcp-ddns -t /etc/kea/kea-dhcp-ddns.conf
sudo systemctl enable --now kea-dhcp-ddns
sudo systemctl --no-pager --full status kea-dhcp-ddns
sudo ss -lunp | grep ':53001'
sudo journalctl -u kea-dhcp-ddns -b --no-pager | tail -n 100

手順6:DHCPv4からD2へ接続する

この例では、クライアントが更新を要求した際にKeaがAとPTRの両方を管理するよう、ddns-override-client-update: trueを使います。一方、更新禁止要求はddns-override-no-update: falseで尊重します。すべての端末に必ずA・PTRが作られるわけではないため、FQDNオプションと実際のNCRログを併せて確認してください。

次は/etc/kea/kea-dhcp4.confの主要部分です。インターフェース、ルーター、DNS、プールは実ネットワークに合わせます。D2へ要求を送るには、dhcp-ddns.enable-updatesとddns-send-updatesの両方を有効にします。

{
  "Dhcp4": {
    "interfaces-config": { "interfaces": [ "ens192" ] },
    "lease-database": {
      "type": "memfile",
      "persist": true,
      "name": "/var/lib/kea/kea-leases4.csv"
    },
    "renew-timer": 900,
    "rebind-timer": 1800,
    "valid-lifetime": 3600,
    "dhcp-ddns": {
      "enable-updates": true,
      "server-ip": "127.0.0.1",
      "server-port": 53001,
      "ncr-protocol": "UDP",
      "ncr-format": "JSON"
    },
    "ddns-send-updates": true,
    "ddns-override-no-update": false,
    "ddns-override-client-update": true,
    "ddns-replace-client-name": "when-not-present",
    "ddns-generated-prefix": "host",
    "ddns-qualifying-suffix": "lab.example.internal",
    "ddns-update-on-renew": false,
    "ddns-conflict-resolution-mode": "check-with-dhcid",
    "subnet4": [
      {
        "id": 100,
        "subnet": "10.20.30.0/24",
        "pools": [ { "pool": "10.20.30.100 - 10.20.30.200" } ],
        "option-data": [
          { "name": "routers", "data": "10.20.30.1" },
          { "name": "domain-name-servers", "data": "10.20.30.53" },
          { "name": "domain-name", "data": "lab.example.internal" }
        ]
      }
    ],
    "loggers": [ { "name": "kea-dhcp4", "severity": "INFO" } ]
  }
}

ddns-replace-client-nameは名前がない場合だけ生成する設定です。サーバーがすべての名前を決める方針ならalwaysを検討できますが、既存名と競合しない予約・命名規則を先に設計してください。

sudo kea-dhcp4 -t /etc/kea/kea-dhcp4.conf
sudo systemctl enable --now kea-dhcp4
sudo systemctl --no-pager --full status kea-dhcp4
sudo ss -lunp | grep -E ':(67|53001)\b'
sudo journalctl -u kea-dhcp4 -b --no-pager | tail -n 100

手順7:実際のA・PTR・DHCIDを検証する

試験クライアントで新しくリースを取得し、実際のアドレスとFQDNで正引き・逆引きを照会します。本番インターフェースのリース更新は接続を切る可能性があるため、コンソールまたは専用試験端末で行ってください。

# 試験クライアントで実環境に合う方法を使ってリースを更新
sudo dhclient -r ens192
sudo dhclient -v ens192
ip -4 address show dev ens192
dig @10.20.30.53 client01.lab.example.internal A +noall +answer
dig @10.20.30.53 -x 10.20.30.101 +noall +answer
dig @10.20.30.53 client01.lab.example.internal DHCID +noall +answer
sudo journalctl -u kea-dhcp4 -u kea-dhcp-ddns -u named \
  --since '-10 minutes' --no-pager
症状最初の確認項目主な原因
AもPTRもないDHCPv4ログとUDP 53001D2接続が無効、命名方針、D2停止
Aだけ作成されるreverse-ddnsのドメインと逆引きゾーン逆引きゾーン名またはDNS宛先の不一致
NOTAUTHBIND primaryとzone宣言secondaryへの送信、ゾーン名の不一致
NOTAUTHまたはREFUSEDTSIGの名前、secret、algorithm鍵の不一致、allow-updateで未許可
YXDOMAIN・競合既存A・DHCIDとクライアント識別子別クライアントが同じ名前を所有

運用中のdynamic zoneを直接編集しない

namedが稼働中のdynamic zoneは.jnlと一緒に管理されます。直接編集すると変更が上書きされたり、ジャーナルと不一致になったりします。手動変更が必要なら先に同期し、対象ゾーンをfreezeして編集・検証後にthawします。

sudo rndc freeze lab.example.internal
sudoedit /var/named/dynamic/db.lab.example.internal
sudo named-checkzone lab.example.internal /var/named/dynamic/db.lab.example.internal
sudo rndc thaw lab.example.internal
sudo rndc zonestatus lab.example.internal

エラーを急いで消すために.jnlやA・PTR・DHCIDを一括削除しないでください。リースの所有者、正引き・逆引き、DHCIDを併せて確認し、停止、バックアップ、復旧の順序を決めます。

ファイアウォール、SELinux、権限の確認

クライアントと権威DNS間には53/TCP・UDP、DHCPサーバーとクライアント間にはDHCPポートが必要です。D2の53001/UDPは同じホストのloopbackにだけbindしているため、外部に開けません。

sudo firewall-cmd --get-active-zones
sudo firewall-cmd --permanent --zone=internal --add-service=dns
sudo firewall-cmd --permanent --zone=internal --add-service=dhcp
sudo firewall-cmd --reload
sudo firewall-cmd --zone=internal --list-all
sudo ss -luntp | grep -E ':(53|67|53001)\b'
sudo ausearch -m AVC -ts recent | tail -n 50

firewalldやSELinuxの無効化は解決策ではありません。namedの書き込み先をdynamicに限定し、D2のsecretはrootと実際のD2実行アカウントだけが読めるようにします。TSIG権限も必要なゾーンだけに与えます。

HA、バックアップ、監視のチェックリスト

  1. Kea HAの両DHCPサーバーからD2と権威DNSへ到達できることを、フェイルオーバー試験で検証する。
  2. DNS secondaryはprimaryのNOTIFY・IXFR/AXFRで同期し、D2の更新先はprimaryと明示する。
  3. Kea設定、リースDB、BIND設定・zone・journal、TSIG鍵を、それぞれの機密区分でバックアップする。
  4. 鍵の復元権限を限定し、定期的に鍵交換とサービス再起動の順序を練習する。
  5. リース成功だけでなく、D2 queue、DNS UPDATE結果、A・PTRの不一致、ゾーンserialを監視する。
sudo systemctl is-active kea-dhcp4 kea-dhcp-ddns named
sudo kea-dhcp4 -t /etc/kea/kea-dhcp4.conf
sudo kea-dhcp-ddns -t /etc/kea/kea-dhcp-ddns.conf
sudo named-checkconf -z /etc/named.conf
sudo rndc status
sudo rndc zonestatus lab.example.internal

関連記事

まとめ

安全な構築では全サービスを一度に起動せず、BINDゾーンの検証、TSIG単独試験、D2接続、実際のDHCPリース試験という順に境界ごとに確認します。A、PTR、DHCIDを併せて観測し、secretの権限、dynamic zoneのジャーナル、バックアップ、鍵交換まで運用基準に含めることで、自動化による整合性の損失を防ぎます。