DNSリクエストをAPIコールへ変換するアダプタをご照覧あれ

2022年04月15日 金曜日


【この記事を書いた人】
田口 景介

社会人生活の半分をフリーランス、半分をIIJで過ごすエンジニア。元々はアプリケーション屋だったはずが、クラウドと出会ったばかりに半身をインフラ屋に売り渡す羽目に。現在はコンテナ技術に傾倒中だが語りだすと長いので割愛。タグをつけるならコンテナ、クラウド、ロードバイク、うどん。

「DNSリクエストをAPIコールへ変換するアダプタをご照覧あれ」のイメージ

もう3年も前のことになりますが、Let’s Encrypt用クライアントツールであるlegoにIIJのDNSアウトソースサービス用プラグインをコントリビュートし、ごく簡単なオペレーションでワイルドカード証明書を全自動に発行できるツールを提供していました。これが思いのほか好評で、ブログをご覧になったお客様にも利用していただいていたようです。

そんなツールもDNSアウトソースサービスのディスコンと共に近々お役御免となります。では、後継であるIIJ DNSプラットフォームサービス(DPF)向けに同種のツールを開発するかと言う話になったのですが、それは却下されました。あれから3年が経ち、いまやIaC(Infrastructure as Code)は当たり前のものになり、DNSレコードの管理に使いたいツールは増える一方です。そんなツール群に一つ一つ手を入れてDPFに対応させるのは、とてもではありませんが現実的ではないからです。

そしてそれは、IIJのDNSサービスに限った話ではありません。前述のlegoは積極的にコントリビュートを受け付けているため、いまや100を超えるDNSサービスに対応している状況ですが、たいがいのツールはそんな面倒な対応はしてくれません。Route53のようなシェアの高いサービスにはネイティブに対応し、それ以外の有象無象(失礼)のDNSサービスはwebhookのインターフェイスを各自使ってください、とされるのが一般的です。

各社のDNSサービスのAPI仕様はまちまち、クライアントツールのプラグインやwebhookのインターフェイスの仕様もまちまち。これでは、とてもではありませんが宣言的にDNSレコードを管理する汎用的なツールの開発などままなりません。クラウドベンダが提供する人気のDNSサービスだけエコシステムが充実するのもやむなしといったところです。

そんなDNS界隈ですが、どんなツールでもたいがいはサポートしてくれている標準インターフェイスが存在します。それがRFC2136として定義されている、通称Dynamic DNSです。DNSサービスが直接RFC2136をサポートしてくれていれば、いちいちプロプライエタリなAPIを使わずにいろいろなツールで連携できるというわけです。実際には、DNSサービスが提供したい機能はレコードの編集だけではなくもっと幅広いものです。そして、それをRFC2136だけで満たせないことはわかっていますし、RFC2136をネイティブにサポートするDNSサービスが皆無であることも承知していますが、それでもと思わずにはいられません。

DNSのリクエストをAPIコールに変換するアダプタ

そこで、抜本的にアーキテクチャを見直し、IIJのSREチームで実装したのがdpf-ddns-adaptorです。これは一種の簡易なDNSサーバで、53/udp, tcpでDNSプロトコルのリクエストを受け付けると、DNSサービスのAPIコールに変換し、該当機能をエミュレートするという代物です。つまり、dpf-ddns-adaptorでDNSサービスをラップすると、あたかもDNSサービスが直接RFC2136に対応しているかのように利用できるというわけです。

おかげで、legoはもちろん、dig, nslookup, nsupdate, external-dns, cert-manager, etc、RFC1035, RFC2136等の仕様を満たすDNSクライアント類には一切手を加えることなく、IIJ DNSプラットフォームサービスへのクエリ、編集に使えるようになりました。DNSサーバとしては少々端折って実装されているためツールによっては動かない場合があるかもしれませんが、今のところはRFC2136を通じてレコード編集を行う場合、付随的に行われるクエリに支障なくレスポンス返すことを優先して実装されています。

dpf-ddns-adaptorはまだ非公開のツールですが、実際にDNSレコードを読み書きする様子をご覧に入れたいと思います。

まず、dpf-ddns-adaptorを起動します。ここでは事前に入手しておいたIIJ DNSプラットフォームサービスのアクセストークンとTSIGの鍵を指定しています。正常に起動すると「start to reconcile」と表示され、デフォルトでは5353/tcp, udpでポートをオープンし、DNSサーバとしてリクエストの受付を開始します。

$ ZONE="編集したレコードがあるDNSゾーンの名前"
$ ./dpf-ddns-adaptor --reconcile-zone=${ZONE} --access-token=${ACCESSTOKEN} --api-endpoint=${API_ENDPOINT} --key-name=${RFC2136_TSIG_KEY} --tsig-key=${RFC2136_TSIG_SECRET}
2022/04/08 06:21:20 start to reconcile

