iptablesルールを可視化するツールを作った話
きっかけ 暇つぶしとインフラの勉強を兼ねて、何か作りたいと思っていた。最初に思いついたのは「iptablesのルールを抽出して、何が許可されてて何が拒否されてるのかをUI的に見せるアプリ」だった。 思いつきはよかったが、実際に手を動かしてみると想像より何段階も難しかった(動いたのはほぼClaude)。この記事はその過程の記録。 言語化しづらい分野だったが、ふんわりとした説明でも個人的にはかなり納得のいくものを生成してくれたので満足ではある。 作ったもの iptables-rel GitHub - wasuken/iptables-rel · GitHub 既存ツールは無かったのか 作り始める前に軽く調べた。同じ発想のツールは既にいくつかある。 gressgraph は iptables -L -vx の出力をグラフ化するツールで、開発者自身が小規模なルールセットでしか検証していないと明言している。iptable_vis はAWKスクリプトでiptablesの出力を読み、blockdiagでフローチャートを生成する。星が700超えていて、それなりに使われているようだった。 ただ、これらは基本的に「チェーンのジャンプ構造」をそのままグラフにする方向性で、IPアドレスやインターフェースの意味を解釈して「ホストAからホストBへの通信」という粒度に変換するような作り方はしていなかった。個人の環境に合わせて意味づけをするなら、これは自分で作った方が早いと判断した。 最初の設計: グラフ、失敗 D3.jsのforce-directedグラフで、ノードをホスト、エッジをルールとして表示するプロトタイプを最初に作った。動くには動いたが、実際に見てみると「関連はわかるけど、間のルールがわからない」という致命的な問題があった。エッジの上に小さくラベルを乗せるだけでは、プロトコルやポート、チェーンの情報を表現しきれない。 次に表形式(ホストごとにルールを一覧表示)を試したが、今度は逆に「どこと繋がってるか」の直感性が失われた。 最終的に落ち着いたのは、グラフとテーブルのハイブリッドだった。ノード間の関係は力学モデルのグラフで表示し、エッジをクリックすると、その2ノード間の全ルールが表形式でサイドバーに展開される。これで「全体像」と「詳細」を両立できた。 インターフェースでつなげる 実データを流し込んでみて気づいたのが、FORWARDチェーンのルールをホスト同士のエッジとしてマッピングすると意味が壊れるケースがあることだった。-i eth0 -o wg0 のようなルールは、2つのホスト間の通信を表しているのではなく、1台のマシンの中で「あるインターフェースから別のインターフェースへ中継してよいか」を表している。 ここでモデルを作り直した。ホストをそのまま1つのノードにするのではなく、ホスト:eth0 ホスト:wg0 のようにインターフェース単位でノードを分割し、ルールでない「そもそも物理的/論理的に繋がっている」という関係は、クリックできない点線のtopologyエッジとして別扱いにした。実線はルール、点線は構造。この区別を入れてから、ようやく「何が起きているか」が読み取れるグラフになった。 パーサ: 再帰的チェーン解決 iptables-save の出力は、INPUT や FORWARD のようなビルトインチェーンから、ユーザー定義のチェーンへ -j でジャンプしていく構造になっている。UFWの設定などはこのジャンプが何段にもネストしていて、素直に1行ずつ読むだけでは意味のあるグラフにならない。 実装したパーサは、ビルトインチェーンを起点に、ACCEPT / DROP / REJECT / DNAT / MASQUERADE のような終端アクションに到達するまで再帰的にチェーンを辿る。テストケースとして、意図的に未定義のチェーンへジャンプするルールや、循環参照(AがBを呼び、BがAを呼ぶ)を仕込んで、無限ループせずに unresolved として記録されることを確認した。完全なNetfilterのセマンティクスを再現しているわけではなく、あくまでヒューリスティックな近似であることは、公開ページのフッターにも明記している。 エリア表示: 凸包から矩形へ 同じサブネットに属するノードを視覚的にグルーピングしたくなった。最初は d3.polygonHull() を使って、ノード群を囲む凸包を薄い背景として描画する方式を試した。技術的には動いたが、見た目が思っていたより有機的すぎて、期待していた「かっちりした枠」の印象にならなかった。 矩形(bounding box)に切り替えた。同じグループのノード座標からmin/maxを取り、パディングを足して角丸の<rect>を描画するだけなので、実装としては凸包より単純になった。CIDRが入れ子になっているケース(/24の中に/28があるなど)は、prefix長が短いほど背面・大きいパディング・薄い色、長いほど前面・小さいパディング・濃い色にすることで、二重の枠として表現した。 インターフェースのノードについては、エリアの境界をノードの座標だけで決めると、繋がっているエッジが箱の外からいきなり生えているように見えて不自然だった。接続エッジの中間点までを座標の計算対象に含めることで、エッジが箱の内側から伸びているように見せている。 公開、しかし・・・ Cloudflare Pagesにデプロイした。ビルドコマンドとアウトプットディレクトリを指定するだけで、静的サイトとして問題なく配信できた。バックエンドは無く、ルールのパースは全部ブラウザ内で完結する構成にした。 一個ドメイン持ってたので、サブドメイン割り当てて公開し、最初デプロイしたとき問題なく表示された。 ところが微修正を加えてPushし、自動デプロイが走った直後、突然サイトにアクセスできなくなった。Cloudflare Pagesが自動で割り当てる *.pages.dev のドメインからは問題なくアクセスできる一方、独自サブドメインだけが ERR_NAME_NOT_RESOLVED で弾かれる状態だった。 ...