⬅️

ローカルプロキシツール mitmproxy

2024/05/08に公開

概要

mitmproxyはローカルプロキシツールと呼ばれるツールです。

試すにあたって、テスト用のバックエンドサーバ(APIサーバ)とmitmproxyを使ったプロキシサーバをDockerを使って構築します。

ディレクトリ構成

.
├── backend
│   ├── Dockerfile
│   └── app
│       └── app.py
├── compose.yaml
└── proxy
    └── app
        └── app.py

コンテナ

テスト用のバックエンドアプリを動かすコンテナとmitmproxy用のコンテナを作成します。

  • compose.yaml
services:
  # バックエンドサーバー
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    container_name: backend-test
    tty: true
    volumes:
      - ./backend/app:/app
    environment:
      - HOST=0.0.0.0
      - TZ=Asia/Tokyo
      - LANG=C.UTF-8
    working_dir: /app
    ports:
       - 0.0.0.0:3500:8000
    command: uvicorn app:app --host 0.0.0.0

  # ローカルプロキシ
  proxy:

    image: mitmproxy/mitmproxy:latest
    container_name: proxy-test
    tty: true
    volumes:
      - ./proxy/app:/app
      - ./proxy/.mitmproxy:/home/mitmproxy
    environment:
      - TZ=Asia/Tokyo
    working_dir: /app
    ports:
      - 0.0.0.0:3600:8080
    command: mitmdump -s app.py

  • backend/Dockerfile
    バックエンドアプリはfastapiを使用するので、それだけインストールしたイメージを作成します。
FROM python:slim
RUN pip install fastapi

バックエンドアプリ

authエンドポイントにアクセスすることで、権限情報を取得する、という想定のAPIを作ります。
(もちろん、良くない仕様ですね)
その際に、パスワードをパラメータとして受け取り、パスワードに応じて権限を返すようにFastAPIで作成します。

{”password”: "A"} が渡されると、{”name”: "admin"} が、
{”password”: "B"} が渡されると、{”name”: "user"} が、
それ以外だと{”name”: "none"} が返されるように作ります。
この{”name”: "admin"} を使ってフロント側で、権限管理する、という設定です。

  • backend/app/app.py
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Authentication(BaseModel):
    password: str
    
class Privilege(BaseModel):
    name: str

@app.post("/auth")
async def auth(authentication : Authentication):
    privilege: Privilege = Privilege(name="none")
    
    if authentication.password == "A":
        privilege.name = "admin"
    elif authentication.password == "B":
        privilege.name = "user"
    
    return privilege
    
    

mitmproxy

mitmproxyを使ってプロキシサーバを立てます。
インストール等は公式のコンテナイメージが使えばokです。
CLIでも動かせますが、pythonライブラリも用意されているので、それを使います。

以下のスクリプトを作成します。
やっていることは単純で、http://localhost:3500/auth へのアクセスの場合は固定レスポンス{"name": "admin"}を返します。
それ以外は、そのまま元々のリクエスト先にリクエストを送ります。

  • proxy/app.py
import json

from mitmproxy import http

def request(flow: http.HTTPFlow) -> None:
    # authへのアクセスであれば、リクエストを送らず {name: admin}を返す
    if flow.request.pretty_url == "http://localhost:3500/auth":
        flow.response = http.Response.make(
            200,  # (optional) status code
            json.dumps({"name": "admin"}),  # (optional) content
            {"Content-Type": "text/json"},  # (optional) headers
        )
    
    # それ以外はhttp://backend:8000 へパス
    flow.request.host = "backend"
    flow.request.port = 8000

確認

まずバックエンド自体の動作確認です。
password によってレスポンスが変わるかをチェックします。

curl --location 'http://localhost:3500/auth' \
--header 'Content-Type: application/json' \
--data '{
    "password": "A"
}'
{"name":"admin"}
curl --location 'http://localhost:3500/auth' \
--header 'Content-Type: application/json' \
--data '{
    "password": "B"
}'
{"name":"user"}

ちゃんと動いていそうです。

次に、mitmproxyをプロキシサーバとしてリクエストします。

curl --location 'http://localhost:3500/auth' \
--header 'Content-Type: application/json' \
--data '{
    "password": "B"
}' \
-x http://localhost:3600
{"name": "admin"}

adminが返ってきています。
passwordに何を入れてもadminが返るようになりました。

curl --location 'http://localhost:3500/auth' \
--header 'Content-Type: application/json' \
--data '{
    "password": ""
}' \
-x http://localhost:3600
{"name": "admin"}

終わりに

こんな感じでプロキシサーバとして使うことでレスポンスを改変したり、監視したり色々できそうです。
色んな使い方があると思うので、追々調べていきたいです。
https://docs.mitmproxy.org/stable/addons-examples/

Discussion