閉域網の名前と時刻
一言でいうと
エアギャップ環境にも、名前と時間は必要です。社内DNSは、上位サーバーなしで、社内ゾーンを権威として答える必要があり、そうしてこそ、知らない名前でタイムアウトを待たずに済みます。社内の時刻サーバーは、上位がなくても基準になると宣言する必要があり、そうしてこそ、サーバーの時計がバラバラにならずに済みます。
なぜ必要なのか
社内ミラーをpypi.airgap.internalと呼ぶには、その名前を解決するDNSが必要です。/etc/hostsをサーバーごとに直すやり方は、サーバーが10台を超えるだけで、ずれてきます。ところが、社内DNSをいつもどおりに立てると、知らない名前を上位に転送し、エアギャップ環境の上位は答えないので、クライアントは、クエリのたびにタイムアウトを待ちます。時間は、もっと静かに壊れます。インターネットの時刻サーバーに届かないサーバーは、自分の時計のとおりに進み、1か月もすれば、サーバーごとに数十秒から分の単位で開きます。すると、証明書が「まだ有効ではない」として拒否され、複数のサーバーのログを時刻で突き合わせられなくなります。
どう動くのか
dnsmasqで社内ゾーン: dnsmasqのマニュアルによると、listen-addressとbind-interfacesは、指定したアドレスにだけソケットを開きます(Ubuntuのsystemd-resolvedのスタブが、127.0.0.53・127.0.0.54の53を使うので、これがないとぶつかります)。no-resolvは、resolv.confから上位を読まないようにし、local=/airgap.internal/は、そのゾーンのクエリを外部に転送せず、/etc/hosts・DHCPだけで答えさせます(--localは、--serverの別名です)。domain=は、短い名前に付けるゾーンを、addn-hosts=は、/etc/hosts以外に読む名前のファイルを決めます。address=/도메인/주소は(プレースホルダーはドメインとアドレスです)、下位の名前まで1つのアドレスで答えますが、社内ゾーン全体を1つのアドレスにまとめるのでなければ、名前のファイルのほうが管理しやすいです。
再起動なしの反映: dnsmasqは、SIGHUPを受け取ると、キャッシュを空にして、/etc/hostsとaddn-hostsのファイルを読み直します。設定ファイル自体は、読み直しません。そのため、頻繁に変わる名前のファイルは、設定と分けておくほうが、運用が楽です。
クライアント側: ip netns execは、/etc/netns/<이름>/resolv.confがあれば(プレースホルダーは名前です)、そのネームスペースの中で、/etc/resolv.confの場所にはめ込んでくれます。実際のサーバーなら、/etc/resolv.conf(またはsystemd-resolvedのDNS設定)に、社内DNSとsearchドメインを書くことに当たります。
chronyで社内の時間: chronyのドキュメントは、デフォルトでは、どのクライアントも受け付けず、純粋なNTPクライアントとしてだけ動作すると書いています。allow <대역>があって初めて(プレースホルダーはアドレス範囲です)、時間を出します。local stratum N(1–15、デフォルトは10)は、上位との同期がなくても、自分の時計を、そのストラタム(stratum)の基準として出すようにします。複数台が互いを基準にするようにするには、orphanのオプションを使います。クライアントで時計をいじらずに確認だけをするには、chronyd -Qが、差だけを出力して終わり(-qは、一度合わせて終わりです)、サーバーでは、chronyc tracking・chronyc clientsで、状態と受け取ったクエリを見ます。
基準の時計が1つであることの限界: local stratumは、「この時計が正しい」という宣言にすぎず、正しくしてはくれません。エアギャップ環境全体が、1つの時計に従うので、互いにずれることはありませんが、その時計が間違っていれば、全員が一緒に間違います。現場では、GPSや標準時の受信機のような、ハードウェアの基準を置くか、持ち込み手続きのたびに、人が確認した時刻に合わせる手続きを置きます。
現場での姿
このラボのVMで測ってみました。no-resolvだけを置いて、local=を外すと、名前のファイルにないnope.airgap.internalも、外部の名前ubuntu.comも、status: REFUSEDでした。転送する上位がないので、「ここでは答えない」という意味です。local=/airgap.internal/を入れると、同じ社内の名前が、NXDOMAIN(権威ある「ない」)に変わりました。REFUSEDを受け取ったクライアントは、次のネームサーバーを探し、NXDOMAINを受け取ったクライアントは、そこで止まります。社内ゾーンだけは、権威ある答えを出す必要がある理由です。chronyは、local stratum 8を入れて再起動すると、chronyc trackingが、Reference ID : 7F7F0101 ()・Stratum : 8を示し、systemctl reload dnsmasqは、ユニットに書かれたとおり、kill -HUP $MAINPIDでした。
このラボのVMは、最初から外部のNTP(UDP 123)に届きません。公開の80/443とDNSだけが開いています。そのため、インストール直後のchronyは、上位サーバーを1つも同期できない状態で、起動しています。エアギャップ環境のサーバーで、よく見るまさにその姿です。ここに、外部への送信までふさぐと、名前解決も切れます。
次のラボですること
VMの外部への送信をふさいで、本物のエアギャップ環境を作り、dnsmasqを、上位のないairgap.internalの権威サーバーとして立てます。clientネームスペースが、そのDNSと検索ドメインを使うようにして、再起動なしで名前を追加します。chronyを社内の時刻サーバーに変えて、clientからクエリし、引き継ぎ用の点検スクリプトで終えます。
参考ドキュメント: dnsmasq(8)・resolved.conf(5)・ip-netns(8)・chrony.conf(5)・chronyd(8)・chronyc(1)