ＢＩＮＤ９で見るカタログゾーン機能について

# はじめに

ＤＮＳコンテンツサーバーを自前で運用しなくなった時代ではありますが、そんな運用をしないといけない人向けに、運用負荷低減のための仕組みついて紹介します。
具体的にはプライマリーコンテンツサーバー[^abbrev1]とセカンダリーコンテンツサーバー[^abbrev2]間でゾーン管理を簡易化する仕組みであるところの「カタログゾーン」（catalog zones）機能について説明します。
なお従来、マスター・スレーブと呼称していたこれら名称は、（ポリティカルコレクトネス的要請により削除）のため、プライマリー・セカンダリーと呼称するようになりました。
互換性の観点から `type master;` および `type slave;` といった設定は使えますが、`type primary;` および `type secondary;` という設定に置き換えて行きましょう！
一応（ポリティカルコレクトネス的要請により削除）のため、権威サーバーという表現もコンテンツサーバーと表現するようにしています。まぁこちらは実務的な話ではありますが。

なお本題において、ＢＩＮＤ９の設定を行ったことがある人を対象に書いています。想定バージョンは９．１８です。

[^abbrev1]: 以後プライマリーサーバーと呼称
[^abbrev2]: 以後セカンダリーサーバーと呼称

# カタログゾーンとは

https://kb.isc.org/docs/aa-01401

カタログゾーンは、プライマリーサーバーからセカンダリサーバーへ「転送すべきゾーン」を管理するためのゾーンです。
ゾーンの形式を取ることで、プライマリーサーバー・セカンダリーサーバー間の管理すべきゾーン情報を、従来のＡＸＦＲ／ＩＸＦＲを通して共有できるようになります。

ゾーンを管理するゾーンということで、各種ゾーン機能（クエリー応答の可否、転送可否など）を制御して、プライマリーサーバーで管理しているゾーンをセカンダリーサーバーでも設定されます。セカンダリーサーバーの設定変更無しに、プライマリーサーバー側だけで自由にゾーンの追加・削除を行うことができるようになります。
これは従来の運用形態から考えると設定漏れが起きにくい仕組みではあります。合わせてセカンダリーサーバーで作業しなくてよいメリットがあります。

