DNS サーバの挙動をテストする
インフラの挙動をテストするためのフレームワークである infrataster という仕組みがあります。サーバ構築時のテストフレームワークは Serverspec が有名ですが、対象をインフラにしたものという乱暴な理解です。
で、インフラといえば DNS なんですが、僕は小心者なので DNS サーバの設定でしくじったりすると、被害が甚大なので、小さな修正でもドキドキしながらオペレーションをしています。また、設定直後はうまく動いているように見えて、実は他のエントリを書き換えてしまい後から障害になってしまう、ということも考えられます。まさに自分が自宅の内部ネットワークのメンテ時に凡ミスして一時的に名前解決ができなくなってツライ目にあってしまったので、infrataster の復習がてら、infrataster-plugin-dns を使ってみたので、そのメモを残しておこうと思います。
infrataster そのものは DNS サーバのテストはできませんが、infrataster-plugin-dns はその名前のとおり infrataster のプラグインとして作成されて、特定の DNS サーバを対象にしてクエリ応答のテストができます。
準備
infrataster は rspec の仕組みのうえに構築されているので、rspec --init
で rspec 的な準備をしておきます。
spec/spec_helper.rb
には以下のように検査したい DNS サーバを定義します。
require 'infrataster/rspec'
require 'infrataster-plugin-dns'
Infrataster::Server.define(:exapmle_com, 'a.iana-servers.net')
ここでは例として example.com
の権威サーバの a.iana-servers.net
を :example_com
として定義しています。
テストケースは spec/example.com_spec.rb
とかに書いていきます。
require 'spec_helper'
describe server(:exapmle_com) do
end
頑張ってテストケースを書く
ここからは運用している DNS サーバに登録している内容をひたすら列挙していく作業です。
infrataster-plugin-dns の README.md にも書かれていますが、基本的に rspec 的なマッチャーは rspec-dns をベースにしているので、そちらを参照していきます。さらに、rspec-dns は内部的に Dnsruby を呼び出しているので、各 RR の応答を検査するときは Dnsruby::RR クラスのドキュメントを読みながら空気を察する必要があります。
というわけで、いくつか例をあげておきます。
NS レコード
example.com.
の NS の場合は dig
で確認すると以下のように応答がありました。
;; QUESTION SECTION:
;example.com. IN NS
;; ANSWER SECTION:
example.com. 86400 IN NS a.iana-servers.net.
example.com. 86400 IN NS b.iana-servers.net.
この場合は Dnsruby::RR::NS を見ながら書くとこんな感じになります。
describe dns('example.com.') do
it 'NS RR : a.iana-servers.net' do
is_expected.to have_entry.with_type('NS').
and_ttl(86400).
and_domainname('a.iana-servers.net')
end
it 'NS RR : b.iana-servers.net' do
is_expected.to have_entry.with_type('NS').
and_ttl(86400).
and_domainname('b.iana-servers.net')
end
end
example.com
の SOA は dig
で確認すると以下のようになっています。
;; QUESTION SECTION:
;example.com. IN SOA
;; ANSWER SECTION:
example.com. 3600 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2018013047 7200 3600 1209600 3600
で、NS の時と同じように Dnsruby::RR::SOA のアトリビュートとRR応答の仕様を見比べながら書いていきます。
describe dns('example.com.') do
it 'SOA RR' do
is_expected.to have_entry.with_type('SOA').
and_ttl(3600).
and_mname('sns.dns.icann.org').
and_rname('noc.dns.icann.org').
and_refresh(7200).
and_retry(3600).
and_expire(1209600).
and_minimum(3600)
end
end
その他の RR
基本的に Dnsruby::RR のドキュメントや infrataster-plugind-dns や rspec-dns の例を見ておけば、なんとなく雰囲気は察することができると思いますし、代表的な RR については例があるのでわかりやすいと思います。
最後に書きながらハマったところをメモがてら残しておきます。
DNSEC 周りの応答には Base64 でエンコードしたデータが含まれていますが、そのまま and_signature
に渡すとテストに失敗します。Base64.decode64
で符号化を解いた状態でマッチャーに渡してあげましょう。コードの中身まで追っていませんが、Dnsruby の処理でそのようになっているような気がします。例えば、example.com
の DNSKEY の1つは以下のように応答があります。
example.com. 3600 IN DNSKEY 256 3 8 AwEAAblDjCPejMhknWXZqbwBEUPI6Lkwjvp0XlUNTBqW2glZrgf3MXjJ ZBXl8rhYoTkrov7jmbBaBOPTkqlQAbfOKFNoG+U+boGG6Zmy00l2XRP1 nckVMpJ2TxiDVcXJqs78MetC1Ztu4p6bj4VrJCYTmv3ZULSrWleMSWtv YXqY0S23
この場合は最後の公開鍵部分が Base64 でエンコードされているので、以下のようにするとテストできます。
describe dns('example.com.') do
it 'DNSKEY RR' do
is_expected.to have_entry.with_type('DNSKEY').
and_key(Base64.decode64('AwEAAblDjCPejMhknWXZqbwBEUPI6Lkwjvp0XlUNTBqW2glZrgf3MXjJZBXl8rhYoTkrov7jmbBaBOPTkqlQAbfOKFNoG+U+boGG6Zmy00l2XRP1nckVMpJ2TxiDVcXJqs78MetC1Ztu4p6bj4VrJCYTmv3ZULSrWleMSWtvYXqY0S23'))
end
end
このようにあらかじめテストケースを網羅的に用意して、履歴を管理することで、思わぬ事故を防ぎ、家庭の平和(と父親の威厳)を守りたいものです。