Polipoを使用してHTTPリクエストをSOCKSプロキシサーバーへプロキシする方法を試してみる

SOCKSに対応していないクライアントからのHTTPリクエストを、Polipoを使用してSOCKSプロキシサーバーへプロキシする方法を試してみました。
2019.04.17

こんにちは。サービスグループの武田です。

社内のイントラにはSOCKSプロキシサーバーが設置されています。詳細は次のブログを参考に。

VPN利用者のためにdelegateでSOCKSサーバーを立ててみました

最近、HTTPプロキシにしか対応していないリクエストを、VPN経由でこのSOCKSプロキシサーバーを通す必要に迫られました。実はこれまではHTTPプロキシも並行稼働していましたが、それがSOCKSに一本化。さて困ったというわけです。

「HTTPプロキシにしか対応していない」というのは具体的にはAWS CLIを用いた操作なんですが、今回はAWS CLIを実行するマシンのローカルにHTTPプロキシを動作させ、HTTPプロキシ経由でSOCKSプロキシを通過させることで解決しました。その一連の動作を整理したのでお届けします。

今回の全体像

EC2インスタンスを2台用意し、1台をプロキシサーバー(socks-proxy)、もう1台をクライアント(client)とします。業務ではクライアントはMacなのですが、検証用ということでインスタンスを立てました。プロキシサーバーにはSOCKSプロキシとしてDanteをインストールします。またクライアントにはHTTPプロキシとしてPolipoをインストールします。クライアントから発行するHTTPリクエストが、Polipoを経由してSOCKSプロキシを通過し、エンドに流れれば成功となります。

なおPolipoはメンテナンスが止まっていますが、手軽さとローカルで一時的に立ち上げるだけなので使用することにしました。

インスタンスの起動と検証の準備

まずはAWSにEC2インスタンスを用意します。最近はスポットインスタンスを使うのがマイブームですので、スポットでAL2を2台リクエストします。

しばらく待つとインスタンスが立ち上がりました。

続いてプロキシサーバーにDanteをインストールします。

socks-proxy

$ sudo yum install -y http://mirror.ghettoforge.org/distributions/gf/gf-release-latest.gf.el7.noarch.rpm
$ sudo yum --enablerepo=gf-plus install -y dante-server
$ sudo cp /etc/sockd.conf{,.origin}
$ sockd -v
Dante v1.4.1.  Copyright (c) 1997 - 2014 Inferno Nettverk A/S, Norway

次に設定ファイル /etc/sockd.conf を修正してSOCKSプロキシとして必要な設定を行います。とりあえず必要最小限のものを設定しています。

/etc/sockd.conf

--- /etc/sockd.conf.origin	2019-04-17 00:00:00.000000000 +0000
+++ /etc/sockd.conf	2019-04-17 00:00:00.000000000 +0000
@@ -46,11 +46,12 @@
 # accept connections going to that address.
 #internal: 10.1.1.1 port = 1080
 # Alternatively, the interface name can be used instead of the address.
-#internal: eth0 port = 1080
+internal: eth0 port = 1080

 # all outgoing connections from the server will use the IP address
 # 195.168.1.1
 #external: 192.168.1.1
+external: eth0

 # list over acceptable authentication methods, order of preference.
 # An authentication method not set here will never be selected.
@@ -61,9 +62,10 @@

 # methods for socks-rules.
 #socksmethod: username none #rfc931
+socksmethod: none

 # methods for client-rules.
-#clientmethod: none
+clientmethod: none

 #or if you want to allow rfc931 (ident) too
 #socksmethod: username rfc931 none
@@ -173,6 +175,11 @@
 #        from: 10.0.0.0/8 port 1-65535 to: 0.0.0.0/0
 #}

+client pass {
+	from: 0.0.0.0/0 to: 0.0.0.0/0
+	log: connect disconnect error
+}
+

 # drop everyone else as soon as we can and log the connect, they are not
 # on our net and have no business connecting to us.  This is the default
@@ -263,6 +270,11 @@
 #        log: connect error
 #}

+socks pass {
+	from: 0.0.0.0/0 to: 0.0.0.0/0
+	log: connect disconnect error
+}
+
 # route all http connects via an upstream socks server, aka "server-chaining".
 #route {
 # from: 10.0.0.0/8 to: 0.0.0.0/0 port = http via: socks.example.net port = socks

有効な部分だけ抜き出すとこんな感じです。シンプルですね。

socks-proxy

$ grep -E -v '^#|^$' /etc/sockd.conf
logoutput: stderr
internal: eth0 port = 1080
external: eth0
socksmethod: none
clientmethod: none
client pass {
	from: 0.0.0.0/0 to: 0.0.0.0/0
	log: connect disconnect error
}
socks pass {
	from: 0.0.0.0/0 to: 0.0.0.0/0
	log: connect disconnect error
}

これでSOCKSプロキシの準備はできました。動作確認のために、それぞれのインスタンスからcurlを叩いてグローバルIPを確認します。まずはプロキシを通さずに素の状態を確認します。

socks-proxy

$ curl https://ifconfig.io
13.113.62.222

client

$ curl https://ifconfig.io
13.231.126.107

