NGINXをDoTまたはDoHゲートウェイとして使用する

NGINX | 2022年2月22日

現在、Domain Name System(DNS)をめぐって多くの議論が交わされており、36年の歴史を持つこのプロトコルに大幅な変更が提案されています。インターネットの名前解決サービスは、ARPANETに端を発し、誕生以来、後方互換性を損なう変更は一度もありませんでした。しかし、DNSのトランスポートメカニズムを変更する新たな提案によって、その状況が変わろうとしている可能性があります。

本記事では、DNSを保護するための2つの新しいテクノロジー、DNS over TLS(DoT)とDNS over HTTPS(DoH)を取り上げ、NGINX Open SourceとNGINX Plusを使用して実装する方法を紹介します。

[編集者注 – この記事は、NGINX JavaScriptモジュールのユースケースを紹介する一連の記事の1つです。完全な一覧については、「NGINX JavaScriptモジュールのユースケース」をご覧ください。

この記事のコードは、 js_import ディレクティブを使用するように更新されています。このディレクティブは、 js_include ディレクティブに代わるもので、NGINX Plus R23<.htmla>以降で使用できます。詳細については、NGINX JavaScript moduleのリファレンスドキュメントを参照してください。Example Configurationセクションでは、NGINX設定ファイルとJavaScriptファイルの正しい構文を示しています。]

DNSの歴史

DNSは、Advanced Research Project Agency(ARPA)の初期インターネット向けネーミングサービスを構築する2度目の試みでした。最初の試みは、John Postelが1979年に公開したInternet Name Serverプロトコル「IEN-116」です。DNSは階層構造を採用し、ホスト名をゾーン単位で分散して、複数の独立した機関が管理できるように設計されました。DNSに関する最初のRFCは1983年に公開され(RFC 882および883)、その後何度か拡張されてきましたが、当時策定された標準に準拠して作成されたクライアントは、現在でも動作します。

では、なぜ今になってプロトコルを変更するのでしょうか。DNSは明らかに意図どおりに機能しており、設計者たちの自信が正しかったことを裏付けています。設計者たちはその完成度に絶対の自信を持ち、DNSパケットにバージョン番号すら含めませんでした。このようなことを誇れるプロトコルは、ほかにほとんど思い当たりません。DNSが考案されたのは、ほとんどのプロトコルが平文で、多くの場合7ビットASCIIを使用していた、今よりも無防備でいられた時代です。しかし、今日のインターネットは1980年代のARPANETよりもはるかに危険な場所になっています。現在では、ほとんどのプロトコルが暗号化と検証のためにTransport Layer Security(TLS)を採用しています。DNSを批判する人々は、追加のセキュリティ保護を導入すべき時期をとうに過ぎていると主張しています。

このように、DNSがインターネット向けの名前サービスプロトコルを提供する2度目の試みであったのと同様に、DNS over TLS(DoT)とDNS over HTTPS(DoH)は、DNSプロトコルを保護する2度目の試みとして登場しつつあります。最初の試みは、DNSSECと呼ばれる拡張機能でした。現在、ほとんどのトップレベルドメイン(TLD)でDNSSECが使用されていますが、DNSパケットで伝送されるデータの暗号化を目的としたものではなく、データが改ざんされていないことを検証する機能のみを提供します。DoTとDoHは、DNSをTLSトンネル内にカプセル化するプロトコル拡張機能であり、採用されれば、36年間にわたって維持されてきた後方互換性に終止符を打つことになります。

DoTとDoHの詳細

DoTは、概ね妥当な拡張と見なされていると思います。Internet Assigned Numbers Authority(IANA)によって専用のポート番号(TCP/853)がすでに割り当てられており、TCP DNSパケットをTLSで暗号化されたトンネル内にカプセル化するだけです。多くのプロトコルで、これと同様の仕組みが以前から採用されています。HTTPSはTLSトンネル内でHTTPを使用し、SMTPS、IMAPS、LDAPSはそれぞれのプロトコルをセキュアにしたものです。DNSは従来からトランスポートプロトコルとしてUDP(場合によってはTCP)を使用しているため、TLSによるカプセル化を追加しても大幅な変更にはなりません。

一方、DoHについては多少意見が分かれています。DoHはDNSパケットをHTTPの「GET」または「POST」リクエスト内にカプセル化し、HTTPS接続を介してHTTP/2以降で送信します。これは実質的に通常のHTTPS接続と変わらないため、企業やサービスプロバイダーは、どのようなリクエストが行われているかを確認できません。Mozillaなどの支持者は、ユーザーがアクセスするサイトを第三者に知られないようにすることで、この仕組みがユーザーのプライバシー向上につながると主張しています。

