Dockerで権威DNSサーバーのNSDを動かす

# 前書き
この2017年に投稿した内容では古くてそのままでは動作しなくなっているので2021年版を別に投稿しました。

https://qiita.com/ohhara_shiojiri/private/497aaba989151fa84b3d

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

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

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

# スレーブサーバー
権威DNSサーバーはマスターとスレーブの2つ以上で構成されます。
マスターはゾーン編集をフロントエンドに出しやすいPowerDNSにして、NSDはスレーブとして運用するのが良いかと思います。
DNSでは多様性も言われてますので、マスターとスレーブで異なる環境と実装を組み合わせましょう。

マスタ・プライマリ側でPowerDNSの場合は以下の投稿で紹介しています。

[Dockerで権威DNSサーバーのPowerDNSを動かす](http://qiita.com/ohhara_shiojiri/items/1705f0045b8d860f771d)

# Docker環境
ホストOSには、CentOS 7を使用、Docker CEとDocker-composeでコンテナを作成します。
インストール方法は公式ガイドを参照下さい。一例も記録しておきます。

Docker CE インストール
https://docs.docker.com/engine/installation/linux/centos/

````
$ sudo yum install -y yum-utils
$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
$ sudo yum-config-manager --enable docker-ce-edge
$ sudo yum makecache fast
$ sudo yum install -y docker-ce
$ sudo systemctl start docker
$ sudo systemctl enable docker
$ sudo docker run hello-world
````

Docker Compose インストール
https://docs.docker.com/compose/install/

````
$ sudo -i
# curl -L https://github.com/docker/compose/releases/download/1.25.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
# chmod +x /usr/local/bin/docker-compose
# exit
$ docker-compose --version
````

# Docker Image
Docker Hubを検索するとNSDのDocker Imageもありますので利用しましょう。
ただし中にはバージョンの古いものもあるので、NSD4の最新バージョンに追随しているDocker Imageを選びました。

https://hub.docker.com/r/hardware/nsd-dnssec/
https://github.com/hardware/nsd-dnssec

# NSDコンテナの設定
適当なディレクトリにDocker-composeファイルを作成します。

````
$ mkdir nsd
$ cd nsd
$ vi docker-compose.yml
````

```yml:docker-compose.yml
version: "3"
services:
  nsd:
    image: hardware/nsd-dnssec:latest
    container_name: nsd
    ports:
      - "5353:53"
      - "5353:53/udp"
    volumes:
      - ./conf:/etc/nsd
      - ./zones:/zones
      - ./db:/var/db/nsd
```

投稿時version:"2"でしたがversion:"3"に変更確認
ボリュームの場所はホスト側の適当な所にします。
設定ファイルをコンテナの外側に配置して可用性を図ります。
これでコンテナを入れ替えても設定ファイルやゾーンのデータベースファイルが再利用出来ます。

````
$ mkdir conf
$ cd conf
$ vi nsd.conf
````

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

remote-control:
  control-enable: yes

zone:
  name: domain.tld
  zonefile: db.domain.tld.signed
  allow-notify: 192.168.254.254 sec_key
  request-xfr: AXFR 192.168.254.254 sec_key
```

zoneの項目で各ドメインを設定します。
nameでドメイン名、zonefileでゾーンファイルが指定出来ます。
例ではIPアドレスの所はマスター側のIPアドレスになります。
ただ管理するゾーンが追加される度に設定ファイルを修正する必要があるので、以下の様にパターン形式に変更しましょう。

```conf:nsd.conf
pattern:
  name: "slave"
  zonefile: "%s.zone"
  allow-notify: 192.168.254.254 NOKEY
  request-xfr: 192.168.254.254 NOKEY
```

zone形式からpattern形式にするとドメイン毎のゾーンを記述する必要はありません。

#NSDコンテナの起動と操作
それでは、Docker-composeでNSDのコンテナを起動させてみます。

````
$ cd ..
$ docker-compose up -d
````

無事にDockerコンテナが起動していれば、以下のような状態になります。
NSDのコンテナnsdは外部に対してTCP/UDP:5353でLISTENしています。
ローカルDNSとの競合を避けるためですが、docker-compose.ymlファイルを編集する事でポート番号は変更出来ます。
このままポート番号を変更せずにローカルDNSと共存させてポートフォワードで運用する方法もあります。

```
$ docker-compose ps
Name   Command   State                     Ports                   
-------------------------------------------------------------------
nsd    run.sh    Up      0.0.0.0:5353->53/tcp, 0.0.0.0:5353->53/udp
```

ユーザーとグループのIDが991なので環境に応じで設定変更しておきましょう。

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

NSDコンテナに入ってゾーンを登録してみましょう。

````
$ docker exec -i -t nsd /bin/sh
# nsd-control reconfig
# nsd-control addzone domain.tld slave
# nsd-control zonestatus
# nsd-control write
# exit
````

nsd-controlで設定ファイルの再読込やゾーンの登録が出来ます。
詳しくは公式ガイドを参照して下さい。
https://www.nlnetlabs.nl/projects/nsd/nsd-control.8.html
あと、writeする時にディレクトリ属性でファイル出力出来なかったので適切に設定しておきましょう。

ゾーンの追加自体を自動化する例が以下にあります。参考にしてください。
[PowerDNS + NSD で DNS を構築する](http://blog.akagi.jp/archives/4072.html)

DNSのツールをインストールして動作確認してみましょう。

````
$ sudo yum -y install bind-utils
$ dig @localhost chaos version.bind txt
````

DNSはTCP/53 UDP/53を使用しますので、ファイヤーウォールを適切に設定しましょう。
ただし権威DNSサーバーは外部公開する必要がある為、フルリゾルバーの様に制限しないように。
また、ubuntuなどでローカルのdnsmasqとかに競合して起動しない場合もあります。


# ローカルDNSの停止(Ubuntu 16.04)
ubuntu 16.04 ではローカルDNSのdnsmasqが動作しています。
当然にTCP/UDP:53番ポートをLISTENするのでポート番号が競合する為に事前に停止します。

```
$ sudo lsof -i:53
COMMAND  PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
dnsmasq 1107 nobody    4u  IPv4  20801      0t0  UDP localhost:domain 
dnsmasq 1107 nobody    5u  IPv4  20802      0t0  TCP localhost:domain (LISTEN)
```

DNS設定をコメントアウト

```:/etc/NetworkManager/NetworkManager.conf
#dns=dnsmasq
```

NetworkManagerを再起動

```
$ sudo service network-manager restart
```

このままだとDNS名前解決できないので、DNSリゾルバを設定します。

```:/etc/resolv.conf
#nameserver 127.0.1.1
nameserver 8.8.8.8
```

NetworkManager経由のサブデーモンとしてdnsmasqが起動している為か、
ホスト自体を再起動しないとdnsmasqが無効になりませんでした。

# ローカルDNSの停止(Ubuntu 18.04)
ubuntu 18.04ではローカルDNSのsystemd-resolveが動作しています。
当然にTCP/UDP:53番ポートをLISTENするのでポート番号が競合する為に事前に停止します。

```
$ sudo lsof -i:53
COMMAND   PID            USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
systemd-r 685 systemd-resolve   12u  IPv4  18787      0t0  UDP localhost:domain 
systemd-r 685 systemd-resolve   13u  IPv4  18788      0t0  TCP localhost:domain (LISTEN)
```

```:/etc/systemd/resolved.conf
DNSStubListener=no
```

```
$ sudo systemctl restart systemd-resolved
$ sudo lsof -i:53
sudo: unable to resolve host vuls: Resource temporarily unavailable
```

このままだとDNS名前解決できないので、DNSリゾルバを設定します。

```:/etc/resolv.conf
#nameserver 127.0.0.53
nameserver 8.8.8.8
```

しかしsystemd-resolvedにより
/etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf
とシンボリックリンクされていて元に戻ってしまいます。
やはりsystemd-resolvedを停止させます。

```
$ sudo systemctl stop systemd-resolved
$ sudo systemctl disable systemd-resolved
```

シンボリックリンクを消して設定ファイルを作成します。

```
$ sudo rm /etc/resolv.conf
```

```:/etc/resolv.conf
nameserver 8.8.8.8
```

# NSDバージョンアップ

投稿後にNSD 4.2.1がリリースされたのでコンテナの更新方法を記録しておきます。

通常、docker-compose upコマンドでイメージの更新があればコンテナは再作成されます。
しかしローカルには取得時のイメージをキャッシュして使用する為、最新のイメージをDocker Hubから再取得する必要があります。

新しいイメージをDocker Hubから再取得します。

```
$ docker pull hardware/nsd-dnssec:latest
```

ローカルのイメージを再確認します。

````
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
hardware/nsd-dnssec   latest              b0034ab6cdde        2 months ago        12.2MB
hardware/nsd-dnssec   <none>              1f4023f05f4f        3 months ago        9.03MB
````

作成日時の新しいイメージがローカルに保存されました。
不要ならば消してもいいですが何かあった場合に以前のイメージに戻せます。
以前のイメージのタグからlatestが外れたので、バージョンを指定しておきます。

````
$ docker tag 1f4023f05f4f hardware/nsd-dnssec:4.2.1
````

ローカルのイメージを再確認します。

````
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
hardware/nsd-dnssec   latest              b0034ab6cdde        2 months ago        12.2MB
hardware/nsd-dnssec   4.2.1               1f4023f05f4f        3 months ago        9.03MB
````

これでコンテナのイメージ更新準備が出来ました。
コンテナの再作成をしましょう。

````
$ docker-compose up -d
Recreating nsd
````

コンテナの中に入ってNSDのバージョンを確認してみます。

````
$ docker exec -i -t nsd /bin/sh
# nsd-control status
version: 4.2.1
````

NSDのバージョンアップが出来ました。
また、ゾーンの設定が継承されている事を動作確認しておきましょう。