次にプロキシサーバーでDanteを起動し、クライアントではプロキシ経由でcurlを叩いてみます。クライアントで指定している172.31.10.105は、プロキシサーバーのプライベートIPです。

socks-proxy

$ sudo sockd

client

$ curl -x socks5://172.31.10.105:1080 https://ifconfig.io
13.113.62.222

クライアントのcurlでプロキシサーバーのグローバルIPが表示されました。これでSOCKSプロキシが起動できていること、プロキシ経由でトラフィックが流れていることが確認できました。

プロキシサーバーのグローバルIPのみ実行を許可するロールの付与

AWS CLIをSOCKSプロキシ経由で実行する検証準備が整いました。今回は次のようなポリシーをアタッチしたIAMロールを作成し、EC2インスタンス2台に割り当てます。プロキシサーバー経由で実行できていれば、クライアントでもec2のアクションが実行できるはずです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "ec2:*",
            "Resource": "*",
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": "13.113.62.222/32"
                }
            }
        }
    ]
}

IAMロールをEC2インスタンスに割り当て、それぞれのインスタンスからAWS CLIを叩いてみます。

socks-proxy

$ aws ec2 describe-regions --region ap-northeast-1 | head -5
{
    "Regions": [
        {
            "Endpoint": "ec2.eu-north-1.amazonaws.com",
            "RegionName": "eu-north-1"

client

$ aws ec2 describe-regions --region ap-northeast-1 | head -5

An error occurred (UnauthorizedOperation) when calling the DescribeRegions operation: You are not authorized to perform this operation.

想定どおり、プロキシサーバーのみ実行できていますね!

それではプロキシサーバーを経由してできるかやってみましょう。AWS CLIではHTTP_PROXYHTTPS_PROXYの環境変数を設定することでプロキシを使ってくれます。

まずはsocks5のスキーマを指定して実行してみます。

client

$ export HTTP_PROXY=socks5://172.31.10.105:1080
$ export HTTPS_PROXY=socks5://172.31.10.105:1080
$ aws ec2 describe-regions --region ap-northeast-1 | head -5

Not supported proxy scheme socks5

Not supportedです。ダメですね。ダメもとでhttpを指定するとどうなるでしょうか。

client

$ export HTTP_PROXY=http://172.31.10.105:1080
$ export HTTPS_PROXY=http://172.31.10.105:1080
$ aws ec2 describe-regions --region ap-northeast-1 | head -5
Unable to locate credentials. You can configure credentials by running "aws configure".

当たり前ですがダメですね……。ちなみになぜ接続エラーとかではなく、credentialsのエラーが表示されるのかは不明です(ソースとか見ればわかるかもしれませんが)。というわけでプロキシサーバーがHTTPに対応していないと無理そうです。

Polipoを利用してクライアントからもAWS CLIを実行できるようにする

以上のことから、AWS CLIをプロキシ経由で実行するためには対応が必要です。次のような候補が考えられました。

  1. クライアント(AWS CLI)をSOCKSに対応させる
  2. プロキシサーバーにHTTPプロキシを追加する
  3. SOCKSプロキシの前段にHTTPプロキシを追加する

1はつらいですね。チャレンジしてませんが。2は無理です。情シスの方針でSOCKS一本化となったためです。残すは3ということで、3の方針で解決することにします。

HTTPプロキシソフトウェアはいくつかありますが、バックエンドにSOCKSを置けるHTTPプロキシを検索したらPolipoがヒットしたため試してみることにしました。現在はメンテナンスされていないため使用の際には自己責任でお願いします。次のようにインストールおよび設定ファイルを準備します。

client

$ sudo yum install https://copr-be.cloud.fedoraproject.org/results/jasonbrooks/polipo/epel-7-x86_64/polipo-1.1.1-2.fc22/polipo-1.1.1-2.el7.centos.x86_64.rpm
$ cat > polipo_config <<EOF
proxyPort = 8080

socksParentProxy = "172.31.10.105:1080"
socksProxyType = socks5
EOF

proxyPortはPolipoが使用するポート番号。socksParentProxyはバックエンドのプロキシサーバー。socksProxyTypeはそこで使用するプロトコルを指定しています。

ちなみにMacでPolipoを使用する場合は、brewで簡単にインストールできます。

mac

$ brew install polipo

それでは先ほど用意した設定ファイルでPolipoを起動し、あらためてAWS CLIを実行してみます。なおHTTP_PROXYHTTPS_PROXYはスキーマがない場合、http://を自動的に追加してくれます。

client

$ sudo polipo -c polipo_config &
$ export HTTP_PROXY=localhost:8080
$ export HTTPS_PROXY=localhost:8080
$ aws ec2 describe-regions --region ap-northeast-1 | head -5
{
    "Regions": [
        {
            "Endpoint": "ec2.eu-north-1.amazonaws.com",
            "RegionName": "eu-north-1"

DE KI TA !!

まとめ

クライアントがSOCKSに対応しているなら何も問題はありません。ただ、今回のようにどうしてもHTTPプロキシが必要な場合は自前で用意してみるのもひとつかと思われます。有事の際、参考になれば幸いです。