外向き通信がないことを資料で示す
目標
製品の構成ファイルと3種類の記録だけで、「認可されていない外部通信がない」ことを判定し、監査にそのまま出せる要約を作ります。確認した範囲と確認できなかったことが、同じ文書に入っていなければなりません。
なぜ重要なのか
ネットワーク分離環境への納品では、「外には出ません」は文として受け入れられません。製品には、アップデート確認、ライセンス検査、リモート診断、利用統計、時刻同期が入っていて、大半はデフォルトがオンです。
このラボのPodには、カーネル権限が一切なく、tcpdumpもiptablesも動作しません。パケットを捕まえて見せる道が、そもそもないという意味です。実際の現場でも、その場所は、ネットワークチームが出すファイアウォールポリシーの写しと機器ログが埋め、開発者が出せる資料は、構成と記録です。そのため、このラボは、設定と記録を根拠に判定します。これが、このラボの仮定です。
数字を当てることよりも、3つを区別することが重要です。コメントで隠してある項目とデフォルトでオンになっている項目、遮断された試行と出ていった通信、そして、名前で突き合わせられず、まだわからないこと。
ステップ
python3 /root/egress/mkdata.pyで、判定の土台になるデータを作ります。構成3つ、許可リスト、プロキシ・DNS・構成変更の記録が出ます。- 構成から外を指すアドレスをすべて抜き出して、/root/egress/endpoints.tsvに
host port scheme state sourceで書きます。 - 許可リストと突き合わせて、/root/egress/classify.tsvに
host port state verdictで、3つに分けます。 - プロキシ記録から、不許可の宛先に向かった試行を数えて、/root/egress/attempts.tsvに
host port attempts ok auth blockedで書きます。 - 問い合わせ記録にだけある宛先を/root/egress/dns-only.tsvに
host queries nxdomainで書き、このPodの名前解決を直接観察して、/root/egress/resolver.txtに残します。 - 3つの記録を時刻でつないで、/root/egress/timeline.tsvに
host change_id first_dns first_proxyで書きます。 - 原本は置いたまま、/root/egress/conf-after/で構成をオフにしたあと、
python3 /root/egress/collect.pyで記録を再び抽出して、/root/egress/after.txtにオフにする前後の数字を書きます。 - /root/egress/egress-report.jsonと/root/egress/egress-report.txtに、監査提出用の要約を出します。
参考
- 素材は、すべて
/root/egressの下に作られます。構成はconf/agent.conf、conf/collector.conf、conf/plugins.jsonの3つで、許可リストはallowlist.txt、記録はproxy.log、dns.log、change.logです。 - ツールが2つ一緒に作られます。
mkdata.pyはデータを作り、collect.pyはconf-after/の構成から記録を再び抽出して、proxy-after.logとdns-after.logを残します。どちらも乱数を使わないので、何回回しても同じ結果が出ます。 - 表は、タブか空白で欄を区切り、ヘッダー行は置きません。空の欄は、ハイフン1つで書きます。
- iniの構成は、セクション単位で
urlとenabledを組にします。セクションにenabledがなければ、オンとみなします(製品説明書4.3節、このラボの仮定)。 jqでJSONを読む場合は、.enabled // trueを使わないでください。代替演算子は、値がちょうどfalseのときも右を使います。has("enabled")で、キーの有無を先に見ます。- 応答コード200は実際に出たもの、407はプロキシが認証を要求したもの、403はプロキシが遮断したものです。3つをひとまとめにしません。
- よくある間違いは、コメントで隠してある宛先を一覧から外すこと、プロキシ記録だけを見てUDP通信を見逃すこと、アドレスで書かれた宛先を許可か不許可のどちらか一方に寄せること、記録を消して0件にすることです。
判定の土台になるデータをネットワークの中で作る
/root/egress/mkdata.pyを保存して実行し、再収集ツール/root/egress/collect.pyも一緒に受け取っておいてください。
エアギャップ環境なので、データを外から受け取ってくることはできません。生成スクリプトは、このステップの「正解」にそのまま入っているので、保存して実行してください。乱数を使わないスクリプトをそのまま使ってはじめて、誰が回しても同じデータが出て、互いの判断を突き合わせられます。ファイルを手で直すと、後のステップの数字がすべてずれます。
構成から外のアドレスを漏れなく抜き出す
構成ファイル3つから、外を指すアドレスをすべて探して、/root/egress/endpoints.tsvにhost port scheme state sourceで書いてください。
セクションの外にあるlistenは、受ける側の場所なので、外のアドレスではありません。コメントで隠してある宛先は、オフとして表に載せ、外しません。plugins.jsonでは、enabledキーがないことと、値がfalseであることは違います。sourceは、ファイル名だけを書きます。
許可リストと突き合わせて3つに分ける
/root/egress/classify.tsvにhost port state verdictで書いてください。verdictは、allowed、denied、unknownのいずれかです。
許可リストは、名前とポートで書かれています。構成にアドレスが直接書かれた宛先は、そのリストと突き合わせる方法がないので、許可でも不許可でもありません。プライベート帯域なので外に出た可能性が低いということと、確認したということは、別の話です。
プロキシ記録から実際の試行を応答別に数える
/root/egress/attempts.tsvに、不許可の宛先に向かった試行をhost port attempts ok auth blockedで書いてください。
okは200、authは407、blockedは403の件数で、attemptsは3つの合計です。許可リストにある宛先は、この表に入れません。構成にはないのに、記録にだけ出てくる宛先があれば、それも不許可です。
接続はなく、名前だけを問い合わせた宛先を探す
/root/egress/dns-only.tsvにhost queries nxdomainを書き、/root/egress/resolver.txtに、このPodの名前解決を直接観察した結果を書いてください。
プロキシを経由しない通信は、接続記録に何も残しません。問い合わせ記録にはあって、接続記録にはない名前が、その痕跡です。承認された宛先は除いてください。resolver.txtは、nameserver、query、statusの3行で、statusはdigが出した文字列をそのまま書きます。
3つの記録を時刻でつなぎ、いつからかを指し示す
/root/egress/timeline.tsvに、不許可の宛先ごとにhost change_id first_dns first_proxyを書いてください。ない欄は、ハイフン1つにします。
対象は、ステップ4とステップ5で出てきた宛先を合わせたものです。change.logのitemの値が、構成のどのセクションを指すかを見ると、変更と宛先がつながります。組にできる変更がなければ、ハイフンです。構成管理の外で生じた宛先を明らかにすることが、この表の役割です。
オフにして再収集し、消えたことを示す
/root/egress/conf-after/で不許可の宛先をオフにし、python3 /root/egress/collect.pyで記録を再び抽出したあと、/root/egress/after.txtに4つの数字を書いてください。
元の構成は証拠なので、複製のほうで直します。項目を消さずに、オフにだけしてください。どうしても必要な機能は、オフにする代わりに、許可リストの中の宛先に切り替えます。記録を手で消して0件にすることはできません。採点ツールは、承認された宛先がそのまま残っているかも見ます。
監査提出用の要約を出す
/root/egress/egress-report.jsonと/root/egress/egress-report.txtに、確認した範囲と確認できなかったことを一緒に書いた要約を出してください。
数字はすべて、前のステップの表から出ます。unverified_hostsには、名前で突き合わせられなかった宛先だけを入れます。direct_egress_checkedとpacket_capture_checkedは、このデータでは確認できなかったことなので、正直に書きます。人が読む表には、記録にだけ出てきた宛先まで、1行ずつ入っていなければなりません。