鷲ノ巣

C# とか PowerShell とか。当ブログの記事は(特に公開直後は)頻繁に改定される場合があることをご了承ください。

CNAME を巡る 2/3 のジレンマ

当ブログをご愛読頂いている方であれば、当然ご存知ないことと思いますが、俺は DNS が大好きです。
とは言っても、DNS サーバーの構築とか運用に詳しいわけでも、攻撃に対抗する方法を知っているわけでもありません。
やったことがあるのは、せいぜい、レンタル DNS サーバーでゾーン定義ファイルを書いて遊ぶくらいのものです。
昨今は、DNSSEC で遊びたいのですが、そのための環境が無く、悲しみに暮れています。

閑話休題

タイトルの 2/3 のジレンマというのは、DNS の運用において成立させたい、とある 3 つの性質のうち、同時に成立できるのは 2 つまでで、3 つ全部を成り立たせるのは不可能だ、ということを指しています。
その 3 つの性質というのは、

です。
こういうの、何て言うんでしょうか。三すくみとか三つ巴では意味が違うし。あちらを立てればこちらが立たず。3 本同時にはどうやっても立たない。的な。

追記

「トリレンマ」と言うと教えて戴きました。
なるほど、2つだからジレンマ(di-lemma)で、3つならばトリレンマ(tri-lemma)というわけですね。

サービス提供者のつもりになる

このジレンマが問題になるのは、自分が(あるいは、読者であるあなたが)インターネット サービスの提供者になる場合です。
もう少し具体的に言うなら、小規模であればレンタル サーバー、大規模であればクラウド サービスと呼ばれるようなサービスを提供していると思ってください。
つまり、Web サーバーを用意して、その上で顧客が作成したコンテンツやアプリケーションをホスティングするという形態のサービスです。

RFC を読む

このジレンマは、CNAME レコードにおける制約に起因しています。
RFC 1912 に、こう書いてあります。

A CNAME record is not allowed to coexist with any other data.

2.4 CNAME record

「CNAME レコードは、他のデータと共存できません」だそうです。
これでは意味がよくわからないので、続けて読んでみます。

In other words, if suzy.podunk.xx is an alias for sue.podunk.xx, you can't also have an MX record for suzy.podunk.edu, or an A record, or even a TXT record.
Especially do not try to combine CNAMEs and NS records like this!:

podunk.xx.      IN      NS      ns1
                IN      NS      ns2
                IN      CNAME   mary
mary            IN      A       1.2.3.4
2.4 CNAME record

言い換えれば、suzy.podunk.xx が sue.podunk.xx の別名である場合、suzy.podunk.edu のための MX レコードや A レコード、TXT レコードさえも設定することはできません。
特に CNAME と NS レコードを組み合わせようとしないでください。

ふむ。
suzy.podunk.edu は suzy.podunk.xx の間違いでしょうか? エラッタは発行されていないようですが…。
要するに、ある名前で CNAME を定義したら、もうその名前では他のあらゆるレコードを定義することができないということで良いのでしょうか。

つまり、こういうことは出来ないということです。

foo.example.com. IN CNAME foo1
                 IN CNAME foo2
                 IN CNAME foo3

foo.example.com という名前の CNAME レコードを 3 つ定義しようとしています。
同じ名前では他のレコードを定義できないというのは、CNAME 同士であっても例外ではありません。
ちなみに、上記のような記述は、昔は DNS ラウンドロビンで使われていたらしいです。現在では使用できません。

CNAME でなければ問題はないので、以下のような記述は(DNS ラウンドロビンという手法の問題点はありますが、それは別として、DNS の仕様上は)可能です。

foo.example.com. IN A 192.0.2.80
                 IN A 192.0.2.81
                 IN A 192.0.2.82

さて、もう少し RFC を読んでいきましょう。

This is often attempted by inexperienced administrators as an obvious way to allow your domain name to also be a host.
However, DNS servers like BIND will see the CNAME and refuse to add any other resources for that name.
Since no other records are allowed to coexist with a CNAME, the NS entries are ignored.
Therefore all the hosts in the podunk.xx domain are ignored as well!

If you want to have your domain also be a host, do the following:

podunk.xx.      IN      NS      ns1
                IN      NS      ns2
                IN      A       1.2.3.4
mary            IN      A       1.2.3.4
2.4 CNAME record

