本記事はDocker+DNS入門 その1:CoreDNSを用いた権威DNSサーバ構築の続きの記事になります.
今回はローカルのdocker環境にUnboundによるキャッシュDNSサーバを構築し,前回の記事で構築した2つの権威DNSサーバへの反復問い合わせを実現します.
対象者
- DNSの基礎的な知識はあるが,実装したことがない方
実行環境
- Windows10
- WSL2 (Ubuntu 22.04)
- Docker v2.21.0(注: Docker desktop ではありません)
- Unbound 1.13.1
- CoreDNS 1.11.1
システムの全体像
Docker環境に3つのDNSサーバを構築します.
2つの権威DNSサーバがそれぞれ管理するドメインに対して,自前のキャッシュDNSサーバが名前解決を行います.
権威DNSサーバ1はexample.org
のドメインを管理し,そのサブドメイン"home".example.org
の管理を権威DNSサーバ2が委譲します.
前回の記事 Docker+DNS入門 その1:CoreDNSを用いた権威DNSサーバ構築でCoreDNSを用いて権威DNSサーバを構築しました.
今回はキャッシュDNSサーバを構築します.
用意するファイル
本記事では前回の記事で用意したファイルに,full_resolver
配下を追加します.
$ tree -a dns_test/
dns_test/
├── compose.yml
├── .env
├── example_auth
│ ├── Corefile
│ └── zone
│ └── db.example.org
├── home_example_auth
│ ├── Corefile
│ └── zone
│ └── db.home.example.org
└── full_resolver
├── Dockerfile
└── server.conf
Unboundの設定
-
Dockerfileを作成
./full_resolver/DockerfileFROM ubuntu:22.04 # Install unbound RUN set -x \ && apt-get update && apt-get install -y --no-install-recommends \ unbound=1.13.1-1ubuntu5.3 \ curl \ ca-certificates \ && apt-get clean \ && rm -r /var/lib/apt/lists/* RUN curl -o /etc/unbound/root.hints -LO https://www.internic.net/domain/named.cache RUN chown -R unbound:unbound /etc/unbound # unbound-anchor: DNSSEC検証のためのルートトラストアンカーの設置と更新 RUN unbound-anchor -4 -a "/var/lib/unbound/root.key" ; ls -al /var/lib/unbound/root.key # unbound-checkconf: unboundの設定のエラーを検査 RUN unbound-checkconf
-
set -x
によってその後に実行したコマンド自体を標準出力します.- これによって,
&&
で複数コマンドをまとめて実行したときの状況が見やすくなります.
- これによって,
-
RUN curl ~
によってルートヒント (ルートDNSサーバのIPアドレスが記載されているファイル) をダウンロードし,docker内に配置します. -
RUN chown -R unbound:unbound /etc/unbound
によって,所有者をunboundに変更し,ルートヒントを読み込めるようにします. -
unbound-anchor
によってDNSSEC検証に必要なルートトラストアンカー (root.key) を取得します.- 本記事ではDNSSECを利用しないため,機能的には不要ですが,
unbound-checkconf
でroot.keyがないと怒られるので取得しておきます.(DNSSECは次回の記事で実装します.) - オプション
-
-4
: トラストアンカーを提供するサーバにアクセスする際に,ipv4を利用します.dockerはデフォルトだとipv6通信ができないため. -
-a "/var/lib/unbound/root.key"
: root.keyを生成するパスを指定します.
-
- 本記事ではDNSSECを利用しないため,機能的には不要ですが,
-
unbound-checkconf
によって,unboundの設定に誤りがないか検査します.
unbound-anchorの注意点
ただし,このままでは
unbound-anchor
によってroot.keyが生成されているにも関わらず,Dockerfile上ではエラー扱いになります.(Unbound 1.16.2)
そこで,unbound-anchor
の後に; ls -al /var/lib/unbound/root.key
を加えます.
これにより,unbound-anchor
の実行結果にかかわらずlsコマンドを呼び出し,root.key
が存在すれば正常とみなして,Dockerfileをエラー終了させないようにします. -
-
unboundの設定ファイル (server.conf) を作成
- 設定ファイルの書き方はunbound.conf(5) – 日本Unboundユーザー会が参考になります.
./full_resolver/server.confserver: verbosity: 4 do-daemonize: no do-ip6: no do-ip4: yes root-hints: root.hints # module-config: "validator" or "iterator" module-config: "iterator" # trust-anchor auto-trust-anchor-file: "/var/lib/unbound/root.key" # access-control access-control: 0.0.0.0/0 refuse access-control: 127.0.0.0/8 allow_snoop access-control: 172.19.0.0/16 allow_snoop # interface interface: 0.0.0.0 # stub-zone stub-zone: name: "example.org" stub-addr: 172.19.0.21 stub-zone: name: "0.19.172.in-addr.arpa." stub-addr: 172.19.0.21
-
root.hints:
でDockerfileでダウンロードしたルートヒントを指定します. -
module-config:
で"iterator"を指定することで,DNSSECを無効にします. -
auto-trust-anchor-file:
でDockerfileでダウンロードしたトラストアンカーを指定します. -
access-contorl:
でこのサーバにアクセス可能なクライアントのIPアドレスの範囲(ここではプライベートIP)を指定します. -
interface:
でunboundがリクエストを受け付ける(リッスンする)インターフェースのIPアドレスを指定します.- デフォルトではlocalhost (172.0.0.1) ですが,外部と通信するので,任意のインターフェース (
0.0.0.0
) で受け付けます.
- デフォルトではlocalhost (172.0.0.1) ですが,外部と通信するので,任意のインターフェース (
-
stub-zone:
によって,あるゾーンに対応する権威DNSサーバを設定します.- ゾーン名を
name:
,その権威DNSサーバのIPアドレスをstub-addr:
で指定します.
- ゾーン名を
dockerの設定
- compose.ymlを作成
./compose.yml
services: example_auth: image: coredns/coredns:1.11.1 container_name: example_auth restart: on-failure volumes: - './example_auth:/etc/coredns' networks: dns_test: ipv4_address: $ipv4_example_auth command: -conf /etc/coredns/Corefile -dns.port $dns_port home_example_auth: image: coredns/coredns:1.11.1 container_name: home_example_auth restart: on-failure volumes: - './home_example_auth:/etc/coredns' networks: dns_test: ipv4_address: $ipv4_home_example_auth command: -conf /etc/coredns/Corefile -dns.port $dns_port full_resolver: container_name: full_resolver build: context: './full_resolver' dockerfile: Dockerfile restart: on-failure volumes: - '/etc/unbound' - './full_resolver:/etc/unbound/unbound.conf.d' networks: dns_test: ipv4_address: $ipv4_full_resolver command: /usr/sbin/unbound networks: dns_test: external: true
- compose.yml内の変数は
./.env
に記述./.envdns_port=53 ipv4_full_resolver=172.19.0.11 ipv4_example_auth=172.19.0.21 ipv4_home_example_auth=172.19.0.31
動作確認
権威DNSサーバの起動
compose.yml
があるディレクトリでdocker compose up
を実行します.
~/dns_test$ docker compose up
[+] Running 3/0
✔ Container home_example_auth Cre... 0.0s
✔ Container full_resolver Created 0.0s
✔ Container example_auth Created 0.0s
Attaching to example_auth, full_resolver, home_example_auth
full_resolver | [1704280672] unbound[1:0] debug: creating udp4 socket 0.0.0.0 53
full_resolver | [1704280672] unbound[1:0] debug: creating tcp4 socket 0.0.0.0 53
full_resolver | [1704280672] unbound[1:0] debug: creating tcp4 socket 127.0.0.1 8953
full_resolver | [1704280672] unbound[1:0] debug: setup SSL certificates
full_resolver | [1704280672] unbound[1:0] debug: switching log to syslog
example_auth | example.org.:53
example_auth | [INFO] plugin/reload: Running configuration SHA512 = ~~~
example_auth | CoreDNS-1.11.1
example_auth | linux/amd64, go1.20.7, ae2bbc2
home_example_auth | home.example.org.:53
home_example_auth | [INFO] plugin/reload: Running configuration SHA512 = ~~~
home_example_auth | CoreDNS-1.11.1
home_example_auth | linux/amd64, go1.20.7, ae2bbc2
キャッシュDNSサーバによる名前解決
- 構築したキャッシュDNSサーバに対して,WSL2から再帰問い合わせを行います.
- まず権威DNSサーバ1 (example.orgサーバ) の内容を問い合わせ,名前解決できていることを確認します.
権威DNSサーバの内容は前回の記事と同じです.Ubuntu-22.04 (WSL2)$ dig +nocookie @172.19.0.11 ns1.example.org ; <<>> DiG 9.18.18-0ubuntu0.22.04.1-Ubuntu <<>> +nocookie @172.19.0.11 ns1.example.org ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 34321 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1232 ;; QUESTION SECTION: ;ns1.example.org. IN A ;; ANSWER SECTION: ns1.example.org. 86400 IN A 172.19.0.21 ;; Query time: 0 msec ;; SERVER: 172.19.0.11#53(172.19.0.11) (UDP) ;; WHEN: ~~~ ;; MSG SIZE rcvd: 60
- 同様に権威DNSサーバ2 (home.example.orgサーバ) の内容を問い合わせ,名前解決できていることを確認します.
Ubuntu-22.04 (WSL2)
$ dig +nocookie @172.19.0.11 www.home.example.org ; <<>> DiG 9.18.18-0ubuntu0.22.04.1-Ubuntu <<>> +nocookie @172.19.0.11 www.home.example.org ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18139 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1232 ;; QUESTION SECTION: ;www.home.example.org. IN A ;; ANSWER SECTION: www.home.example.org. 86400 IN A 172.19.0.33 ;; Query time: 0 msec ;; SERVER: 172.19.0.11#53(172.19.0.11) (UDP) ;; WHEN: ~~~ ;; MSG SIZE rcvd: 65
Unboundの設定(server.conf)で,example.orgドメインのstub-zoneを権威DNSサーバ1としているため,www.home.example.org
の問い合わせはまず権威DNSサーバ1へ送られます. そのあと,権威DNSサーバ1から権威DNSサーバ2への委譲され,名前解決されています.
次回の話
これまでの権威DNSサーバ,キャッシュDNSサーバの構築では,簡単のためDNSSEC機能を無効化していました.
しかし,実際にはDNSSEC機能を無効にすると,キャッシュDNSサーバの問い合わせに対して,偽装された回答が送られてもそれを受け入れてしまいます (DNSキャッシュポイズニング).
次回の記事では,これまで構築してきたDNSサーバにDNSSEC機能を実装します.