Jun 23

DoH(DNS over HTTPS)を某個人webサーバ(というかこのblogをホストしてるサーバだけど)で使えるようにしてbrowserにも設定してみた。いろんな恥ずかしいサイトも心置きなく閲覧できるよう一応この業界で働いている身としてはやはりちゃんとdogfoodしないと、ということで以前からやろうと思いつつ先延ばしになっていたのだが、ようやく重い腰を上げて設定。やってみたら意外と簡単だった。

現状は、実験も兼ねてjinmei.org以下の名前へのクエリにはクライアント認証なしで応答するようになっている。たとえばDoH Proxyを以下のようにインストールして、

% pip3 install doh-proxy

付属のクライアントを使うと動作確認できる:

% doh-client --qname ns1.jinmei.org --qtype AAAA --domain www.jinmei.org
[...]
;ANSWER
ns1.jinmei.org. 283 IN AAAA 2604:a880:2:d0::569:a001

(ただし、筆者が試したところでは、このblog執筆時点でPython 3.7だと動作しない模様。3.5か3.6が無難である)

このクライアントは認証なしでクエリを出すので、jinmei.org以外の名前の問い合わせには403が返る:

% doh-client --qname irs.gov --qtype AAAA --domain www.jinmei.org
[...]
2019-06-23 16:54:22,298:    DEBUG: Response headers: [(':status', '403'), ('server', 'nginx/1.14.2'), ('date', 'Sun, 23 Jun 2019 23:54:22 GMT'), ('content-type', 'application/octet-stream'), ('content-length', '24')]

以下は備忘録的な設定メモ。

サーバの設定

サーバ側は以下の構成になっている:

  • Frontend: NGINX 1.14.2。通常のwebサービスと共用し、DoH用にはreverse proxyとして動作
  • HTTPS-DNS proxy: DoH Proxy。ただしpipでインストールできるバージョン(執筆時点で0.8)だと若干動作が不安定なのでgithubの最新版を利用。さらにTLS認証情報利用などのために若干手を入れている。
  • Backend: unbound 1.5.10。Backendはrecursive DNSサーバとして使えるものならなんでもいいはずだが、たまたまlocalhost用にunboundをインストールしていたのでそれを流用した。

NGINXの設定例は概ねDoH Proxyのgithubページの通り。ただしこのままだとopen resolverになってしまう(とはいえ、TCP限定なのでsource address spoofingによる踏み台としては悪用されにくいだろうが)ので、クライアント認証のための設定を追加している。それを含めた設定の主要部分は以下の通り:

http {
    # CA for client certificates
    ssl_client_certificate /usr/local/etc/nginx/client_certs/ca.crt;
    ssl_verify_client optional;

    upstream dohproxy_backend {
        server [::1]:8080;
    }

    server {
        listen [::]:443 ssl http2;
        listen 443 ssl http2;
        server_name www.jinmei.org;
[...]
	# DNS over HTTPS
	location /dns-query {
            if ($ssl_client_verify != SUCCESS) {
                return 403;
            }
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_redirect off;
            proxy_buffering off;
            proxy_pass http://dohproxy_backend;
	}
[...一般のwebサーバとしての設定が続く]

以下設定に関する補足:

  • upstreamにはDoH Proxyのlisten address, portを指定する
  • ssl_client_certificateca.crtはクライアントの証明書を検証するためのCA証明書。たとえばこのtutorialに作成手順が載っている。
  • 不特定多数向けの一般webサービス用にはクライアント認証は要求できないので、ssl_verify_clientにはoptionalを指定し、/dns-queryのlocation内でのみ検証失敗時に403を返すようにしている。ただし、現時点で動いているサーバでは検証の結果とクエリ名で動作を変えるためにこの部分は無効化しており、backendのDoH proxyに$ssl_client_verifyの値を渡している(が、事故を起こしやすそうなので一般的にはそういう設定にはしない方がいいだろう。筆者も実験フェーズ後にはこの段階での認証を必須とする設定に戻す予定である)。

DoH Proxyの設定(というか起動時のオプション)には特別なことはない。--listen-address--portをNGINXのupstream設定に合わせて指定し、--upstream-resolverにbackend DNSサーバの待ち受けアドレス(通常::1)を指定すればよい。

BackendのunboundにはDoH特有の設定は必要ない。

クライアント(browser)の設定

このblog執筆時点では、おそらく実用上まともに使えるDoHクライアントはデスクトップ版のFirefoxくらいであろう(某githubページによればマイナー系のクライアントは他にもいくつかある模様)。

クライアント認証を別にすれば、Firefoxでの設定は簡単である(以下はMacのデスクトップ版の場合だが、他でも同様と思われる)。Preferencesの”General”にある”Network Settings”でEnable DNS over HTTPSをチェックし、さらにCustomをチェックしてサーバのURLを入れればよい。筆者のサーバの場合では、このURLはhttps://www.jinmei.org/dns-queryになる。

クライアント認証のための設定は若干面倒だが、本質的に難しいことはない。まずbrowserにimportする証明書を用意する。この手順はたとえば上でも参照したtutorialに記載されている。大雑把なsummaryとしては以下の通り:

  • RSA鍵生成: openssl genrsa
  • 署名リクエスト作成: openssl req
  • サーバ側で署名: openssl x509
  • 署名された証明書をexport: openssl pkcs12 -export

最後に、この結果としてできたxxx.pfxファイルをFirefoxにimportする。これには、PreferencesのPrivacy & Securityの一番下、”Certificates”で”View Certificates…”をクリックし、”Your Certificates”タブで”Import…”でexportしたばかりのpfxファイルを選べば完了。

動作確認

FirefoxはDoHでの名前解決に失敗するとtraditionalなDNS(Do53)に自動でフォールバックしてしまうので、正しく動作しているかの確認は少々面倒である。なお、DoHサーバ自体の名前解決のためにDo53が必要なので、Do53を完全に切ってしまうわけにはいかない。

パケットダンプを取得して眺めてみれば、ブラウザであちこちのサイトに行ってみてもport 53へのパケットがほとんど出ず、一方でDoHサーバへのport 443へのパケットが多数見られることから間接的には確認できる。

また、DoH Proxyはデフォルトで全クエリをlogに出すので、browserの挙動とproxyのlogを突き合わせてみてもある程度は動作確認になる。

2019-06-23 19:02:21,389:     INFO: [HTTPS] ::1 (Original IP: 192.0.2.42) connect.garmin.com.cdn.garmingtm.com. A IN 0 RD
2019-06-23 19:02:21,390:     INFO: [DNS] 192.0.2.42 connect.garmin.com.cdn.garmingtm.com. A IN 60604 RD
2019-06-23 19:02:21,396:     INFO: [DNS] 192.0.2.42 connect.garmin.com.cdn.garmingtm.com. A IN 60604 QR/RD/RA 3/0/0 0/0/4096 NOERROR 4ms

今後

これからしばらくはDoHで生活してみようと思う。大抵1-2ホップ先にいる共有のキャッシュサーバを使う場合と比べると、明らかにクエリ発生時の遅延は増すはずだが、これまでに少し使った限りでは体感的に明らかに遅いというような感じはなかった(Web browserが唯一のアプリケーションなのでページにrenderingなどの方が律速していて目立たないという可能性もあるだろうし、Firefox自身のキャッシュでクエリ自体が抑えられている場合もあると思われる)。それでも使っているうちに最初には気が付かなった問題に遭遇することもあるだろうから、そういう事例から何かの学びがあることを期待したい。

コメントを投稿 / Submit Comments



(あれば / Optional):