これはしばしば、経験の少ない管理者が、ドメイン名もホストとするためのわかりやすい方法として試みることです。
しかし、BIND のような DNS サーバーは CNAME を見て、その名前に対する他のリソースを追加することを拒否するでしょう。
他のレコードが CNAME と共存することを許されないので、NS エントリーが無視されます。従って、podunk.xx ドメインのホストは同様に無視されます。

ドメイン名をホストにしたい場合、以下のようにしてください。

なるほど大変だ。

ちなみにこの後には、比較的よく知られていると思われる、「MX 等のリソースレコードが CNAME レコードを指してはならない」という注意書きが続きますが、それは割愛します。

Zone Apex によるアクセス

さて、ここで、今でもしばしば行われていそうな誤った設定の例が挙げられています。
何だか原文で使われているドメインの例が好きになれないので、前述の例に倣って書き換えますと、こういうことです。

$ORIGIN foo.example.com.

@   IN CNAME www
www IN A 192.0.2.80

つまり、www.foo.example.com でも foo.example.com でも同じサイトを見せたいがために、ドメイン名である foo.exmample.com を www に対する CNAME にしたというわけです。
一般にドメイン名と同じ名前は @ と省略しますので、以降、このように書くこととします。

さて、しかし、ドメイン名の直下には、通常、RFC にも書かれていた NS レコードや、MX レコード、TXT レコードなどが存在します。

@   IN SOA ns1 postmaster (
                  ...
    )

    IN NS ns1
    IN NS ns2

    IN MX 10 mx

    IN SPF "v=spf1 mx -all"
    IN TXT "v=spf1 mx -all"

ns1 IN A 192.0.2.53
ns2 IN A 192.0.2.54
mx  IN A 192.0.2.25

www IN A 192.0.2.80

@   IN CNAME www

おわかりでしょうか。
上記の例では、最後の CNAME があるがために、@ に対する SOA も、NS も、MX も、SPF も TXT も、書くことができないのです(つまりこの例は不正です)!。

ではどうすればよいか。方法は 2 つあります。
両方とも A レコードで書く

www IN A 192.0.2.80
@   IN A 192.0.2.80

か、もしくは、@ の方を実体にして、www をエイリアスにしてしまいます。

@   IN A 192.0.2.80
www IN CNAME @

俺が使っているメールのドメインAmazon Route53 で管理していますが、Route53 では、ドメイン名と同じ CNAME を作ることはできなくなっています。

このように、www 等のホスト名を伴わず、ドメイン名だけでアクセスすることを、「Zone Apex」というようです。Apex というのは頂点という意味です。
これが、ジレンマの 1 つめ、Zone Apex によるアクセスです。

DNS サーバーのアウトソーシング

昨今、どこのサービスでも、だいたい独自ドメインに対応していると思います(当社調べ)。
顧客が用意したドメイン名でサービスにアクセスしてもらうために、顧客が管理している DNS サーバーに、適切なレコードの設定をしてもらう。
そのためのドキュメントは提供する必要があります。

顧客としては、このドメインを、いろいろと他の用途(メールとか)にも利用したいわけで、Web サービスだけ提供する弊社が、ドメインDNS サーバーの管理までまるっと引き受けるわけには行きません。
というわけで、自社が提供するサービスの外部にある DNS サーバーを利用してもらう。
これを、DNS サーバーのアウトソーシングと言います。

例えば、先の例を使うなら、顧客に 192.0.2.80 という IP アドレスを割り当てるので、顧客が管理する DNS サーバー上において、

@   IN A 192.0.2.80
www IN CNAME @

という設定をしてもらうということになります。

変動する IP アドレス

さて、小規模なレンタル サーバー サービスであれば、顧客には上記のように IP アドレスを一つ割り当てておけば事足りるでしょう。
実際、そういうサービスは多くあります。

しかし、ここでは、小規模なゾーンのニワカ管理者の立場を一旦忘れて、Web サーバーが何十台とか何百台とかあるような、大規模なインターネットサービスの管理者のつもりになってみます。
顧客が期待するのは、顧客がアップロードしたコンテンツがちゃんと配信されることです。サーバーの構成なんかはどうでもいいことです。
一方でサービス提供者は、顧客にとってはどうでもいい内部の事情で、しばしばネットワーク構成を変更したりします。

このような場合に、顧客に対して IP アドレスを通知して使ってもらっていると、それを軽々に変更することができません。
事業者側としては、顧客に影響を与えずに内部の構成変更をしたい。
どうすればいいか。そうだ、CNAME を使おう。

