Closed7

100日チャレンジ day50 (自作プロキシサーバとMITMによるキャッシュ)

riddle_tecriddle_tec

昨日
https://zenn.dev/gin_nazo/scraps/370bf54c6769e8


https://blog.framinal.life/entry/2025/04/14/154104

100日チャレンジに感化されたので、アレンジして自分でもやってみます。

やりたいこと

  • 世の中のさまざまなドメインの簡易実装をつくり、バックエンドの実装に慣れる(dbスキーマ設計や、関数の分割、使いやすいインターフェイスの切り方に慣れる
  • 設計力(これはシステムのオーバービューを先に自分で作ってaiに依頼できるようにする
  • 生成aiをつかったバイブコーティングになれる
  • 実際にやったことはzennのスクラップにまとめ、成果はzennのブログにまとめる(アプリ自体の公開は必須ではないかコードはgithubにおく)

できたもの

https://github.com/lirlia/100day_challenge_backend/tree/main/day50_go_proxy

riddle_tecriddle_tec

Go言語での高機能HTTP/HTTPSフォワードプロキシ開発、素晴らしいですね!Goはネットワークプログラミングに適しており、パフォーマンスも期待できるため、非常に良い選択だと思います。

Day50のプロジェクトとして、以下の仕様で進めるのはいかがでしょうか。

Day50 プロジェクト: 高機能 HTTP/HTTPS フォワードプロキシ (Go言語)

目標: 基本的なフォワードプロキシ機能に加え、1つまたは複数の高度な機能を備えたプロキシサーバーをGo言語で実装する。

基本機能:

  1. HTTPリクエスト転送:
    • クライアントからのHTTPリクエストを受け取り、指定されたターゲットサーバーに転送する。
    • ターゲットサーバーからのレスポンスをクライアントに返す。
  2. HTTPS (CONNECTメソッド) 対応:
    • クライアントからのCONNECTリクエストを受け取り、ターゲットサーバーとの間にTCPトンネルを確立する。
    • その後はクライアントとターゲットサーバー間の暗号化された通信を透過的に中継する。

中核となる挑戦的機能 (以下から1つ以上選択、または組み合わせ):

  • A. 動的なリクエスト/レスポンス改変:
    • A-1. ヘッダー操作: 設定ファイルやAPI経由で指定されたルールに基づき、リクエスト/レスポンスのHTTPヘッダーを追加・削除・変更する。
      • 例: 特定のドメインへのリクエストに X-Forwarded-For ヘッダーを付与する。特定のレスポンスから Server ヘッダーを削除する。
    • A-2. ボディ書き換え (HTTPのみ): (HTTPSのMITMは今回はスコープ外とします)
      • HTTPレスポンスボディの内容をリアルタイムで書き換える。設定ファイルで正規表現と置換文字列を指定できるようにする。
      • 例: HTML内の特定の文字列を別の文字列に置換する。
  • B. インテリジェント・キャッシング:
    • HTTPレスポンスをSQLiteにキャッシュする。
    • Cache-Control, Expires, ETag, Last-Modified ヘッダーを考慮してキャッシュの鮮度を管理する。
    • キャッシュヒット時には効率的にレスポンスを返す。
    • SQLiteのテーブル設計とクエリを工夫する。
  • C. 高度なアクセス制御とフィルタリング:
    • 設定ファイル (例: YAMLやJSON) で定義されたルールに基づいてアクセスを制御する。
    • ルールは送信元IP、リクエスト先ドメイン/IP、ポート、HTTPメソッド、User-Agentなどで指定可能にする。
    • 許可/拒否のポリシーを設定できるようにする。
  • D. 詳細なトラフィックロギング:
    • 通過するHTTP/HTTPSリクエストのメタデータ (タイムスタンプ, 送信元IP, リクエストメソッド, URL, ステータスコード, User-Agent, レスポンスサイズなど) をSQLiteに構造化して記録する。
    • (オプション) ログを閲覧・検索するためのシンプルなWeb UIをGoのnet/httpパッケージで提供する。

技術スタック:

  • 言語: Go
  • 主要パッケージ:
    • net/http (HTTPサーバー、クライアント、プロキシ機能の中核)
    • net (TCP接続処理)
    • database/sql, github.com/mattn/go-sqlite3 (SQLiteへのアクセス)
    • encoding/json or gopkg.in/yaml.v3 (設定ファイルのパース)
    • (UIを作る場合) html/template
  • 設定管理: JSONまたはYAMLファイル
  • データベース: SQLite (proxy_data.dbのようなファイル名)

ディレクトリ構成案 (ワークスペースルートからの相対):

/
├── day50_go_proxy/
│   ├── main.go                     # プロキシサーバーのエントリーポイント
│   ├── proxy/                      # プロキシのコアロジック
│   │   ├── http_handler.go         # HTTPリクエスト処理
│   │   ├── https_handler.go        # HTTPS (CONNECT) 処理
│   │   ├── modifier.go             # (もしAを実装する場合) リクエスト/レスポンス改変
│   │   ├── cache.go                # (もしBを実装する場合) キャッシュロジック
│   │   ├── acl.go                  # (もしCを実装する場合) アクセス制御ロジック
│   │   └── logger.go               # (もしDを実装する場合) ロギングロジック
│   ├── config/
│   │   ├── config.go               # 設定ファイルの読み込み・管理
│   │   └── config.example.yml      # 設定ファイル例
│   ├── db/
│   │   ├── schema.sql              # (もしBまたはDを実装する場合) DBスキーマ
│   │   └── database.go             # (もしBまたはDを実装する場合) DB初期化・操作
│   ├── ui/                         # (もしWeb UIを実装する場合)
│   │   ├── static/                 # CSS, JSファイル
│   │   └── templates/              # HTMLテンプレート
│   ├── go.mod
│   ├── go.sum
│   └── README.md
├── template/
│   └── ... (既存のtemplateディレクトリ内容)
└── ... (他のdayXXディレクトリ)

テスト方法:

  1. プロキシサーバーを指定のポート (例: localhost:8080) で起動する。
  2. ターミナルから curl コマンドでプロキシ経由のアクセスを試す。
    • HTTP: curl -x http://localhost:8080 http://example.com
    • HTTPS: curl -x http://localhost:8080 -p https://google.com
  3. 選択した挑戦的機能が正しく動作することを確認する。
    • ヘッダー改変なら、curl -v でヘッダーを確認。
    • キャッシュなら、2回目以降のアクセスでレスポンスが速いか、またはログでキャッシュヒットを確認。
    • アクセス制御なら、拒否されるべきリクエストが拒否されるか確認。
    • ロギングなら、SQLiteのDBファイルに記録が残っているか確認。

実装ステップ案:

  1. プロジェクト初期化:
    • day50_go_proxy ディレクトリ作成 (templateからのコピーは不要、Goプロジェクトとして新規作成)。
    • go mod init github.com/lirlia/100day_challenge_backend/day50_go_proxy を実行。
    • README.md にプロジェクト概要を記述。
    • 基本的なHTTPフォワードプロキシ機能を実装 (http_handler.go)。
    • curl -x http://localhost:8080 http://example.com で動作確認。
  2. HTTPS (CONNECT) 対応:
    • CONNECT メソッドを処理し、TCPトンネルを確立する機能を実装 (https_handler.go)。
    • curl -x http://localhost:8080 -p https://google.com で動作確認。
  3. 挑戦的機能の実装 (1つ目):
    • 上記 A, B, C, D から1つ選択し、設計・実装を行う。
    • 関連する設定ファイル (config.go, config.example.yml) やDB周り (db/) も整備する。
    • 単体テストや curl で機能確認。
  4. (オプション) 挑戦的機能の実装 (2つ目以降):
    • さらに機能を追加したい場合は、同様に実装とテストを行う。
  5. (オプション) Web UI 実装:
    • ロギング機能Dを実装した場合など、必要に応じてシンプルなWeb UIを実装する。
  6. 最終テストとドキュメント整備:
    • 全体の動作確認。
    • README.md に使い方、設定方法、ビルド方法などを追記。
    • PROGRESS.md の作成 (手動で作成し、進捗を記述)。

デザインについて:
今回はGo言語でのバックエンド実装がメインなので、UIはもし作成するとしても非常にシンプルなもので良いでしょう。Tailwind CSS 等は必須ではありません。

まず、どの挑戦的機能に最も興味がありますか?
それによって、最初の詳細設計や実装の優先順位が変わってきます。
例えば、「D. 詳細なトラフィックロギング」と「C. 高度なアクセス制御」を組み合わせ、管理画面でログを見ながらフィルタリングルールを調整できる、といった形も面白いかもしれません。

ご意見をお聞かせください!了解しました。Go言語で高機能HTTP/HTTPSフォワードプロキシを開発しましょう。
まずはプロジェクトの初期セットアップと、基本的なHTTP/HTTPSフォワードプロキシ機能の実装から始めます。

作業計画:

  1. プロジェクト初期化:
    • day50_go_proxy ディレクトリを作成します。
    • go mod init github.com/lirlia/100day_challenge_backend/day50_go_proxy を実行します。
    • README.mdPROGRESS.md を作成します。
    • .gitignore を作成します。
  2. 設定ファイルの準備:
    • プロキシサーバーの待ち受けポートなどを設定ファイル (config/config.yml) で管理できるようにします。
    • 設定読み込み用のGoの構造体と関数 (config/config.go) を作成します。
  3. 基本的なHTTPフォワードプロキシ機能の実装:
    • main.go にHTTPサーバーを起動する処理を記述します。
    • proxy/http_handler.go にHTTPリクエストをターゲットサーバーに転送するロジックを実装します。
  4. HTTPS (CONNECTメソッド) 対応:
    • proxy/https_handler.goCONNECTメソッドを処理し、TCPトンネルを確立するロジックを実装します。
  5. テスト:
    • curl -x http://localhost:[設定ポート] http://example.com
    • curl -x http://localhost:[設定ポート] -p https://google.com
      で動作確認を行います。
riddle_tecriddle_tec

HTTPSもプロキシしてキャッシュしてほしいので MITM でいい感じにやる

riddle_tecriddle_tec

学び

  • http はシンプルにリクエストを別に作ってその結果をコピーしてるだけ
  • https は http.Hijacker ってのを使って CONNECT 時の tcp connection を取得し、以後はそれを使い回してプロキシを行う

ネスペや旧セキスペで学んだり、実際にHTTPS Proxyを使っていたけど実際に実装してみるとこうなってるのか〜という驚きがあった。

このスクラップは3ヶ月前にクローズされました