第386回では、Unboundを使って家庭内DNSサーバーを構築しました。ですがこの記事から9年が経ち、Ubuntuのネットワークまわりも変化しました。当時とは状況も変わってきていますので、改めて最新のLTSである24.04を使い、Unboundの構築方法を紹介します。
家庭内DNSサーバーが必要な理由
DNSサーバには大きく分けて、ゾーン情報を管理するコンテンツサーバーと、名前解決を代行するキャッシュサーバーの二種類に分けられます。コンテンツサーバーは、そのドメインの名前情報を管理するサーバーで、インターネット全体に対してサービスを提供しなければなりません。そのため基本的に、ご家庭内には不要なサーバーです。
対してキャッシュサーバーは、どのご家庭にも存在します。通常、クライアントが直接、DNSのルートサーバーと通信することはありません。クライアントはキャッシュサーバーにリクエストを投げ、実際の名前解決はキャッシュサーバーがクライアントの代理として行います。今時のご家庭であれば、Wi-Fi機能を持ったルーターを設置して、インターネットに接続しているケースが多いでしょう。通常はこのルーターがキャッシュサーバーを兼ねており、OSのネットワーク設定では、使用するDNSサーバーとして、ルーターのIPアドレスが指定されているのが一般的です。
今回紹介するUnboundは、Ubuntu上に構築するDNSキャッシュサーバーです。つまり現在利用している、ルーターのキャッシュサーバー機能を置き換えられるものとなります。
「でも、ルーターの機能で間に合うなら、そんなのいらないのでは?」
そう考える人もいるでしょう。ですが自由にいじれるLinux上にサーバーを構築するのには、それなりに便利な理由というものがあるのです。
クエリログが確認できる
キャッシュサーバーは、クライアントに代わって名前を解決します。そしてリクエストされたDNSクエリとその結果は、クエリログというログに残ります。つまりクエリログを見れば、いつ、誰が、どのドメインにアクセスしたのかがわかるわけです。内部がブラックボックスなスマホアプリであっても、DNSクエリを見れば、どのサイトと通信しているかがわかります。DNSクエリによって、不審なドメインと通信しているアプリを発見した、などという例もあります。パケットキャプチャをしなくても、通信しているサイトがわかるというのは、意外と便利なものなのです。
もちろん、ルーターのキャッシュサーバーでも、クエリログを見られたり、場合によってはSyslog転送に対応している機種もあるでしょう。ですがフル機能のLinuxサーバーであれば、データの保全や加工、集計、監視といった面においてアドバンテージがあります。特に企業においては、クエリログを保全しておくと、セキュリティ的にも役立つ日が来るかもしれません。
普段使いのドメインを家庭内でも使える
コンテンツサーバーはご家庭内には不要と言いましたが、ご家庭内でも名前解決をしたくなることがあります。特に家庭内サーバーを運用していると、IPアドレスではなくドメイン名でアクセスしたいですよね。Unboundは、ローカルなゾーンを持つことができます。本来であれば正規のコンテンツサーバーへ名前解決しに行く所を、このローカルなゾーン情報を元に応答を返せます。つまりこのゾーンにプライベートIPアドレスを登録すれば、家庭内のPCやサーバーに、好きな名前をつけられるのです。
もちろん、単にIPアドレスのかわりに名前を使いたいだけであれば、DNSサーバーは必要ありません。UbuntuではAvahiがインストールされていますので、mDNSによってホスト名からIPアドレスを引くことができます。ですがこの場合、mDNSの仕様によって、ドメイン部分が「.local」となってしまいます。対してUnboundを使えば、インターネット上で使っているのと同じドメイン(example.comなど)を家庭内でも使うことができるのです。そしてこうしたドメインが使えるということは、正規のSSL証明書が使えるということでもあります。
またインターネット経由と家庭内とで、同じドメインを違うIPアドレスで運用したいような場合にも役立ちます。例えば家庭内でWebサービスを動かし、インターネットに公開しているとします。するとそのサービスのドメイン(例えばwww.example.comなど)には、ルーターに割り当てられたグローバルIPアドレスを、パブリックなDNSレコードとして登録しているはずです。ですが家庭内からそのサービスを利用する時は、プライベートIPアドレスを使って、直接接続したいですよね。Unboundに「www.example.com」のローカルデータを持たせ、プライベートIPアドレスを登録すれば、インターネット上と異なる名前解決結果を返すことができるわけです。
特定のドメインをDNSレベルでブロックできる
世の中には、接続したくないドメインというものが存在します。例えば犯罪にかかわるサイトであったり、マルウェアを配布しているサイトであったりなどです。Unboundは、前述のローカルなゾーンの機能を使い、指定されたドメインの名前解決要求を拒否したり、常にNXDOMAIN(そのドメインは存在しない)を返すことができます。クライアントはそのドメインの名前解決ができなくなるため、そのサイトには到達できなくなるというわけです。DNSレベルでブロックするため、クライアントにセキュリティソフトなどを別途インストールする必要もありませんし、PCやスマホからゲーム機まで、クライアントの種類も選びません。またファイアウォールで通信を遮断しているわけではないため、「回避しようと思えばできる」ゆるさも、筆者は気に入っています。
最近では「.zip」「.mov」のような、ファイル拡張子と同じトップレベルドメインが登場しました。これがセキュリティ的な脅威になるという話があります。例えばファイル名に見せかけてチャットサービスなどに文字を入力すると、それが勝手にハイパーリンク化され、クリックした人を有害なサイトに誘導する、といった攻撃もありうるでしょう。Unboundではトップレベルドメインごと、名前解決を無効にできます。実際に筆者の自宅では、.zipドメインはすべて、名前解決ができなくなっています。
Unboundのインストール
それでは実際に、Unboundサーバーを構築してみましょう。Unboundはunboundパッケージでインストールできます。
インストールすると、自動的にUnboundが起動します。
ですが待ち受けているアドレスが127.0.0.1のため、外部からの名前解決要求を受け取ることができません。家庭内の他のPCからも利用できるよう、設定を変えていきましょう。
Unboundの設定
Unboundの設定ファイルは/etc/unbound/unbound.confです。このファイルは、属性キーワードと、その後に続く値で構成されています。属性キーワードの後にはコロンが続き、キーワード間は半角スペースで区切る必要があります。
属性の後には、別の属性も指定できます。これを「句」と呼び、複数の属性をグループ化できます。
デフォルトのunbound.confは以下のように、include-toplevelが一行書かれているだけです。
実は以前のUnboundでは、ここは以下のように設定されていました。
includeはその名の通り、他のファイルから設定を読み込むために使われます。これにより、適度な粒度で設定ファイルを分割し、可読性やメンテナンス性を上げることができるわけです。include-toplevelはより構造的な設定を可能にするincludeオプションです。include-toplevelが指定されると、現在アクティブな句がすべて閉じられ、インクルードされたファイル内、もしくはinclude-toplevelの直後で、句の使用が強制されます。つまり設定ファイルは句単位の粒度で区切り、/etc/unbound/unbound.conf.d/*.confに分割して置くのがよいでしょう。
Unbound全体の設定ファイルとして、/etc/unbound/unbound.conf.d/unbound.confを以下の内容で作成しました。
interfaceはUnboundが待ち受けに利用するIPアドレスです。ここではサーバーに指定されているプライベートIPアドレスを指定しました。現在のUbuntuでは、systemd-resolvedがスタブリゾルバとして127.0.0.{53,54}:53を待ち受けているため、ここを「0.0.0.0」とすると、バインドに失敗するため注意してください。portは待ち受けるポート番号です。
access-controlは、文字通りアクセスのコントロールです。ここでは自分自身と、LAN内のIPアドレスのレンジからのアクセスを「allow(許可)」としています。
use-syslogはSyslogを使ってログを出力する設定です。
log-queriesはクエリログを出力する設定です。
hide-versionとhide-identityは、version.server、version.bind、id.server、hostname.bindクエリを拒否する設定です。
forward-zone句には、Unboundがクエリをフォワードする設定を記述します。nameにはフォワードするゾーンの名前を指定します。ここでは「.」を指定していますが、これはすべての問い合わせを意味します。forward-addrには、このゾーンのフォワード先を指定します。ここではプロバイダが提供しているDNSサーバーのアドレスを設定しました。これで自分自身が(ローカルなデータやキャッシュで)応答できないクエリに関しては、すべてプロバイダのDNSサーバーに、再帰的に問い合わせます。
設定が完了したら、Unboundを再起動してください。これで、通常のキャッシュサーバーとして動作するようになりました。
ログの設定
上記の設定でuse-syslogを有効にしました。Unboundがsyslogに出力するログファシリティはdaemonです。ですがUbuntuのデフォルト設定では、daemon.*ログは個別のログファイルに書かれず/var/log/syslogにだけ書かれてしまいます。そこでログをUnbound専用のファイルに書き出すよう、設定を変更します。「/etc/rsyslog/30-unbound.conf」というファイルを、以下の内容で作成します。
これはログファシリティが「daemon」で、プログラム名に「unbound」を含むログを、「/var/log/unbound/unbound.log」に出力する設定です。最後の行にある「&」は直前の条件式を、「~」はログの破棄を意味します。つまりここでUnboundのログは破棄され、後続の条件にはマッチしなくなります。この行を指定しないと、unbound.logとsyslogの両方に、ログが重複して書かれてしまうためです。設定ができたら、rsyslogを再起動します。
実際にクライアントが名前解決のリクエストを投げると、「/var/log/unbound/unbound.log」には以下のようにログが出力されます。
クエリログは出力され続けますから、ディスクが枯渇しないよう、ログのローテートも設定しておきましょう。「/etc/logrotate.d/unbound」というファイルを、以下の内容で作成します。
ここでは日次ローテートで、30世代分のログを保存する設定としました。このあたりは環境にあわせて変更してください。またログファイルが大きくなりがちな環境では、ログファイルの圧縮も行ったほうがいいかもしれません。
ローカルゾーンの設定
家庭内サーバーの名前解決ができるよう、ローカルゾーンを設定しましょう。ここでは例として、「example.com」というドメインをインターネット上で運用しており、そのサブドメインとして「host1」と「host2」というサーバーを、家庭内に立てた想定です。
まずローカルゾーンを定義する設定ファイルとして、「/etc/unbound/unbound.conf.d/local-zone.conf」を作成します。内容は以下の通りです。
server句の中に、local-zoneとして「example.com」を定義しました。またこのゾーンのタイプとして「transparent」を指定しています。これはUnboundがデータを持っていなかった場合の挙動の指定です。具体的に言うと、「transparent」を指定した場合、まずローカルデータに対してマッチングを行い、データが存在しなかった場合は、フォワード先のDNSサーバーに対して通常の名前解決を依頼します。ここの例で言えば、「host1.example.com」の名前解決要求に対しては、ローカルデータとして定義されている「192.168.1.20」を返しますが、仮に「host3.example.com」という名前解決が要求された場合は、ローカルデータが存在しないため、フォワード先に名前解決を依頼するというわけです。インターネット上で運用しているドメインに対し、ローカルなデータを追加したい場合は、このタイプを選択してください。
local-dataは文字通り、ローカルなデータの設定です。ここではhost1とhost2というサーバーのプライベートIPアドレスを設定しています。
ローカルゾーンの設定ができたら、Unboundを再起動してください。local-dataに設定した名前解決ができることと、(インターネット上から名前解決できる)「example.com」の別のレコードの名前解決に影響がないことを確認しておきましょう。
特定ドメインのブロック
先ほどはexample.comのゾーンに対し、transparentというタイプを設定しました。ゾーンには他にもいくつかのタイプが用意されており、ゾーンをブロックする際に利用できるのが「refuse」です。例えば先ほどの例のように、.zipドメインすべてをブロックするとしましょう。「/etc/unbound/unbound.conf.d/blacklist.conf」というファイルを、以下の内容で作成してください。
refuseは、そのゾーンの名前解決要求に対して「REFUSED(リクエストの拒否)」を返します。ただしrefuseに指定されたゾーンであっても、Unboundがローカルデータを持っていた場合は、そのレコードを応答します。
これに対して「always_nxdomain」というタイプは、ローカルデータがあったとしてもすべて無視し、名前解決要求に対して常に「NXDOMAIN(ドメインが存在しない)」を返します。例えば以下の設定では、「example.net」に対する名前解決要求は、すべてNXDOMAINとなります。絶対にブロックしたいドメインは、「always_nxdomain」を指定してしまうとよいでしょう。
このように、DNSサーバーを自前で構築することで、ご家庭のネットワークをちょっと便利に改善できるかもしれません。DNSサーバーはそれほどパワーが必要なサーバーではありませんから、ご家庭にいくつか余っているRaspberry Piなどで運用してみてはいかがでしょうか。