さっそく、digでクエリしてみましょう。ポート番号が標準の53ではないためフラグで明示しています。あたかも普通のDNSサーバへアクセスしたかのようにレスポンスがありますが、出力されているのはAPIのレスポンスをDNSプロトコルに成形したものです。あまりにも普通過ぎて、何がうれしいのかわかりませんね。

$ dig -p 5353 +noall +answer @localhost SOA ike.iij.jp.
ike.iij.jp.             0       IN      SOA     ns000.d-53.net. dns-managers.iij.ad.jp. 8 3600 600 604800 900
$ dig -p 5353 +noall +answer @localhost AXFR ike.iij.jp.
ike.iij.jp.             0       IN      SOA     ns000.d-53.net. dns-managers.iij.ad.jp. 8 3600 600 604800 900
ike.iij.jp.             0       IN      NS      ns00.iij-dns.net.
ike.iij.jp.             0       IN      NS      ns01.iij-dns.jp.
ike.iij.jp.             0       IN      SOA     ns000.d-53.net. dns-managers.iij.ad.jp. 8 3600 600 604800 900

それでは本命のnsupdateでレコードを追加してみます。ネームサーバの反映までしばらく時間がかかりますが(数分程度)、期待どおりにレコードが追加できたことを確認できます。nsupdateによってレコードを書き込むときにはdpf-ddns-adaptorを経由していますが、その後のレコードの存在確認は本物の権威DNSサーバへアクセスしていることから、API経由でゾーンが更新され、実際にDNSサーバへと反映されたことがわかります。やっぱり普通過ぎて、何がうれしいのかわかりにくいですね。

$ nsupdate -p 5353 -y hmac-sha256:${RFC2136_TSIG_KEY}:${RFC2136_TSIG_SECRET} <<EOF
server 127.0.0.1
update add www.ike.iij.jp. 300 IN A 202.232.2.180
send
EOF
$ # 数分待ってから、本物の権威DNSサーバへアクセスしてレコードの存在を確認する
$ dig +noall +answer @ns00.iij-dns.net. A www.ike.iij.jp.
www.ike.iij.jp.         28800   IN      A       202.232.2.180

legoを使ってLet’s Encryptからサーバ証明書を発行する

もっといろいろなツールでDNSの管理を行うとありがたみを感じられると思うので、今度は前述のlegoを使ってLet’s Encryptからサーバ証明書を発行してみましょう。legoはDNSレコードを管理するツールというわけではありませんが、認証手段にDNSを利用するためRFC2136クライアントとしての機能を備えています。

少々寄り道になりますが、Let’s EncryptがサポートするACMEの認証プロセスについて簡単に解説します。ACMEを利用してサーバ証明書を発行するには、その証明書に指定されたSANsに一致するDNSゾーンの所有者であることを証明する必要があります。いわゆる、ドメイン認証証明書の発行プロセスがプロトコルとして厳密に定義されたものと考えていいでしょう。その証拠を示す手段はいくつか用意されていますが、DNSを利用する方式がDNS-01認証です。これは、ACMEサーバから発行されたトークンを証明書のSANsにマッチするゾーンへTXTレコードとして書き込むことで認証を行う仕組みです。そのゾーンへレコードを書き込める、すなわち所有者であるとみなすということです。

dpf-ddns-adaptorを起動できていれば、legoを利用するのはいたって簡単です。ネームサーバとしてdpf-ddns-adaptorがオープンしている127.0.01:5353を指定し、コマンドラインでDNSプロバイダにrfc2136(以前はiijでした)を指定するだけです。

