ChromeDriver で Basic 認証付きプロキシを通せない → 多段プロキシで解決

## 背景

Chrome ブラウザを ChromeDriver から自動操作してIPアドレス制限付きのサイトにアクセスする必要がありました。許可されている固定IPアドレスをもつプロキシサーバーがあるのでそれを経由してサイトにアクセスします。そのプロキシサーバーは Basic 認証方式での認証を必要とします。 

ところが、 ChromeDriver にはプロキシ接続のオプションはあるものの認証情報は渡すことができないようで、行き詰まってしまいました。

そこで、認証用のローカルプロキシサーバーを立てて解決する方法を試みました。

※ コードは Go で書いていますが、解決方法は言語に依存しません。

## 必要なもの

- Docker (なくてもいい)

## コード

いろいろ削ってシンプルに書くとこんな感じです。[agouti](https://github.com/sclevine/agouti) を使っています。

```go:main.go
package main

import (
	"github.com/sclevine/agouti"
	"log"
)

func main() {
	driver := agouti.ChromeDriver(agouti.ChromeOptions("args", []string{"--proxy-server=proxy.example.com:8888"}))
	if err := driver.Start(); err != nil {
		log.Fatal(err)
	}
	defer driver.Stop()
	page, err := driver.NewPage()
	if err != nil {
		log.Fatal(err)
	}
	
	// 診断くんにアクセス
	if err := page.Navigate("http://taruo.net/e/"); err != nil {
		log.Fatal(err)
	}
}
```

`--proxy-server=proxy.example.com:8888` という起動オプションを渡してプロキシサーバーへの接続を試みます。しかし、認証情報 (ユーザー名とパスワード) はここでは指定できません。少なくとも私が調べて試した限りでは不可能でした。当然このまま実行するとブラウザ上で認証情報を求められます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/38804/6e72701f-34b0-8567-bf27-2d85bbf1cdd0.png)
これでは自動化ができません。

## ローカルプロキシに認証を代理してもらう

ブラウザの方で認証ができないので、ブラウザとプロキシサーバー (甲) の間にもう1つプロキシサーバー (乙) を介在させて、乙に認証情報を載せます。

```
ブラウザ -> 乙 (認証情報を載せるプロキシ) -> 甲 (認証が必要なプロキシ) -> サイト
```

そこで乙にあたるローカルプロキシサーバーを立てるのですが、 Docker がインストールされているのであれば [Squid](http://www.squid-cache.org/) の image からコンテナを起動させるのが手っ取り早いです (Docker がない場合は Squid を OS にインストールしてください) 。

https://github.com/sameersbn/docker-squid

```sh
docker pull sameersbn/squid:3.5.27
```

Suquid の設定ファイルは[ここらへん](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/networking_guide/sec-squid-configuration)から適当に取ってきて `~/squid/squid.conf` としてとりあえず保存しておきます。

```conf:squid.conf
#defaults
acl localnet src 10.0.0.0/8
acl localnet src 172.16.0.0/12
acl localnet src 192.168.0.0/16
acl localnet src fc00::/7
acl localnet src fe80::/10
acl SSL_ports port 443
acl Safe_ports port 80
acl Safe_ports port 21
acl Safe_ports port 443
acl Safe_ports port 70
acl Safe_ports port 210
acl Safe_ports port 1025-65535
acl Safe_ports port 280
acl Safe_ports port 488
acl Safe_ports port 591
acl Safe_ports port 777
acl CONNECT method CONNECT
http_access allow manager localhost
http_access deny manager
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow localnet
http_access allow localhost
http_access deny all
http_port 3128
hierarchy_stoplist cgi-bin ?
coredump_dir /var/spool/squid
refresh_pattern ^ftp:       1440    20% 10080
refresh_pattern ^gopher:    1440    0%  1440
refresh_pattern -i (/cgi-bin/|\?) 0 0%  0
refresh_pattern .       0   20% 4320
```

認証情報を設定ファイルに追記します。

```sh
echo 'cache_peer {甲プロキシのホスト} parent {甲プロキシのポート番号} 0 no-query login={ユーザー名}:{パスワード}' >> ~/squid/squid.conf
echo 'never_direct allow all' >> ~/squid/squid.conf
```

- http://www.squid-cache.org/Versions/v3/3.5/cfgman/cache_peer.html
- http://www.squid-cache.org/Versions/v3/3.5/cfgman/never_direct.html

設定が準備できたら Docker コンテナを起動します。

```sh
docker run --name squid -d \
  --publish 3128:3128 \
  --volume ~/squid/squid.conf:/etc/squid/squid.conf \
  --volume ~/squid/cache:/var/spool/squid \
  sameersbn/squid:3.5.27
```

これで `localhost:3128` が開放されているはず[^1]なのでコードの起動オプション部分を　`--proxy-server=localhost:3128` に変更します。

プログラムを実行すると
![image.png](https://qiita-image-store.s3.amazonaws.com/0/38804/09de49c7-a11e-53c8-f6e3-897700e3c0e2.png)
上手くいきました。

## 参考

- [Capabilities &amp; ChromeOptions - ChromeDriver - WebDriver for Chrome](https://sites.google.com/a/chromium.org/chromedriver/capabilities#TOC-chromeOptions-object)
- [認証付きプロキシを強要されるOSXユーザがsquildで幸せになれた話 - Qiita](https://qiita.com/kmats@github/items/8a41c942e079c7a95919)

[^1]: コンテナが起動していなかったら設定ファイルに誤りがある可能性があります。