Dockerで権威DNSサーバーのNSDを動かしてみた 2022年版

# 前書き
2017年に投稿した内容では古くてそのままでは動作しなくなっているので2022年版として改めて検証も兼ねてメモを残しておきます。

https://qiita.com/ohhara_shiojiri/items/39cb9c3a632676c7b33d

# 背景
DNSの実装と言えば古くからBINDが使われていますが、昨今では脆弱性が度々発覚してセキュリティ的に運用が難しい状況にあります。
BINDに代わるDNSの実装が他にもあり主要なLinuxディストリビューションでも公式パッケージ化されて最新バージョンにも追随しています。

# 動作環境

## ホスト側

- OS:Ubuntu Server 20.04.4LTS
- Docker CE version 20.10.16
- docker compose version v2.5.1

# Docker環境の準備

以下のページにて紹介していますので参考にしてみて下さい。

- [Linuxサーバー環境のDockerインストールメモ - Qiita ](https://qiita.com/ohhara_shiojiri/items/486a54ad895d6bb3144e)

# Dockerコンテナ環境で動かす理由

NSDに限った事では無いのですが、Dockerコンテナの特徴と利点が有用であれば使ってみるのも有りだと思います。

- ホストOS側とコンテナ分離する事でホストOS側への影響が少ない
- 構築したコンテナ環境を他の別環境に移設やバックアップがしやすい
- Kubernetesなどのクラウド・コンテナオーケストレーション環境に展開出来る

# 権威DNSサーバー NSD
コンテンツサーバーとも呼ばれる自組織や他組織のゾーンを管理しているDNSサーバーです。
一般の利用者がドメイン検索で使用するキャッシュDNSサーバーと呼ばれるフルリゾルバーのDNSサーバーが権威DNSサーバーのルートサーバーから順繰りに問い合わせてドメイン検索する仕組みがDNSです。
今回はBINDではなくNSDを使用します。世界に13箇所あるルートサーバーの一部でも採用されています。

https://www.nlnetlabs.nl/projects/nsd/

# ローカルホスト側のリゾルバ設定

ubuntuの場合、systemdでDNSの53番ポートがサービス起動していて、他のDNSサービスとは競合してしまうので、名前解決のローカルリゾルバは動作させたまま53番ポートのListenを無効化させます。

```:/etc/systemd/resolved.conf
[Resolve]
DNSStubListener=no
```

なおnameserverの指定はnetplanの記述に自動修正されます。

```
$ cd /etc
$ sudo ln -sf ../run/systemd/resolve/resolv.conf resolv.conf
$ sudo systemctl restart systemd-resolved
```

## サービスポート変換

クラウドのインスタンス環境などの場合はローカルリゾルバを変更せずに、Dockerコンテナ側NSDを5353番ポートなどの別のサーピスポート番号で動作させて、クラウド側のセキュリティグループ設定でグルーバルIPアドレス側の53番ポートとNAT変換させる方法もあります。

```:nsd/compose.yaml
ports:
  - 5353:53/tcp
  - 5353:53/udp
```

# NSDコンテナの設定

前回投稿で使用したソースは既にメンテナンスがされていない為、新しくフォークされた別のソースを利用します。

https://github.com/The-Kube-Way/nsd

## docker compose設定

適当なディレクトリに「docker compose」ファイルを作成します。

```
$ mkdir -p nsd
$ cd nsd
```

```:nsd/compose.yaml
services:
  nsd:
    container_name: nsd
    restart: always
    image: ghcr.io/the-kube-way/nsd:latest
    tmpfs:
      - /tmp
      - /var/db/nsd
    volumes:
      - ./conf:/etc/nsd:ro
      - ./zones:/zones
      - ./keys:/keys:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - 53:53/tcp
      - 53:53/udp
```

各種設定ファイル類はカレントディレクトリ以下に配置してコンテナ側にマウントさせています。次に各設定ファイルを作成します。

## NSD設定ファイルの作成

```
$ mkdir -p conf
```

```:conf/nsd.conf
server:
  server-count: 1
  ip4-only: yes
  verbosity: 1
  hide-version: yes
  zonesdir: "/zones"

remote-control:
  control-enable: yes

pattern:
  name: "slave"
  zonefile: "%s.zone"
  allow-notify: [Zone転送を許可するIPアドレス] NOKEY
  request-xfr: [Zone転送を許可するIPアドレス] NOKEY

zone:
  name: example.jp
  zonefile: example.jp.zone
  notify: [Zone転送を許可するIPアドレス] NOKEY # Masterの場合
  provide-xfr: [Zone転送を許可するIPアドレス] NOKEY # Masterの場合
  allow-notify: [Zone転送を許可するIPアドレス] NOKEY # Slaveの場合
  request-xfr: AXFR [Zone転送を許可するIPアドレス] NOKEY # Slaveの場合
```

権威DNSサーバーは１つのMasterサーバーと複数のSlaveサーバーで構成されますが、権威DNSサーバーの動作に違いは無くドメインのZone転送構成によります。
なおドメインのZone転送はSlaveサーバー側に限定してZone情報が他に漏洩しないようにセキュリティを確保する必要があります。ここで設定ファイル内でそれを記述しています。
また外部のDNSキャッシュサーバーのフルリゾルバ側からの問い合わせに回答出来る必要がある為、DNSのTCP/53 UDP/53をセキュリティグループなどファイヤーウォールのフィルターで制限しないように。

Slaveサーバーの場合、設定ファイルのpatternテンプレートを用意することで、いちいちZoneファイルを用意しなくてもMasterサーバーからドメインのZoneデータを作成してくれます。
NSDのコンテナを起動した後で行いますので、後の方で記載しておきます。

作成した設定ファイルの動作確認する方法があります。

```
$ docker run -it --rm -v $PWD/conf:/etc/nsd selfhostingtools/nsd nsd-checkconf /etc/nsd/nsd.conf
```

エラー表示されなければ設定ファイルの記述内容は合っています。

## ドメインのZoneファイルの作成

Masterサーバーの場合は、予めドメインのZoneファイルを作成します。
記述方法はBINDと同じです。

```
$ mkdir -p zones
```

```:zones/example.jp.zone
$ORIGIN example.jp.
$TTL 86400

@ IN SOA ns1.example.jp. hostmaster.example.jp. (
                2021033101      ; serial
                28800           ; refresh
                7200            ; retry
                86400           ; expire
                86400           ; min TTL
                )

                NS              ns1.example.jp.
                NS              ns2.example.jp.

@               A               [IPアドレス]
www             A               [IPアドレス]
```

作成した設定ファイルの動作確認する方法があります。

```
$ docker run -it --rm -v $PWD/zones:/zones selfhostingtools/nsd nsd-checkzone example.jp /zones/example.jp.zone
```

エラー表示されなければ設定ファイルの記述内容は合っています。

## nsdアカウントの設定

NSDコンテナ環境ではアカウントnsdで動作させる必要があります。
ユーザーIDとグループIDを991でホストOS側にも用意します。

```
$ sudo groupadd -g 991 -r nsd
$ sudo useradd -g nsd -s /sbin/nologin -M -u 991 -r nsd
```

## ディレクトリとファイルのオーナー権限を変更

作成したnsdアカウントに、設定ファイルと使用ディレクトリのオーナー権限を変更します。

```
$ sudo chown nsd:nsd zones
$ sudo chown nsd:nsd conf
$ sudo chown nsd:nsd conf/nsd.conf
$ mkdir -p keys
$ sudo chown nsd:nsd keys
```

## リモートコントロール用鍵ファイルの作成

リモート操作のnsd-controlコマンドの使用に自己証明書の鍵ファイルが必要になりましたので専用コマンドで作成します。

```
$ docker run -it --rm -v $PWD/conf:/etc/nsd ghcr.io/the-kube-way/nsd nsd-control-setup
```

作成した自己証明書の鍵ファイルのオーナー権限をアカウントnsdに変更しておきます。

```
$ sudo chown nsd:nsd conf/*
```

## NSDのコンテナ起動

以上で必要な設定ファイルが用意出来たので、docker composeコマンドでNSDコンテナを起動させます。

```
$ docker compose up -d
```

## SlaveサーバーのZoneファイル作成

Slaveサーバーの場合、設定ファイルのpatternテンプレートを用意することで、MasterサーバーからZone転送させてZoneファイルを作成出来ます。

```:conf/nsd.conf
pattern:
  name: "slave"
  zonefile: "%s.zone"
  allow-notify: [Zone転送を許可するIPアドレス] NOKEY
  request-xfr: [Zone転送を許可するIPアドレス] NOKEY
```

```
$ docker exec -i -t nsd /bin/ash
# nsd-control reconfig
# nsd-control addzone [ドメイン名] slave
# nsd-control transfer [ドメイン名]
# nsd-control zonestatus
# nsd-control write
# exit
```

これでzonesディレクトリにMasterサーバーからZone転送されたドメインのZoneファイルが作成されています。
またnsd設定ファイルnsd.confにドメインのZone項目を忘れないように追記しておきます。これはnsdの再起動時にはZoneファイルからドメインのZoneデータが読み込まれる為です。

## バージョンアップ方法

バージョンアップする方法は、オリジナルのDocker Imageを更新する必要があります。
Dockerコンテナ内のパッケージを更新してもNSDは更新されません。
必要とするDocker ImageだけpullしてDocker Composeで再起動させます。

```
$ cd nsd
$ docker pull ghcr.io/the-kube-way/nsd:latest
$ docker compose up -d
```

## バージョンの確認方法

設定ファイルでバージョン表示を隠匿した場合はDNSの応答では表示されません。
Dockerコンテナからコマンドを実行しましょう。

```
$ docker exec nsd /usr/local/sbin/nsd -v
NSD version 4.5.0
Written by NLnet Labs.
```

# 動作確認

権威DNSサーバーの一般的な動作確認をdigコマンドでサーバーホストの内部と外部の両方から行います。
キャッシュ除外の為、digコマンドオプション+norecを付けます。

- 正引き：dig +norec @[localhost] example.jp A
- 逆引き：dig +norec @[localhost] -x [IPアドレス番号]
- バージョン表示：dig +norec @[localhost] chaos version.bind txt
- Zone転送：dig +norec @[localhost] axfr +multi

バージョンは非表示で、Zone転送が許可されたSlaveサーバーのIPアドレス番号限定を確認します。