Skip to content

Commit 15818eb

Browse files
committed
TCP Proxy
1 parent de0e074 commit 15818eb

File tree

3 files changed

+235
-0
lines changed

3 files changed

+235
-0
lines changed
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
---
2+
title: "TCP Proxyを作って面倒なProxy設定を一掃する"
3+
date: 2025/09/04 00:00:00
4+
postid: a
5+
tag:
6+
- Network
7+
- プロキシ
8+
- Linux
9+
- WSL
10+
- 環境構築
11+
category:
12+
- Infrastructure
13+
thumbnail: /images/2025/20250904a/thumbnail.jpg
14+
author: 神崎 林太郎
15+
lede: "セキュリティ上の理由などで、どうしてもProxyを使わざるを得ない環境下の時に、非常にハマりがちな設定のひとつとしてProxyの設定があります。これには理由があり、ProxyというのはOSレベルで適用するものではなく、利用するツール毎に設定を行う必要があり、各ツールのドキュメンテーションを読んで適切な設定を行う必要があります。"
16+
---
17+
18+
<img src="/images/2025/20250904a/top.jpg" alt="" width="1024" height="1024">
19+
20+
# はじめに
21+
22+
Technology Innovation Groupの神崎です。
23+
24+
セキュリティ上の理由などで、どうしてもProxyを使わざるを得ない環境下の時に、非常にハマりがちな設定のひとつとしてProxyの設定があります。これには理由があり、ProxyというのはOSレベルで適用するものではなく、利用するツール毎に設定を行う必要があり、各ツールのドキュメンテーションを読んで適切な設定を行う必要があります。そのため利用したいツールすべてについて、適切な設定をしないと動かないという特徴があります。「新しく使い始めたCLIツールがプロキシに対応していなかった」「Dockerのpullがプロキシで失敗する」などあるあるではないかと思います。
25+
26+
また、Proxy機能はツール目線でいうとよく使われる機能ではないため、特に「枯れていない」ツールを利用する際は、そもそもドキュメンテーションがなかったり、Proxyの認証に関してバグがあったりなど、Proxyの設定のトラブルシューティングに非常に時間がかかることがあります(場合によってはツールのソースコードを追うようなことまで必要になります)。
27+
28+
これらは非常に時間がかかり、生産性を低下させることが多く、特にツールの選定やちょっと使ってみたい場合はそれ自体を諦める遠因となることさえあります。そのため、何かツールごとではなく、OSレベルで設定する方法があれば、快適に過ごせるのではないか?と思っていました。
29+
30+
# TL;DR
31+
32+
解決策として、下記の構成を提案します。
33+
34+
- [moproxy](https://github.com/sorz/moproxy)を利用し、TCPパケットをHTTP CONNECTリクエストにくるんで、Proxyへの中継を行う
35+
- Linuxカーネルのパケットフィルタリング機能 [^1] であるnftablesを利用して、宛先ポートが80(HTTP)ないし443(HTTPS)へのパケットをmoproxyに転送する
36+
- Well known portsではないポートへの通信は一旦ないものと想定する(必要に応じて個別に設定する)
37+
- 各ツールでは、Proxyの設定は全く行わず、通常のProxyなしの設定を行い利用する
38+
39+
本記事ではその構成でなぜ動くのかを、HTTPのCONNECT仕様を深掘って解説した上で、具体的な設定例を示します。
40+
41+
※今回の記事ではWindows上のアプリケーションについては対象としていません。特にVPNクライアントが入っているケースを踏まえるとパケットがループしないようにうまく工夫をする必要がありますが、私自身がWindowsに明るくないので、Linuxのみの記述になります。すみません(多くのアプリケーションではインターネットオプションの設定を読みに行く形になるため、そこまで気にする機会はないかなと思います。~~だが、`Invoke-WebRequest`、おまえはゆるさん…~~)。
42+
43+
[^1]: NATの作成や、FWのルール設定などのバックエンドとして機能する
44+
45+
# そもそもProxyとの通信は何をやっているのか
46+
47+
私が一から解説する必要はないと思いますので、詳しくはこのサイト([作って学ぶ 「Https Man in The Middle Proxy」 in Go](https://yuroyoro.hatenablog.com/entry/2018/02/16/193816))を確認して貰いたいのですが、大きく分けると以下の2つのことをやっています。(HTTPSを前提とした場合)
48+
49+
1. HTTP CONNECTでProxyにHTTPS通信を始めたい旨を通知する(この情報を元にProxyは②のパケットを転送する)
50+
2. ProxyにTCPパケットそのものを転送する
51+
52+
```mermaid
53+
sequenceDiagram
54+
    participant Browser as ブラウザ/ツールなど
55+
    participant Proxy as Proxy
56+
    participant Web as 接続先
57+
58+
Note over Browser,Proxy: ①Proxyに通信を始めたい旨を通知する
59+
    Browser->>+Proxy: HTTP CONNECT
60+
    activate Browser
61+
    Proxy->>Proxy: 認証
62+
    Proxy->>-Browser: 200 or 407 RESPONSE
63+
64+
Note over Browser,Proxy: ②パケット転送を開始する
65+
    Browser->>+Proxy: TCPパケットの送信
66+
    Proxy->>+Web: TCPパケットの転送
67+
    Web->>Web: 返却
68+
    Web->>-Proxy:
69+
    opt
70+
        Proxy->>Proxy: 復号化/再暗号化
71+
    end
72+
    Proxy->>-Browser:
73+
    deactivate Browser
74+
```
75+
76+
# Proxyを「透過」させるのに必要な実装
77+
78+
前述した仕組みを踏まえると、HTTPS通信にてProxy処理を「透過的」に行うには、ブラウザ/ツールなどから何かしらの手段でTCPのパケットを横取りし、パケットを送信する前にProxyにCONNECTリクエストを送った上で、横取りしたTCPパケットを再送してあげればよいことになります。
79+
(また、HTTP通信の場合でも、相手先のProxyの実装にもよりますが、同様にTCPパケットを転送することで透過的に扱うことができます)
80+
81+
Linux上では、だいたい以下のような設定を行うことで上記を実現できます。
82+
83+
```mermaid
84+
flowchart TB
85+
Web[接続先]
86+
Browser[ブラウザ/ツールなど] --> nftables
87+
subgraph nftables
88+
direction TB
89+
decision1{内部IPへの通信か?}
90+
decision1 -->|No| decision2
91+
decision2{port 443 or 80への通信か?}
92+
end
93+
decision1 -->|Yes| Web
94+
decision2 -->|Yes| moproxy
95+
decision2 -->|No| Web
96+
moproxy --> Web
97+
```
98+
99+
## moproxyの設定例
100+
101+
moproxyの設定例を示します。WSL2上のubuntuを前提に書いているため適宜読み替えて貰えればと思います。
102+
103+
今回はProxyがTLSの復号・再暗号化を行うので、必要な中間証明書をインストールします。
104+
※信頼できる証明書をインストールするように注意してください!
105+
106+
```sh
107+
sudo install -m 644 証明書パス.crt /usr/local/share/ca-certificates/internal_ca.crt
108+
sudo update-ca-certificates
109+
```
110+
111+
moproxyをインストールします。
112+
113+
```sh
114+
TMPDIR=$(mktemp -d)
115+
curl -fL -o "${TMPDIR}/moproxy.deb" 'https://github.com/sorz/moproxy/releases/download/v0.5.1/moproxy_0.5.1-1_amd64.deb'
116+
sudo dpkg -i "${TMPDIR}/moproxy.deb"
117+
```
118+
119+
下記ファイルを環境によって読み替えながら設置します。
120+
121+
```toml
122+
# /etc/systemd/system/moproxy.service.d/override.conf
123+
[Service]
124+
EnvironmentFile=
125+
EnvironmentFile=/etc/default/moproxy
126+
ExecStart=
127+
ExecStart=/usr/bin/moproxy --host $HOST --port $PORT --list /etc/moproxy/proxy.ini
128+
```
129+
130+
```toml
131+
# /etc/moproxy/proxy.ini; chmod 600 推奨
132+
[server-1]
133+
address=proxy.fqdn
134+
protocol=http
135+
http username = username
136+
http password = passw0rd
137+
```
138+
139+
```sh
140+
# /etc/default/moproxy
141+
HOST="::"
142+
PORT="2080"
143+
```
144+
145+
最後にsystemdを設定します。
146+
147+
```sh
148+
sudo systemctl daemon-reload
149+
systemctl is-enabled moproxy.service || sudo systemctl enable moproxy.service
150+
sudo systemctl is-active moproxy.service && sudo systemctl restart moproxy.service || sudo systemctl start moproxy.service
151+
```
152+
153+
上記にて、port 2080で待ち受けるサービスが作成されます。
154+
155+
## nftablesでの設定例
156+
157+
nftables自体はカーネルの機能としてインストールされているため、systemd-unitファイルを用意します。
158+
特にDockerを利用している場合は、デフォルトで用意されている`nftables.service`を使うと、Docker用のネットワーク設定がリセットされてしまうため、個別に`nft`コマンドを呼ぶようにします。
159+
160+
unitファイルは、`nft`コマンドを直接呼びdaemonが起動しないので、`Type=oneshot`を設定します。また、`moproxy.service`への依存関係を持たせたいため、`RemainAfterExit=yes`をセットします。
161+
162+
nftables上では以下のルールを設定します。
163+
164+
- `proxy`という名称の新規tableを作成
165+
- `proxy` tableにpreroutingのnat chainを追加 ※ルーティングされてきたパケット用、Dockerなど
166+
- `proxy` tableにoutputのnat chainを追加 ※ホストからのパケット用
167+
- 内部IPの場合は`accept`し何もしない (下記例だとクラスAアドレス)
168+
- tcpの宛先ポートが80,443の場合は、port 2080にパケットを転送する
169+
170+
```toml
171+
# /usr/local/lib/systemd/system/nft-proxy.service
172+
[Unit]
173+
description=nftables proxy
174+
Wants=moproxy.service
175+
176+
[Service]
177+
Type=oneshot
178+
RemainAfterExit=yes
179+
ExecStart=/usr/sbin/nft add table proxy
180+
ExecStart=/usr/sbin/nft add chain ip proxy prerouting '{type nat hook prerouting priority 10;}'
181+
ExecStart=/usr/sbin/nft add chain ip proxy output '{type nat hook output priority 10;}'
182+
ExecStart=/usr/sbin/nft add rule proxy output ip daddr 10.0.0.0-10.255.255.255 accept
183+
# ExecStart=/usr/sbin/nft nft add rule proxy output ip daddr <proxy_ip> accept
184+
ExecStart=/usr/sbin/nft add rule proxy output tcp dport {80, 443} redirect to 2080
185+
ExecStart=/usr/sbin/nft add rule proxy prerouting tcp dport {80, 443} redirect to 2080
186+
ExecStop=/usr/sbin/nft flush table proxy
187+
188+
[Install]
189+
WantedBy=multi-user.target
190+
```
191+
192+
※今回はProxyサーバが80でも443でもないポートを利用していたので、特にループしていませんが、Proxyサーバが80か443を利用している場合は宛先IPによる`accept`ルールを追加する必要があります。
193+
194+
## WSLでの設定例
195+
196+
WSL上で自動的にプロキシ設定を行わないようにwsl.confを設定します。
197+
198+
```toml
199+
# %USERPROFILE%\.wslconfig
200+
[wsl2]
201+
autoProxy = false
202+
dnsTunneling = true
203+
```
204+
205+
## 注意点
206+
207+
今回の構成では力業でTCPパケットを転送するということを行っているため、当然UDPを利用する通信(QUICやDNS)はProxyにリダイレクトされないです。
208+
209+
特にProxyへの除外先をIPではなくFQDN名で指定したいというニーズがある場合、この構成ではうまく動きません。moproxyの設定ファイルにはFQDN名でルール設定できるように見えますが、上記の構成ではmoproxy自体はTLSを復号しないため、FQDNを読む手段がなく動きません。(相手先のProxyがsocksv5などに対応していて、moproxyでCNIを読める設定にすれば可)
210+
211+
やはりProxyを使わざるを得ない環境では実現できる構成に制限がつくということかなと思います。
212+
213+
## 小ネタ
214+
215+
今回の設定では必要ないですが、Proxyへの認証時にはユーザー名やパスワードのURLエンコードが必要になるケースがあります。jqでは`@uri`を使うことでワンライナーでURLエンコードをやってくれるので、紹介しておきます。
216+
217+
```sh
218+
#
219+
PROXY_PASSWORD=$(systemd-ask-password --keyname=proxy.password "プロキシログイン用パスワードを入力してください: ")
220+
PROXY_PASSWORD=$(echo "${PROXY_PASSWORD}" | jq -Rr @uri)
221+
https_proxy="http://${PROXY_USERNAME}:${PROXY_PASSWORD}@${PROXY_URL}" curl -fL -o ...
222+
```
223+
224+
# さいごに
225+
226+
Proxyを介したコネクションはツールの実装によるところがあり、トラブルが起こりやすいですが、この方式であればツール側はProxyの存在を気にせずに通常通りのリクエストを行うことができます。実は、Rancher Desktopは内部で同じ仕組みを使っており、いい仕組みを考える人がいるもんだと感心をしました。
227+
228+
また、moproxyはRustで書いてあり、動作を理解するために今回初めてまともにRustのソースコードを読んだのですが、とてもよい勉強になりました。自分で一から書けるかは置いておいて、読むのに当たっては結構好きな言語かもしれないという発見もあったので、機会があれば自分で書いたりすることもできればと思います。(特にマイコン向けとかで低レイヤーの実装が必要になった際には挑戦してみたい気がします)
229+
230+
# 参考
231+
232+
先人たちも色々な工夫をされていたようなので、こちらも参考にしてみてください。
233+
234+
- [ProxyとDockerと新人社員と時々わたし](/articles/20201020/)
235+
- [ローカルプロキシで認証プロキシの煩わしさを解消!](/articles/20240227a/)
23.9 KB
Loading
159 KB
Loading

0 commit comments

Comments
 (0)