しかし、厳密にはそうではありません。DoHに批判的な人々は、DoHがDNSにおけるTorとは言い切れないと指摘しています。ブラウザーがDoHで検索したホストに最終的に接続する際、リクエストではほぼ確実にTLSのServer Name Indication(SNI)拡張が使用されるためです。SNIにはホスト名が含まれ、平文で送信されます。さらに、ブラウザーがOnline Certificate Status Protocol(OSCP)を使用してサーバー証明書の検証を試みる場合、その処理も平文で行われる可能性が高いと考えられます。したがって、DNS検索を監視できる者は、接続時のSNIやOCSP検証時の証明書名も読み取ることができます。

多くの人にとって、DoHの最大の問題は、ユーザーのDoHリクエストがデフォルトで送信されるDNSサーバーをブラウザベンダーが選択することです(たとえば、米国のFirefoxユーザーの場合、DNSサーバーはCloudflareが所有しています)。DNSサーバーの運営者は、ユーザーのIPアドレスと、ユーザーがリクエストを送信したサイトのドメイン名を確認できます。大した情報ではないように思えるかもしれませんが、イリノイ大学の研究者は、いわゆるページロードフィンガープリント、つまりWebページの構成要素に対するリクエストの送信先アドレスだけから、その人がどのWebサイトにアクセスしたかを推測できることを明らかにしました。この情報は、「ユーザーをプロファイリングし、広告のターゲットにする」ために利用される可能性があります。

NGINXはどのように役立つのか

DoTとDoHは本質的に悪いものではなく、ユーザーのプライバシー向上につながるユースケースもあります。しかし、公開型の一元管理されたDoHサービスはユーザーのプライバシーに悪影響を及ぼすという見解が広まりつつあり、そのようなサービスは決して使用しないことを推奨します。

いずれにしても、サイトやアプリ向けに独自のDNSゾーンを管理しているケースが多いでしょう。これには、パブリックゾーン、プライベートゾーン、スプリットホライズン構成のゾーンなどがあります。将来的に、独自のDoTまたはDoHサービスを運用したいと考えることもあるでしょう。その際に役立つのがNGINXです。

DoTによるプライバシー強化はDNSセキュリティに大きなメリットをもたらしますが、現在使用しているDNSサーバーがDoTに対応していない場合はどうすればよいでしょうか。NGINXは、DoTと標準DNSの間のゲートウェイとして機能することで、この課題の解決を支援します。

DoTポートがブロックされる可能性がある場合には、ファイアウォールを通過できるDoHの特性が役立つかもしれません。この場合も、NGINXはDoHからDoT/DNSへのゲートウェイを提供することで対応できます。

シンプルなDoT-DNSゲートウェイの導入

NGINX Stream(TCP/UDP)モジュールはSSL終端をサポートしているため、DoTサービスを非常に簡単にセットアップできます。わずか数行のNGINX設定で、シンプルなDoTゲートウェイを構築できます。

DNSサーバー用の「upstream」ブロックと、TLS終端用の「server」ブロックが必要です:

コード例を読み込み中…

もちろん、逆方向に、受信したDNSリクエストをアップストリームのDoTサーバーへ転送することもできます。ただし、DNSトラフィックの大半はUDPであり、NGINXが変換できるのはDoTとTCPベースのDNSなどの他のTCPサービスとの間に限られるため、この方法の有用性は低くなります。

コード例を読み込み中…

シンプルなDoH-DNSゲートウェイ

シンプルなDoHゲートウェイの構成は、DoTゲートウェイと比べてやや複雑です。HTTPSサービスとStreamサービスの両方が必要であり、JavaScriptコードとNGINX JavaScript module<.htmla>(njs)を使用して、2つのプロトコル間で変換します。最もシンプルな構成は次のとおりです。

コード例を読み込み中…

この構成では、パケットをDNSサービスに転送するために必要な最小限の処理のみを行います。このユースケースでは、その他のフィルタリング、ログ記録、セキュリティ機能はアップストリームDNSサーバーが実行することを前提としています。

この構成で使用するJavaScriptスクリプト(nginx_stream.js)には、さまざまなDNSライブラリモジュールファイルが含まれています。dns.jsモジュール内のdns_decode_level変数では、DNSパケットに対して実行する処理の量を設定します。DNSパケットの処理はパフォーマンスを低下させます。上記のような構成を使用する場合は、dns_decode_levelを0に設定してください。

より高度なDoHゲートウェイ

NGINXはHTTPを得意としているため、単純なDoHゲートウェイとしてのみ使用するのは、その可能性を十分に活かせず、もったいないと考えています。

ここで使用するJavaScriptコードは、DNSパケットを完全または部分的にデコードするよう設定できます。これにより、DNS応答の最小TTLに基づいてExpiresヘッダーとCache-Controlヘッダーを設定した、DoHクエリ用のHTTPコンテンツキャッシュを構築できます。

