Cntlmの代わりにpx-proxyでNTLM認証プロキシを手軽に突破する【Windows】

様々なプログラミングツールでNTLM認証のかかったProxyサーバーを突破してインターネットに接続するためのソフトウェアとして、[Cntlm Authentication Proxy](https://sourceforge.net/projects/cntlm/)が有名です。

Cntlmはとても便利なのですが、WindowsのノートPCで使う上でいくつか不満もありました。

- Proxy環境外で動作しない（参考→[Working with a sometimes proxy ](https://sourceforge.net/p/cntlm/discussion/702676/thread/73bc9d96/)）。
- Proxy構成スクリプト（pac）を設定できない。
- 認証情報を設定ファイルに書かなければならない。

そこで[Px](https://github.com/genotrance/px)という別のProxy Serverを試したところ上記の不満が解消されました。
日本語の記事が見つからなかったので紹介します。

とは言っても導入・設定はCntlmと比較しても簡単で、Cntlmを使っている方ならば簡潔にまとまったREADME.mdを読めば事足りてしまうと思います。

また欠点を先に書いておくと、Px経由で`apt upgrade`等を実行した場合に失敗することがあり、Cntlmよりも動作が不安定なようです（[issue](https://github.com/genotrance/px/issues/113)）。
私も何度か`apt update`を失敗したり、Dockerイメージのビルド中のファイルのダウンロードに失敗しビルド失敗になったりしました。

なので安定性を重視される方はCntlmを使い続けた方が良いと思うものの、両方インストールして起動する方を切り替えるのは簡単なので、一度試されてはいかがでしょうか。

~~なおLinuxやmacOSでも使えるCntlmとは異なり、PxはWindowsでしか動作しません（一から書き直された[Px2](https://github.com/genotrance/px2)でマルチOS対応が進められているようです）。~~
他の選択肢として、LinuxとmacOSでもProxy環境外で動作しpacに対応した[Alpaca](https://github.com/samuong/alpaca)というものも見つけました。macOSならHomebrewでインストールできるようです。こちらはCntlmとは違い、macOS以外では認証情報を保存する機能がなく、起動時にパスワードを入力しなければならない模様。

（2021/02/04追記）マルチOS対応かつWindowsではGUIでも設定できるという[winfoom](https://github.com/ecovaci/winfoom)も見つけました。Proxy環境外では動作しなくなってしまうようですが、その他の点はこちらでも満足できそうです。

（2022/05/22追記）pxの新バージョンはLinuxでも動作するようです（未確認）。

## インストール方法

WindowsにPythonをインストールせずとも実行できる、バイナリファイルを使う方法は以下の通り。インストール自体の管理者権限は不要です。

ポートを空けるためのファイアウォールの設定が必要な場合は別途行ってください。

1. [GitHubのReleaseページ](https://github.com/genotrance/px/releases)から最新の実行ファイルを含んだzip（`px-vX.X.X-windows.zip`）をダウンロード。
2. 展開したディレクトリの名前を変更して`%USERPROFILE%/AppData/Local/Programs/px`などとして置く。
3. PowerShellで上記ディレクトリを開き、`.\px.exe --install`を実行するとスタートアップに`px.exe`が登録される。

## 設定方法

### pxの設定

1. PowerShellで`px.exe`のあるディレクトリを開き、`.\px.exe --save`を実行するとディレクトリ内に設定ファイル`px.ini`が生成される。
2. `px.ini`を適宜編集する。
3. `px.exe`をダブルクリックして起動（もしくはスタートアップで自動起動）。

例えばプロキシ設定にpacファイルを使っておりDocker Desktop含むWSL2等の仮想マシンを使いたい場合、以下のようにプロキシ構成スクリプトを指定する`pac`と、ホスト上の仮想マシンからのアクセスを制御する`hostonly`のみデフォルト値から変更してやれば大丈夫です。

この`hostonly`の設定はとても便利で、Cntlmならばゲートウェイモードにして許可リストに仮想マシンのIPアドレスを入れ、禁止リストで仮想マシン以外を禁止リストに登録もしくはファイアウォールで仮想マシンからのアクセスのみ許可するということをしなければなりません。
Pxなら`hostonly = 1`にするだけで仮想マシンからのアクセスのみを許可してくれるので、IPアドレスを手動で入力する面倒な設定は不要です。

`pac`にはWindowsのプロキシ設定に書かれている「スクリプトのアドレス」をそのまま入れると機能します。

```txt:px.ini
[proxy]
server =
pac = http://proxy.pac
listen = 127.0.0.1
port = 3128
gateway = 0
hostonly = 1
allow = *.*.*.*
noproxy =
useragent =
username =
auth =

[settings]
workers = 2
threads = 5
idle = 30
socktimeout = 20.0
proxyreload = 60
foreground = 0
log = 0
```

Windowsの資格情報を使ってプロキシ認証を通ってくれるので、わざわざIDやパスワードを設定ファイルに書いておく必要はありません。

### 各所に行うプロキシ設定（おまけ）

以下の各所への設定はPx特有のものではなくCntlm等でも同じです。

#### Windows/WSL1

プロキシ設定を`http://localhost:3128`にしてやればインターネットに接続できます。

```bash:bash(WSL1)
$ curl -I https://curl.se -x http://localhost:3128
HTTP/1.1 200 Connection established
Server: SimpleHTTP/0.6 Python/3.7.3
Date: Wed, 03 Feb 2021 xx:xx:xx GMT
Proxy-Agent: SimpleHTTP/0.6 Python/3.7.3
以下略
```

Ubuntuの場合、`~/.bashrc`とapt用の`/etc/apt/apt.conf`にプロキシ設定を書いておけば大体事足りると思います。

#### Docker Desktop（WSL2バックエンドのLinuxコンテナ）

Docker Desktopのダッシュボードから設定（「Settings」→「Resources」→「PROXIES」）に入れる、`docker pull`コマンド等で使われる設定（Dockerデーモンが使う設定）には`http://localhost:3128`を、
コンテナ内のプロキシ設定には、コンテナ内からホストのWindowsのポートを指定するために`http://host.docker.internal:3128`と書きます。[^1]

後者の設定はホストの`~/.docker/config.json`にだけ行っておけばコンテナ生成時に自動的にコンテナ内に大文字環境変数（`$HTTP_PROXY`など）と小文字環境変数（`$http_proxy`など）が同時に設定されるので、`docker run`時にオプションで環境変数を設定する手間を省けます。

ダッシュボードと`~/.docker/config.json`にプロキシ設定を行った状態で、DockerHubからイメージをpullできコンテナ内にプロキシ環境変数が設定されているかを下記のようにコンテナ内の環境変数を表示させ確認します。

```bash:pwsh
> docker run --rm alpine env
Unable to find image 'alpine:latest' locally
latest: Pulling from library/alpine
4c0d98bf9879: Pull complete
Digest: sha256:08d6ca16c60fe7490c03d10dc339d9fd8ea67c6466dea8d558526b1330a85930
Status: Downloaded newer image for alpine:latest
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=xxxxxxxxxxxx
HTTP_PROXY=http://host.docker.internal:3128
http_proxy=http://host.docker.internal:3128
HTTPS_PROXY=http://host.docker.internal:3128
https_proxy=http://host.docker.internal:3128
HOME=/root
```

コンテナがインターネットに接続できることをcurl公式イメージで確認します。

```bash:pwsh
> docker run --rm -it curlimages/curl curl -I https://curl.se
HTTP/1.1 200 Connection established
Server: SimpleHTTP/0.6 Python/3.7.3
Date: Wed, 03 Feb 2021 xx:xx:xx GMT
Proxy-Agent: SimpleHTTP/0.6 Python/3.7.3
以下略
```

#### WSL2

WSL2で動くLinux仮想マシンからWindows側のポートに接続するにはWindowsのIPアドレスを指定しなければならないのですが、デフォルトでは特別なホスト名が存在しないため名前解決できません（[GitHubのissue](https://github.com/microsoft/WSL/issues/4619)には要望が沢山投稿されており、[Microsoftでは解決に向けて動いているようです](https://github.com/microsoft/WSL/issues/4619#issuecomment-740777647)）。

ただしDocker Desktopをインストールした状態ならば、`C:\Windows\System32\drivers\etc\hosts`にDocker Desktopが`host.docker.internal`のホスト名でWindowsの物理NICのIPアドレスを書き込み、これを元にWSL内で`/etc/hosts`が自動生成されるため、WSL上からWindowsを`host.docker.internal`で名前解決できます。[^2]

よってDockerコンテナ同様に`http://host.docker.internal:3128`を指定してやります。

```bash:bash(WSL2)
$ curl -I https://curl.se -x http://host.docker.internal:3128
HTTP/1.1 200 Connection established
Server: SimpleHTTP/0.6 Python/3.7.3
Date: Wed, 03 Feb 2021 xx:xx:xx GMT
Proxy-Agent: SimpleHTTP/0.6 Python/3.7.3
以下略
```

`localhost`が`host.docker.internal`になった以外、WSL1の場合と同じです。

## 参考記事

- [Cntlmを使ってNTLMv2認証Proxy環境をやっつける](https://qiita.com/yohskeey/items/6f17ea051fbe5568f3bf)
- [Docker Desktop for Windows の proxy 設定](https://qiita.com/2fbCvmiYKX/items/c6aab333364af25fd924)
- [Configure Docker to use a corporate proxy](https://medium.com/@saniaky/configure-docker-to-use-a-host-proxy-e88bd988c0aa)

[^1]: 余談ですが`host.docker.internal`はDocker Desktopでしか機能しないため、LinuxでCntlmを使って同じようにDockerホストのポートにアクセスする設定をするにはホストの物理NICのIPを指定する（つまり固定IPにする必要がある）か、コンテナ作成時に`--add-host=host.docker.internal:host-gateway`オプションを毎回付けて`host.docker.internal`をコンテナ内で名前解決できるようにしてやらなければなりません（[GitHubのissue](https://github.com/docker/for-linux/issues/264)）。現状、OS混在環境でOS間の差異を吸収する必要があるならば、どのOSでも上記オプションを付けてコンテナ内のプロキシ設定は常に`http://host.docker.internal:3128`にするのが一番良いと思われます。

[^2]: WSL1/2上どころかWindows上からでも名前解決できるので`localhost`の代わりに使って全設定箇所`host.docker.internal`にもできるのですが、IPアドレス不定なため信頼性はイマイチです。私の場合はVPN接続時にDocker Desktopを起動すると疎通不能な謎のIPが書き込まれたりしました。