この機能に対応しているサーバーはＢＩＮＤ９、[ＫｎｏｔＤＮＳ](https://www.knot-dns.cz/docs/3.2/html/configuration.html#catalog-zones)、[ＰｏｗｅｒＤＮＳ]()であることを確認しています（サポート状態は未確認）。
ここではフルスペック（プライマリーサーバー・セカンダリーサーバー共に）で対応しているＢＩＮＤ９を例に説明します。

なお本機能はＢＩＮＤ９．１１より対応（バージョン１）していますが、ＢＩＮＤ９．１８．３以降対応のバージョン２で例示します。

## プライマリーコンテンツＤＮＳサーバーでのカタログゾーンの設定
`named.conf` 側には `allow-new-zones` の設定と、`catalog.exmaple` ゾーンの追加を実施します。
`catalog.example` というゾーン名は `catalog.屋号` 程度に設定しておくのがよいと思います。
運用上どれくらい自由度があるのかは不明ですが、実在しそうに無い、ジャンクなゾーン名がいいかもしれません。
下手な名前にすると混乱を引き起こす可能性がありますが。

下記例では、極めてミニマムに設定しています。このゾーン自体はセカンダリーサーバーからしかアクセスがないので、アクセス制限を実施するなどの配慮が必要です。
この例ではセカンダリーサーバーのＩＰアドレスアドレスからの問い合わせかつ、特定のＴＳＩＧキー（[後述](#プライマリーサーバーセカンダリーサーバー間で共有するｔｓｉｇキー)）による問い合わせが行われていること、でアクセスを制限しています。

```text:named.conf
options {
    allow-new-zones         yes;
}

zone "catalog.example" {
    type primary;
    file "primary/catalog.example.db";
    allow-query  {
        { !{ !セカンダリーサーバーのＩＰアドレスｖ４/32;  "any"; }; key "ＴＳＩＧキー名"; };
        { !{ !セカンダリーサーバーのＩＰアドレスｖ６/128; "any"; }; key "ＴＳＩＧキー名"; };
    };
    allow-transfer  {
        { !{ !セカンダリーサーバーのＩＰアドレスｖ４/32;  "any"; }; key "ＴＳＩＧキー名"; };
        { !{ !セカンダリーサーバーのＩＰアドレスｖ６/128; "any"; }; key "ＴＳＩＧキー名"; };
    };
};
```

また上記設定に合わせるようにゾーン情報を書きます。この部分の書き方については下記のテンプレそのまま利用します。

```text:catalog.example.db
@                   IN SOA  . . 1 86400 3600 86400 3600
@                   IN NS   invalid.
version             IN TXT  "2"
```

- ＳＯＡレコードのシリアルを１から始めてますが、日付ベースの指定でも構いません。
- いずれにせよレコード変更の度にシリアルを上げます。
- ゾーンを構成するため、ＳＯＡとＮＳレコードが（ダミーでも）必須。
- カタログゾーンとして運用するためには `version` レコードが必須となります。

## セカンダリーコンテンツＤＮＳサーバーでのカタログゾーンの設定

```text:named.conf
options {
    catalog-zones {
        zone "catalog.example";
    };
};

zone "catalog.example"  {
    type secondary;
    file "secondary/catalog.example.db";
    primaries { プライマリーサーバーのＩＰアドレス key "ＴＳＩＧキー名"; }
};
```

## プライマリーサーバー／セカンダリーサーバー間で共有するＴＳＩＧキー

プライマリーサーバー側でＴＳＩＧキーを生成します。
「ＴＳＩＧキー名」は何でも構わないのですが、どういう名前にするのか悩んだ場合、[ＢＩＮＤ９のマニュアル](https://bind9.readthedocs.io/en/latest/chapter7.html#generating-a-shared-key)によれば、プライマリーサーバーのホスト名とセカンダリーのホスト名を繋いだものが例示されています。

プライマリーサーバーを `ns0.example.jp`、セカンダリーを `ns1.example.jp` とした時、`ns0-ns1.` という名前で、ＴＳＩＧキーファイル名は `ns0-ns1.key` となります。

```console
$ sudo tsig-keygen -a hmac-sha256 ＴＳＩＧキー名 > ＴＳＩＧキー.key
$ sudo chmod 0400 ＴＳＩＧキー.key
```

このファイルをセカンダリーにもコピーします。プライマリーサーバーもセカンダリサーバーも同じく、named.conf には以下のような形で設定を読み込みます。

```text:named.conf
include "ＴＳＩＧキー.key";
```

# ゾーンの追加方法
`example.jp` というゾーンを追加してみます。
事前に、プライマリーサーバー側にてゾーンファイルの作成および `zone "example.jp" { ... };` の設定を行った後、カタログゾーンにレコードを追加します。

まずこのゾーン名からハッシュ値を生成します。シンプルに[ワイヤー形式](#ワイヤー形式)をＳＨＡ１でハッシュ化します。
取り出しましたる `eb7b38678a581b9e32078e8b009d0c5c789bce7a` という文字列を下記のように設定します。

```text:catalog-example.db
@                                              IN SOA  . . 2 86400 3600 86400 3600
eb7b38678a581b9e32078e8b009d0c5c789bce7a.zones IN PTR  example.jp.
```

その後 `rndc reload` でゾーンファイルを読み直します。

# ワイヤー形式

ハッシュ値を計算するための元となるワイヤー形式（wire format）について説明します。
これの定義については[ＲＦＣ１０３５](https://www.rfc-editor.org/rfc/rfc1035)の `3.1 Name space definitions` に準拠します。

ドメイン名（ゾーン名） `example.jp` を例に取ると、以下のようにして組み立てす。

- `.` を基準に分割します。その際、最後に `.` があるものとして３つに分割します。
- 具体的には `exmaple`、`jp`、` ` という「ラベル」になります。
- 各文字列を数えます。７文字、２文字、０文字となります。
- 各レベルの直前に文字数をバイナリ（１オクテットの）で表現して`printf "\7example\2jp\0"` コマンドを実行します（バイナリです）。

この時得られるバイナリのことをワイヤー形式となります。

```console
$ printf "\7example\2jp\0" | hexdump -C
00000000  07 65 78 61 6d 70 6c 65  02 6a 70 00              |.example.jp.|
0000000c
```

これをＳＨＡ１でハッシュ化します。

```console
$ printf "\7example\2jp\0" | openssl sha1
(stdin)= eb7b38678a581b9e32078e8b009d0c5c789bce7a
```

この時得られた `eb7b38678a581b9e32078e8b009d0c5c789bce7a` をカタログゾーンに設定します。

# 実装依存のカスタムプロパティの指定

セカンダリサーバーでの挙動を制御するためのカスタムプロパティが設定できます。
このカスタムプロパティは実装依存で、以下の説明ではＢＩＮＤ９の仕様に基づいて説明しています。
他のサーバーでは機能する保証が無いため、都度確認する必要があります。

## デフォルトプライマリーサーバーの指定

以下の設定を行うことで、デフォルトのプライマリーサーバーを指定することができます。

```text:catalog.example.db
label.primaries.ext IN A    XXX.XXX.XXX.XXX
label.primaries.ext IN AAAA XXXX::XXX
label.primaries.ext IN TXT  "ＴＳＩＧキー名"
```

- `label.primaries.ext` でプライマリーサーバー情報を設定します。
- ＡおよびＡＡＡＡレコードで、プライマリーサーバーのＩＰアドレスを設定します。
- ＴＸＴレコードでＴＳＩＧキー名を指定します。

これはセカンダリサーバーに対して下記のを行ったのと同じになります。なお同時に設定した場合の振る舞い（どちらを優先するか）は未確認です。

```text:named.conf ※セカンダリーサーバーの設定例
options {
    catalog-zones {
        zone "catalog.example"
        default-primaries {
            プライマリーサーバーのＩＰｖ４アドレス key "ＴＳＩＧキー名";
            プライマリーサーバーのＩＰｖ６アドレス key "ＴＳＩＧキー名";
        };
    };
};
```

## デフォルトのクエリー許可設定

ＴＯＤＯ

```text:catalog.example.db
allow-query.ext IN APL 1:0.0.0.0/0 2:::/0
```

## デフォルトのゾーン転送許可設定

ＴＯＤＯ

```text:catalog.example.db
allow-query.ext IN APL 1!:0.0.0.0/0 2!:::/0
```

## ゾーン毎のカスタムの事例

ＴＯＤＯ

```text:catalog-example.db
label.primaries.ext.eb7b38678a581b9e32078e8b009d0c5c789bce7a.zones IN A    XXX.XXX.XXX.XXX
label.primaries.ext.eb7b38678a581b9e32078e8b009d0c5c789bce7a.zones IN AAAA XXXX::XXX
label.primaries.ext.eb7b38678a581b9e32078e8b009d0c5c789bce7a.zones IN TXT  "ＴＳＩＧキー名"
allow-query.ext.eb7b38678a581b9e32078e8b009d0c5c789bce7a.zones     IN APL 1:0.0.0.0/0 2:::/0
allow-query.ext.eb7b38678a581b9e32078e8b009d0c5c789bce7a.zones     IN APL 1!:0.0.0.0/0 2!:::/0
```

# よくある質問とその答え

ＴＯＤＯ

## Ｑ．`primaries.ext` レコードと `label.primaries.ext` レコードがあるようですが、違いは何？
Ａ．どちらもプライマリーサーバーを指定するモノですが、後者はＴＳＩＧキーを指定して、アクセス制御（認証）を行う点が違います。前者の使用は推奨されませんので、ここでは無視します。

## Ｑ．`primaries.ext` レコードと `label.primaries.ext` レコードを同時に指定した場合の振る舞いは？
Ａ．知らんでもいい…のですが、試したところ、指定された分、下記の設定とイコールになります。

```text:named.conf ※セカンダリーサーバーの設定例
options {
    catalog-zones {
        zone "catalog.example"
        default-primaries {
            primaries.ext で指定されたＩＰアドレス;
            label.primaries.ext で指定されたＩＰアドレス key "ＴＳＩＧキー名";
        };
    };
};
```

この時 `primaries.ext` で指定された分のアクセスが行われた後、`label.primaries.ext` で指定された分のアクセスがラウンドロビンで実施されます[^manual]。

[^manual]: マニュアルによるとランダムでアクセスされる…とあるが…。

## Ｑ．バージョン１とバージョン２の違いは？
Ａ．カスタムプロパティのレコードに `.ext` というサフィックスが付くか、付かないかの差だそうです。多分ですが、`.zones.catalog.example.` サブドメインと `.ext.catalog.example.` サブドメインとで、対称的に設定させたかったのでは無いかとみてます。

## Ｑ．ＴＯＤＯ
Ａ．ＴＯＤＯ

# 参考文献

検索して出てくる資料も悪くないですが、本家ドキュメント（英語ではありますが）を参照の上で設定しましょう。
参考資料では想定が甘かったり、抜けていたり、変っていたり、間違っているケースが多々見られます。
正しい運用は正しい設定から始まります。

- [ＢＩＮＤ９ドキュメントの概要](https://kb.isc.org/docs/aa-01031)
- [ＢＩＮＤ９最新管理者リファレンス マニュアル](https://bind9.readthedocs.io/en/latest/)
- [ＢＩＮＤ９．１８管理者リファレンス マニュアル](https://bind9.readthedocs.io/en/v9_18_9/)
- [ＤＮＳのレプリケーション設定と運用](https://wiki.ninth-nine.com/bind9/ＤＮＳのレプリケーション設定と運用)
- [A short introduction to Catalog Zones](https://kb.isc.org/docs/aa-01401)
- [Catalog zones in BIND 9.11](https://jpmens.net/2016/05/24/catalog-zones-are-coming-to-bind-9-11/)
- [ＤＮＳソフトウェア最新動向（権威ＤＮＳサーバー編）](https://www.nic.ad.jp/ja/materials/iw/2021/proceedings/c2/c2-aharen-2.pdf)
- [implement a Catalog zones](https://dnsops.jp/event/20170628/catlogz-one.pdf)
- [ひとりRFCの旅 introduction && RFC1035 DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION](https://maleic1618.hatenablog.jp/entry/solo-rfc-advent-1)