以下に、接続の最適化を強化し、コンテンツのキャッシュとログ記録にも対応した、より包括的な例を示します。

コード例を読み込み中…

NGINX Plusの機能を使用した高度なDNSフィルター

NGINX Plusサブスクリプションをご利用の場合は、上記の例に、アクティブヘルスチェックや高可用性などの高度なNGINX Plus機能を組み合わせることができます。また、キャッシュされたDoHレスポンスの管理にキャッシュパージAPIを使用することもできます。

NGINX Plusのキーバリューストアを使用して、悪意のあるドメインへのアクセスを実質的に阻止するDNSレスポンスを返すことで、ユーザーを保護するDNSフィルタリングシステムを構築することもできます。キーバリューストアの内容は、RESTful NGINX Plus APIを使用して動的に管理します。

悪意のあるドメインを「blocked」と「blackhole」の2つのカテゴリーに分類します。DNSフィルタリングシステムは、ドメインに対するDNSクエリをカテゴリーに応じて異なる方法で処理します。

  • For blockedドメインの場合、NXDOMAIN応答(ドメインが存在しないことを示す)を返します
  • For blackholeドメインでは、Aレコードのリクエストに対して0.0.0.0を値とするDNS Aレコードを1件返し、AAAA(IPv6)レコードのリクエストに対して::を値とするAAAAレコードを1件返します。その他のレコードタイプでは、回答が0件のレスポンスを返します

DNSフィルターはリクエストを受信すると、まず、照会されたFQDNと完全に一致するキーがあるかどうかを確認します。一致するキーが見つかった場合は、関連付けられた値の指定に従ってリクエストを「スクラブ」(ブロックまたはブラックホール化)します。完全一致がない場合は、ブロック対象ドメインとブラックホール対象ドメインの2つのリストでドメイン名を検索し、一致するものがあれば、適切な方法でリクエストをスクラブします。

照会されたドメインが悪意のあるものでない場合、システムは通常の処理を行うため、そのリクエストをGoogle DNSサーバーに転送します。

Key-Value Storeの設定

悪意があると判断したドメインを、NGINX Plusのキーバリューストアにある2種類のエントリで定義します。

  • Individual entries for fully qualified domain name (FQDNs), each with a value of either blocked or blackhole
  • ドメインのリストが2つあり、キーはそれぞれ「blocked_domains」と「blackholed_domains」で、各キーにはカンマ区切り値(CSV)形式のドメインリストがマッピングされています

キー値ストアの次の設定は、streamコンテキストに記述します。keyval_zoneディレクティブは、dns_configという名前のキー値ストア用メモリブロックを割り当てます。最初のkeyvalディレクティブは、設定済みの一致するFQDNキー値ペアを読み込み、2番目と3番目のディレクティブは2つのドメインリストを定義します。

コード例を読み込み中…

その後、NGINX Plus APIを使用して、明示的に除外する任意のFQDNキーに値「blocked」または「blackhole」を割り当てるか、「blocked_domains」または「blackhole_domains」キーに関連付けられたCSV形式のリストを変更できます。完全一致するFQDNの変更や削除、いずれかのリストに含まれるドメインの変更はいつでも可能で、変更内容はDNSフィルターに即座に反映されます。

リクエストを処理するアップストリームサーバーの選択

以下の設定では、変数「$dns_response」を読み込みます。この変数には、以下の「js_preread」で定義されている「server」ブロック内の「DNSクエリをリッスンするサーバーの設定」ディレクティブによって値が設定されます。呼び出されたJavaScriptコードがリクエストのスクラビングが必要と判断すると、必要に応じてこの変数を「blocked」または「blackhole」に設定します。

We use a mapディレクティブを使用して、$dns_response変数の値を$upstream_pool変数に割り当てます。これにより、以下の「アップストリームサーバーの定義」で示すアップストリームグループのうち、どのグループがリクエストを処理するかを制御します。悪意のないドメインへのクエリはデフォルトのGoogle DNSサーバーに転送され、ブロック対象またはブラックホール対象のドメインへのクエリは、それぞれのカテゴリに対応するアップストリームサーバーで処理されます。

コード例を読み込み中…

アップストリームサーバーの定義

この設定では、アップストリームサーバーの「blocked」、「blackhole」、「google」グループを定義します。各グループはそれぞれ、ブロック対象、ブラックホール対象、悪意のないドメインへのリクエストを処理します。

コード例を読み込み中…

DNSクエリをリッスンするサーバーの設定

ここでは、受信するDNSリクエストを待ち受けるサーバーをstreamコンテキストで定義します。js_prereadディレクティブは、DNSパケットをデコードし、パケットのNAMEフィールドからドメイン名を取得して、キーと値のストアでそのドメインを検索するJavascriptコードを呼び出します。ドメイン名がFQDNキーと一致するか、blocked_domainsまたはblackhole_domainsのいずれかのリストに含まれるドメインのゾーン内にある場合、そのリクエストは破棄されます。それ以外の場合は、名前解決のためにGoogle DNSサーバーへ送信されます。

