ローカルプロキシツール mitmproxy
概要
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"}
終わりに
こんな感じでプロキシサーバとして使うことでレスポンスを改変したり、監視したり色々できそうです。
色んな使い方があると思うので、追々調べていきたいです。
Discussion