つまり、顧客に対して、IP アドレスではなくホスト名を割り当て、その名前を CNAME で参照してもらう。
こうすれば、事業者はその名前に対する IP アドレスは自由に変更することができます。
顧客に割り当てた CNAME はシステムが生成した適当なものですから、本来のサービスのドメインにするために、こういう設定をします。

www.foo.example.com. IN CNAME service-internal-name-97c2e416fed4.example.com.

2/3 のジレンマ

これで 3 つの要素がすべて出揃いました。
これらを同時にすべて成立させようとすると、どうなるでしょうか。

Zone Apex でアクセスさせたい。しかし、ドメイン名と同じ CNAME は作れない。A レコードを作ればいいんだけど、IP アドレスはころころ変わるので、CNAME にしたい。
ここで「CNAME にしたい、でも出来ない」という矛盾が発生してしまいます。

実際、かつてこの問題が発生していたのが、AWS の ELB (Elastic Load Balancing) と、DNS サービスである Route53 です。
ELB の IP アドレスは変動し得るため、独自ドメインでアクセスする際には A レコードを作ることができません。そのため、CNAME で設定する必要があります。
しかし、そうすると Zone Apex アクセスを諦めざるを得ない。

この問題を解決するために、Amazon は Route53 にエイリアスという独自機能を追加しました。
エイリアスとは、管理上は ELB のホスト名に対する別名のように扱うことができるのですが、DNS サーバーの外部からは A レコードとして見えるというものです。
通常、A レコードは、その IP アドレスを変更しない限り、いつでも同じ IP アドレスを返しますが、エイリアスの場合は、ELB の IP アドレスが変わると、それに追従して A レコードが指す IP アドレスも自動的に変更されるわけです。

つまり、DNS サーバーとして Route53 を使うならば、Zone Apex と IP アドレスの変動には対処することができます。
ということは、DNS サーバーのアウトソーシングを諦める必要があるということでもあるわけです(顧客に「Route53 を使ってください」と言うのは非現実的ですよね。既に利用している幸運な顧客ならともかく…)。

Zone Apex でのアクセスを諦めれば(何らかのホスト名を割り振れば)、変動し得る IP アドレスに対する CNAME を作成することは問題なくできます。
DNS サーバーのアウトソーシングを諦め、自社で管理する Route 53 で顧客のドメインをすべて面倒見るならば、エイリアス機能を使うことができます。
変動し得る IP アドレスを諦めれば、顧客に対して IP アドレスを静的に割り当てることで解決できます。
いずれか 1 つを諦めれば、他の 2 つを成立させることはできるのですが、3 つ同時にはできない。
これを、2/3 のジレンマと呼んでみたというわけです。

無茶をすれば…

しかしながら、実は、3 つをすべて成立させることができている場合もあります。
つまり、ドメイン名と同じ CNAME レコードを作って、それで問題なくアクセスできているということです。

ただし、それは偶然に依存した、危ういバランスの上に成り立っていることだというのを忘れてはなりません。
仕様で禁止されている CNAME の使い方をしていて、DNS サーバーが、偶然それで都合よく振る舞っているというだけのことなのです。
DNS サーバー ソフトウェアを入れ替えたり、バージョンアップしたりして、サーバーの挙動が変われば、その CNAME レコードばかりでなく、ドメイン全体がアクセス不能になり得るということは、既に指摘されています。

特定の DNS サーバー ソフトウェアの、特定のバージョンでは、CNAME の扱いが厳密でないために、たまたま望んだ動作をするということもあるでしょう。
ですが、DNS サーバーはアウトソーシングされているのです。そのソフトウェアの入れ替えやバージョンアップを、サービス事業者が制御することができないのです。
こんな恐ろしいことがあるでしょうか。

サービス事業者として、3 つの要望をすべて成立させるために、DNS サーバーにこういう設定をしてくださいという案内をしているはずです。
それは一歩間違えれば、ドメイン全体を停止させかねない設定方法です。
その際、こうした注意事項まできちんと説明して、合意しているでしょうか?
CNAME について望んだ扱いをしてくれるサーバー ソフトウェア名とバージョンのリストを持っているでしょうか?
まぁ、そうやってバージョンを限定するというのも、甚だしく不健全な運用ではあるのですが。

そういえば?

Dynamic DNS って、解決策になるんでしょうかね?