Httptap - あらゆるLinuxプログラムのHTTP/HTTPSリクエストを可視化可能
(github.com/monasticacademy)- httptap は
httptap -- <command>の形式で Linux プログラムを実行し、そのプログラムが生成する HTTP/HTTPS リクエストとレスポンスの要約をターミナルに表示するツール - root 権限、デーモン、システム全体への変更、
iptablesルール、ルーティングテーブルの変更なしで動作し、実行ファイルは依存関係のない 静的 Go バイナリ - 現時点では Linux 専用で、network namespace のような Linux 専用システムコールを使用しているため、他の OS への移植は難しいとしている
- HTTPS トラフィックは、実行時にその場で生成した認証局をサブプロセス環境に注入して復号し、生の IP/TCP/UDP パケットを処理する 透過 TCP プロキシ 方式で動作する
- Ubuntu 23.10 以降や、非特権 user namespace をデフォルトで無効化しているディストリビューションでは
sysctl設定が必要になる場合があり、受信接続の待ち受けや/dev/net/tunへのアクセスなどの制約がある
httptap ができること
- httptap は任意の Linux コマンドをラップして実行し、そのコマンドが生成する HTTP/HTTPS リクエストを表示する
- 基本的な使い方は
httptap -- <command> - 例として
curl https://monasticacademy.orgを実行すると、GET https://monasticacademy.org/リクエストと308レスポンスが表示される
- 基本的な使い方は
- Python の
requests.get('https://monasticacademy.org')の例では、リダイレクトをたどりながら 2 つのリクエストを表示する- 最初のリクエストは
https://monasticacademy.org/に対する308 - 次のリクエストは
https://www.monasticacademy.org/に対する200
- 最初のリクエストは
インストールと実行条件
- 事前ビルド済みバイナリは最新リリースの tarball を取得してインストールできる
- すべてのバージョンと CPU アーキテクチャは releases で確認可能
- Go でのインストール方法も提供されている
go install github.com/monasticacademy/httptap@latest
- 実行には通常 root 権限は不要で、デーモンやシステム全体の設定も必要ない
iptablesルールを作成しない- ルーティングテーブルを変更しない
- 同じシステム上の他プロセスに通常は影響しない
- Ubuntu 23.10 以降では次の設定が必要
sudo sysctl -w kernel.apparmor_restrict_unprivileged_unconfined=0sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
- これらの設定は、非特権 user namespace を制限する最近のカーネル機能を無効化する
- 非特権 user namespace をデフォルトで無効化している他のディストリビューションでも必要になる場合がある
- httptap 用の AppArmor プロファイルを同梱してこの必要性をなくす方法を調査中
使用例
curl -s https://buddhismforai.sutra.co -o /dev/nullの実行例では、サーバーが302リダイレクトを返したことがわかるcurl -sLのようにリダイレクトをたどるようにすると、追加のリクエストまで表示される- 最初のリクエストは
302 - 2 番目のリクエストはリダイレクト先 URL に対する
200
- 最初のリクエストは
gcloud compute instances listの例では、Google Cloud CLI が内部的に使用する HTTP エンドポイントを確認できるPOST https://oauth2.googleapis.com/tokenGET https://compute.googleapis.com/compute/v1/.../aggregated/instances?...- 通常の
gcloud出力と httptap のリクエストログが一緒に表示される
kubectl get allの例では、Kubernetes API サーバーへのリクエストを表示する--https 443 6443は 443 と 6443 ポートへの TCP 接続を HTTPS として扱わせる--insecure-skip-tls-verifyは、kubectl が httptap の生成した認証局を使用しないため必要
curl --doh-url https://cloudflare-dns.com/dns-queryの例では、DNS-over-HTTP の流れを確認できる- 最初の 2 リクエストは DNS 問い合わせ
- 次の 2 リクエストは対象サイトへの通常の HTTP リクエスト
--headと--bodyオプションを併用すると、HTTP ヘッダーと生のペイロードを出力する
HAR 出力
--dump-har out.harオプションで HTTP リクエストとレスポンスを HAR ファイル としてダンプできる- 生成された HAR ファイルは複数の HAR ビューアーで可視化できる
- 例として Google HAR Analyzer が挙げられている
- 例の HAR には
https://monasticacademy.orgへの308 Redirectリクエストとhttps://www.monasticacademy.orgへの200 OKリクエストが含まれる
localhost へのアクセス
- httptap 内からホストの localhost ポートへアクセスするには、
localhostの代わりにhost.httptap.localまたは169.254.77.65を使う - Linux の各 network namespace は独自の loopback デバイス
127.0.0.1を持つため、httptap 内の127.0.0.1:1234はホスト側の同じアドレスとポートではない - httptap は
169.254.77.65を127.0.0.1にルーティングするようハードコードして、この問題を回避している
デーモン化されるサブプロセス
- Linux では、元のプロセスが終了した後も残るサブプロセスを作成できる
- これはデーモンや、コマンドラインから起動した GUI アプリで一般的な方式
- こうしたプロセスを httptap 配下で実行すると、デーモン化されたプロセスはそのまま httptap の network namespace 内に残る
--no-exitオプションを使うと、直接起動したサブプロセスが終了した後も httptap がプロキシとロギングを継続する- Visual Studio Code の例では
httptap --no-exit -- code --ignore-certificate-errors .の形式を使う - 終了するには VS Code を閉じた後、
Ctrl+Cで httptap を停止する必要がある
- Visual Studio Code の例では
--no-exitなしで httptap が先に終了すると、network namespace は残っていても TUN デバイスのパケットを読むプロセスがいないため、アプリのネットワーク接続は切断されるsetsid setsid curl http://httpbin.org/getや Python のfork、setsid、forkの例でも、--no-exitがないとCould not resolve hostエラーが発生する
仕組み
httptap -- <command>は<command>を隔離された network namespace で実行し、その場で生成した認証局を注入して HTTPS トラフィックを復号する- Linux の TUN デバイスを作成し、すべてのネットワークトラフィックがそのデバイスを通過するようにサブプロセス環境を構成する
- TUN デバイスに書き込まれたトラフィックは、そのデバイスを作成したプロセスが保持するファイルディスクリプタへ渡される
- root の network namespace を変更するとシステム全体のトラフィックに影響するため、httptap は別の network namespace を作成する
- その namespace には loopback デバイスと TUN デバイスしか存在しない
- サブプロセスはこの namespace 内で実行される
- TUN デバイスから受け取るトラフィックは生の IP パケット
- httptap は IP パケットとその内部の TCP/UDP パケットをパースする
- さらにサブプロセスへ生の IP パケットを書き戻す必要がある
- 独自の TCP/IP 実装は TCP プロトコル全体の多くを省いているが、目的には十分に機能する
- サブプロセスが
www.example.comにリクエストすると、httptap は対象 IP 宛ての TCP SYN を受け取り、SYN+ACK で応答する- 別途 Linux カーネルの通常のソケット API を使って実際の宛先 IP と TCP 接続を確立する
- その後、双方向にデータを中継する
- この構成が 従来型の透過 TCP プロキシ
- HTTPS の復号は認証局の注入によって行う
- 起動時に private key と x509 certificate に対応する認証局を生成する
- サブプロセスにだけ見えるファイルシステムへ証明書を書き込む
- サブプロセスにだけ見える環境変数を設定し、その認証局を信頼リストへ追加する
- httptap はその認証局の private key を保持しているため、サブプロセスが通信しようとしているサーバーであるかのように証明でき、平文の HTTP リクエストを読める
制約
- 現時点では Linux 専用 で、network namespace のような Linux 専用システムコールに依存している
- プロセスは受信ネットワーク接続を待ち受けできない
/dev/net/tunへのアクセス権限が必要- すべての ICMP echo リクエストは、実際のネットワークへ ICMP パケットを送信せず、そのまま echo される
2件のコメント
it was developed at the Monastic Academy in Vermont in the US. We believe that a monastic schedule, and the practice of the Buddhist spiritual path more generally, provide ideal conditions for technological development.
Hacker News のコメント
README の「How it was made」セクションは、ツールそのものと同じくらい興味深かった
100エーカーを少し超える土地で共同生活しながら修行し、朝夕に一緒に念仏と瞑想を行い、毎月およそ1週間は瞑想リトリートを運営・参加しているという。残りの時間は土地の管理、建物の保守、料理、掃除、計画、資金調達、そしてここ数年はソフトウェア開発まで一緒に行っている、という内容
マイクロコードとロジックがナノ秒単位で問題を起こしていた時期に、過労のエンジニアが1人退職する際、端末に辞表としてこんなメモを残したという:「私は Vermont のコミューンへ行き、季節より短い時間単位は扱わない」
良いアイデアなのか、彼らが想像する結果につながるのかは別問題
技術と瞑想の組み合わせには惹かれる。アイデア自体は魅力的だが、実際にやるのはたぶん難しそう。一種の仏教版 Recurseのように見える
httptap は、root 権限なしで実行できるプロセス単位の HTTP トレーサー
httptap <プログラム>のように Linux プログラムを実行すると、標準出力で HTTP/HTTPS リクエストとレスポンスのトレースを見られるhttptap -- python -c "import requests; requests.get('https://monasticacademy.org')"---> GET https://monasticacademy.org/<--- 308 https://monasticacademy.org/ (15 bytes)---> GET https://www.monasticacademy.org/<--- 200 https://www.monasticacademy.org/ (5796 bytes)分離されたネットワーク名前空間内で実行する方式で、独自の TCP/IP スタックとして gVisor を使う。HTTP プロキシではないため、プロキシ設定に依存しない。TLS トラフィックはその場で CA を生成して復号し、
iptablesルールをインストールしたりグローバルなシステム変更を行ったりしないnetstackライブラリとして使っていると理解している特にプロキシ設定が不要なら、さらに有用
https://blog.wireshark.org/2010/02/running-wireshark-as-you/
プロセスを分離されたネットワーク名前空間で実行するというアイデアが天才的
HTTPS の部分はさらに興味深い。プログラムに共通の環境変数[1]を設定して、一時ディレクトリ内の CA バンドルを使わせているように見えるが、
http_proxyの派生変数と同様に、プログラムがその変数を単に無視できてしまうという問題がある/etc/resolv.confにオーバーレイファイルシステムをマウントしているのも見える[2]。httptap が/etc/ca-certificatesディレクトリを一時 CA バンドルとしてマウントすると役に立つのか気になる[1] https://github.com/monasticacademy/httptap/blob/cb92ee3acfb2...
[2] https://github.com/monasticacademy/httptap/blob/cb92ee3acfb2...
httptap が
/etc/resolv.confにオーバーレイをマウントしているのは、DNS 解決についても CA ルートと同じように、任意のプロセスにどの DNS サーバーを使えと確実に伝える方法がないため。ただし/etc/resolv.confはかなり信頼できる選択肢ではある。プロセスをネットワーク名前空間に入れた瞬間、デスクトップ Linux で最も一般的な設定である systemd リゾルバーlocalhost:53にもはやアクセスできなくなるため、DNS 解決を提供する必要がある/etc/ca-certificatesをオーバーレイでマウントするのも役に立つかもしれない。ただ、そのディレクトリ構造を見てみると、ディストリビューションごとにあまりにも一貫性がなくて困惑した。それでも可能ではある。少なくとも一部の TLS 実装が認識する形で、そのディレクトリに証明書を追加する方法について知見があれば聞きたい反例として、TLS で通信し、コンパイル済みコードを強力に難読化したマルウェアを想像できる。固定された CA 証明書の束をバイナリに含め、ファイルシステムを一切開かないプログラムかもしれない。それでも、ほとんどのルート CA 証明書が期限切れになるまで約10年は有効で、安全な TLS 接続を作れる。TLS はすべてユーザー空間で処理され、OpenSSL や他の共通ライブラリを使う保証はないため、特定の OpenSSL 関数にフックする方式にも期待できない。サーバーが自己署名証明書を使い、クライアントが何らかの理由でそれを受け入れるなら、さらに悪くなる
それでも、多少の作業で 99% のケースを安定して処理することは確実に可能。何もないよりはましだ
ここで TUN デバイスを使うのは本当に見事なアイデア。README の “How it was made” セクションも、GitHub README で読んだものの中でも最高レベルだった
Subtrace[1] というものを作っているのだが、入ってくるリクエストと出ていくリクエストの両方を自動的にインターセプトできる。プログラムを起動するインターフェースも同じ形に収束したようで面白い[2]。ただし Subtrace の目的は httptap とは少し異なり、クラウドバックエンドサービスの可観測性/モニタリング寄りなので、双方向のリクエストを強調している。アプローチも違う。Seccomp BPF で
socket、connect、listen、acceptなど約 10 個のシステムコールをインターセプトし、すべての TCP 接続を Subtrace 経由でプロキシする。その後 TCP ストリームから HTTP リクエストをパースし、Chrome DevTools の Network タブを通常の Web アプリのようにブラウザで動作するよう再利用してユーザーに見せているhttptap 配下でプログラムを実行してみて、何か面白い逸話があったのか気になる。どのプログラムが一番頻繁に「おうちに電話」しているのかも気になる
[1] https://github.com/subtrace/subtrace
[2] https://docs.subtrace.dev/quickstart
https://github.com/M66B/NetGuard
信じがたいかもしれないが、Firefox のネットワークタブで同じことをやろうとした半分できかけの試みが、リポジトリの
networktabディレクトリにある。とても素晴らしいプロジェクトなので、もっと学びたいし、さらに話してもよい権限のないユーザーがネットワークトラフィックを分析する別のツールとして、rootless Podman と Pasta を使うこともできる
podman runのオプションに次を追加すればよい--network=pasta:--pcap,myfile.pcapすると Pasta がネットワークトラフィックを PCAP ファイルに記録し、後で分析できる。
tsharkで記録済み PCAP ファイルを分析する簡単な例も書いておいたhttps://github.com/eriksjolund/podman-networking-docs?tab=re...
かなり興味深い。Go アプリケーションで似たような「タップ」機能をするライブラリを書いたことがある: https://github.com/henvic/httpretty
https://asciinema.org/a/297429
任意のプログラムに対してこのようなことをやることも考えたが、実際の実装方法を深く掘り下げたわけではなかった。誰かが作り上げたのを見ると嬉しい
なぜ eBPF を使わないのか気になる。そうすれば、すでに実行中のものも含めて、すべてのプロセスの HTTP リクエストを一括で見られるはずだ。しかも TLS も気にする必要はなく、例えば
write(2)にフックすればよさそうに思えるwrite(2)にフックすることで TLS がどう解決するのか分からない。暗号文を読んで変更することはできるだろうが、プロセスは平文バイトでwrite(2)を呼び出しているわけではないので、実際の HTTP リクエストは読めない。結局ネットワークに載る暗号化されたバイトだけを見ることになり、それは NSA でも見られるhttptap が使っているような CA 証明書トリック が必要になる。もちろん証明書ピンニングのような注意点はあるが、ほとんどの実用的なシナリオでは安定して動作させられる
Subtrace[1] を作りながらこの特定の問題についてばかげたほど考えてきたので、もっと単純またはエレガントなアプローチがあるなら本気で興味がある
[1] https://github.com/subtrace/subtrace
writeシステムコールにフックしても TLS 復号 には役立たないいずれにせよ選択肢が多いほどよい
いいね。nginx 設定のデバッグにすぐ使ってみると思う
今は
curl -vを使って、出力から何が間違っているのかを手作業で眺める必要があるが、このツールならリダイレクトループのようなものがすぐ明らかになりそうだアプリの HTTP/S 呼び出しスタックを素早く大まかに眺める必要があるときに、とても良さそうだ
個人的にはすべてを見るために eBPF を好むが、このユーティリティは eBPF トレースで重要な部分を絞り込むのに役立つかもしれない
良さそう
GitHubプロフィールが https://www.monasticacademy.org/about を指していて、それ自体に特に意見はないが、彼らの修道院式トレーニング・リトリートとGitHubプロジェクトがどうつながっているのか気になった
READMEのいちばん下を見ると、そのつながりが説明されていた: https://github.com/monasticacademy/httptap?tab=readme-ov-fil...
その意味するところは、バーモント州の123エーカーの土地で、人々が比較的伝統的な仏教の僧院構造に従って共同生活をしているということ。ただし、正式に受戒した僧侶ではない。日中はさまざまな技術系・非技術系プロジェクトに一緒に取り組んでいる。上にあるREADMEリンクが良い概要になっている
https://github.com/monasticacademy/httptap?tab=readme-ov-fil...