コード例を読み込み中…

あと少しです。最後に、クエリに対してブラックホール化またはブロックのいずれかの応答を返すserverブロックを追加するだけです。

コード例を読み込み中…

このブロック内のサーバーは、すぐ上にある実際のDNSサーバーとほぼ同じです。主な違いは、リクエストを上流のDNSサーバーグループに転送する代わりに、クライアントへ応答パケットを送信する点です。JavaScriptコードは、ポート9953または9853から実行されていることを検出すると、パケットをブロックすることを示すフラグを設定する代わりに、実際の応答パケットを$dns_responseに格納します。必要な処理はこれだけです。

もちろん、このフィルタリングはDoTおよびDoHサービスにも適用できますが、ここでは簡潔にするために標準DNSを使用しました。DNSフィルタリングとDoHゲートウェイの統合については、読者の演習課題とします。

フィルターのテスト

これまでにNGINX Plus APIを使用して、2つのFQDNのエントリといくつかのドメインを、2つのドメインリストに追加しました。

$ curl -s http://localhost:8080/api/5/stream/keyvals/dns_config | jq{
   "www.some.bad.host": "blocked",
   "www.some.other.host": "blackhole",
   "blocked_domains": "bar.com,baz.com",
   "blackhole_domains": "foo.com,nginx.com"
}

そのため、名前解決をリクエストすると、www.foo.com(ブラックホール化されたfoo.comドメイン内のホスト)について、IPアドレス0.0.0.0のAレコードが返されます。

$ dig @localhost www.foo.com
; <<>> DiG 9.11.3-1ubuntu1.9-Ubuntu <<>> @localhost www.foo.com
; (1 server found)
;; global options: +mcd
;; Got answer:
;; ->>HEADER,,- opcode: QUERY, status: NOERROR, id: 58558
;; flags: qr aa rd ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
; www.foo.com.                  IN      A

;; ANSWER SECTION:
www.foo.com.           300      IN      A      0.0.0.0

;; Query time: 0 msec
;; SERVER: 172.0.0.1#53(126.0.0.1)
;; WHEN: Mon Dec 2 14:31:35 UTC 2019
;; MSG SIZEW  rcvd: 45

コードへのアクセス

DOHとDOTのファイルは、私の「GitHubリポジトリ」で入手できます:

  • JavaScriptコードはnjs.d/dnsフォルダーにあります
  • The NGINX configuration in the examplesフォルダー内のNGINX設定

NGINX Plusをお試しいただくには、今すぐ30日間の無料トライアルを開始するか、ユースケースについてお問い合わせください。


共有
タグ: DNS, Tech, 2022, F5 NGINX

関連するブログ記事

チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する
NGINX | 2024/01/18

チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する

OpenTelemetryを使用してNGINXサーバーの状態と健全性をより明確に把握することで、問題のトラブルシューティングを行い、Webアプリケーションのパフォーマンスを向上できます。

AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正
NGINX | 2023/05/31

AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正

Akita SoftwareのAPIオブザーバビリティ向け新しいNGINX Plus Certified Moduleを活用して、APIエンドポイントの問題をより迅速に特定し、修正できます。

JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法
NGINX | 2023/02/09

JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法

Red HatのシニアプリンシパルデベロッパーアドボケイトであるDaniel Oh氏が、Javaプロジェクトをゼロから構築するためのひな形を作成し、Kubernetesネイティブアプリケーションとしてビルドする方法を解説します。

F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング
NGINX | 2022/05/24

F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング

NGINXaaS for Azureは、NGINX PlusをAzureネイティブのSaaSとして利用できるサービスです。NGINXの設定を容易にリフト&シフトできるため、クラウド導入とモダンアプリの展開を加速できます。

SELinuxでNGINXおよびNGINX Plusを使用する
NGINX | 2018/08/17

SELinuxでNGINXおよびNGINX Plusを使用する

Red Hat Enterprise Linux(RHEL)および関連ディストリビューションでSecurity-Enhanced Linux(SELinux)を有効にすると、デフォルト設定によりNGINXとNGINX Plusの一部の操作が制限されます。この記事では、すべての機能を利用できるようにSELinuxの設定を変更する方法について説明します。

NGINX PlusによるAWS Auto Scalingグループの負荷分散
NGINX | 2017/03/06

NGINX PlusによるAWS Auto Scalingグループの負荷分散

新しいnginx-asg-sync統合ソフトウェアにより、NGINX PlusはAWS Auto Scalingグループの負荷を直接分散できるため、クラウドでの負荷分散がさらに柔軟になります