$ export RFC2136_NAMESERVER=127.0.0.1:5353
$ lego --email iijtaro@iij.ad.jp --dns rfc2136 --domains 'acme.ike.iij.jp' run
2022/04/12 17:31:49 [INFO] [acme.ike.iij.jp] acme: Obtaining bundled SAN certificate
2022/04/12 17:31:50 [INFO] [acme.ike.iij.jp] AuthURL: https://acme-v02.api.letsencrypt.org/acme/authz-v3/97447478600
2022/04/12 17:31:50 [INFO] [acme.ike.iij.jp] acme: Could not find solver for: tls-alpn-01
2022/04/12 17:31:50 [INFO] [acme.ike.iij.jp] acme: Could not find solver for: http-01
2022/04/12 17:31:50 [INFO] [acme.ike.iij.jp] acme: use dns-01 solver
2022/04/12 17:31:50 [INFO] [acme.ike.iij.jp] acme: Preparing to solve DNS-01
2022/04/12 17:31:51 [INFO] [acme.ike.iij.jp] acme: Trying to solve DNS-01
2022/04/12 17:31:51 [INFO] [acme.ike.iij.jp] acme: Checking DNS record propagation using [127.0.0.53:53]
2022/04/12 17:31:53 [INFO] Wait for propagation [timeout: 10m0s, interval: 2s]
2022/04/12 17:31:53 [INFO] [acme.ike.iij.jp] acme: Waiting for DNS record propagation.
--snip--
2022/04/12 17:32:36 [INFO] [acme.ike.iij.jp] acme: Waiting for DNS record propagation.
2022/04/12 17:32:41 [INFO] [acme.ike.iij.jp] The server validated our request
2022/04/12 17:32:41 [INFO] [acme.ike.iij.jp] acme: Cleaning DNS-01 challenge
2022/04/12 17:32:41 [INFO] [acme.ike.iij.jp] acme: Validations succeeded; requesting certificates
2022/04/12 17:32:43 [INFO] [acme.ike.iij.jp] Server responded with a certificate.
$ ls -l .lego/certificates/acme.ike.iij.jp.crt
-rw------- 1 iijtaro admin 5317 Apr 12 17:32 .lego/certificates/acme.ike.iij.jp.crt    # サーバ証明書が発行された

ツールへ一切手を加えることなく、またプラグインを開発するでもなく、すんなり証明書の発行ができました。今回はlegoをサンプルに使ってみましたが、Kubenretes界隈で広く使われているcert-managerやexternal-dnsでももちろん利用できますし、その他terraformなどrfc2136に対応するクライアントであれば広く利用できると思われます。ちょっと便利だと思いませんか?

今回はLet’s Encryptを利用しましたが、認証局によるACMEのサポートは広がりを見せており、つい先日もgoogleからPrivate PreviewではありますがACMEをサポートした認証局がアナウンスされました。ツールにしろ、サービスにしろ、インターフェイスの標準化が進むと、エコシステムがどんどん充実していきますね。

https://cloud.google.com/blog/products/identity-security/automate-public-certificate-lifecycle-management-via–acme-client-api

紆余曲折ありました

実はこのツールへ行きつく前に、いろいろな試行錯誤がありました。以前に稼働していた構成は、dpf-ddns-adaptorに相当する仕事をBINDにやってもらい、セカンダリDNSサーバとして利用するDNSサービスへゾーン転送するというものでした。

この構成ではなんら開発をすることなく、DNSサーバのコンフィグレーションだけでDynamic DNSを利用してDNSサービスのレコード編集を行うことができましたが、いくつかのデメリットがありました。

  • ゾーン転送するにはポート53/tcpでアクセスできる必要があるが、ブロックされることが多くネットワーク構成に制限を受ける
  • せっかく備わっているDNSSECの署名機能が使えない
  • IIJ DNSプラットフォームサービスのGUIでレコードの編集ができない
  • システム構成が複雑で、運用負荷が高い

特に、ゾーン転送を行うにはPort 53を開ける必要があるというのは、思いのほか頭を悩ませる問題でした。一時期DNSパケットのアンプ攻撃が流行して以降、不必要なPort 53宛の通信はブロックするのがごく一般的な考え方となりました。つまり、この構成はBINDの場所によってはなんらかのポリシーを設定しないとゾーン転送ができず、機能しないということを意味します。dpf-ddns-adaptorを利用した構成ならば、DNSサービスへのアクセスはHTTPベースのAPIコールだけですから、ほとんどネットワーク構成の影響を受けることがありませんし、必要に応じて柔軟にポリシー設定も可能です。

また、DNSサービスをセカンダリDNSサーバとして利用するコンセプトも足かせとなってしまいました。DNSの仕様上当然ながらプライマリDNSサーバにしかレコードの変更はできません。セカンダリDNSサーバはレプリカを受け取るだけです。つまり、DNSサービスが持っている優れたGUIは利用できません。また、IIJ DNSプラットフォームサービスはDNSSECの有効化に必要な署名を自動化する機能を備えていますが、署名とはDNSレコードの更新を意味しますから、セカンダリDNSサーバとして運用する以上利用できません。一方、現在のdpf-ddns-adaptorを利用した構成ならばいずれのデメリットも存在しません。

今にして思うと、DNSレコードを宣言的に管理し、構成管理を自動化するという目的を優先しすぎて、いろいろな不利益に目をつぶってしまったように思えます。反省することしきりです。

田口 景介

2022年04月15日 金曜日

社会人生活の半分をフリーランス、半分をIIJで過ごすエンジニア。元々はアプリケーション屋だったはずが、クラウドと出会ったばかりに半身をインフラ屋に売り渡す羽目に。現在はコンテナ技術に傾倒中だが語りだすと長いので割愛。タグをつけるならコンテナ、クラウド、ロードバイク、うどん。

Related
関連記事