Proxy環境下での仮想GW構築

はじめに
---

この記事自体は随分古くなったものの、未だに時々proxyに心を折られた方からいいねを頂くことがある。
そして、現在では遥かに優れた[素晴らしい記事](https://qiita.com/wadahiro/items/7fa852b7217940e6ef26#_reference-52a1da21d815844b0eb4)を書かれている方がおり、より簡単にproxyとの戦いに終止符を打つことができるようになった。是非そちらを参照頂きたい。

概要
----

通常、proxy環境下においてクライアントがproxyを通過して外部ネットワークに（間接的に）アクセスするには、環境変数http_proxy等、明示的に設定を行う必要がある。
しかし、ソフトウェアによっては設定方法が違ったり対応していなかったり、そもそも環境変数を差し替えるのが難しい状況があったりして、このあたりが問題で作業に支障をきたしているような状況を最近良く見る。

規則違反（あるいはセキュリティインシデント）になってもよければ踏み台を準備するなど対処方法はいくらでもあるが、そこは規則を遵守したい。

そこで、TCPを対象に、透過的にproxy経由で外部ネットワークと通信できるようにする仮想的なGWを手元で構築することを考える。これをDefault Gatewayにすることで、proxyが存在しないかのような環境を作る。

要素
----

### 透過型proxy

基本情報処理技術者試験にもよく出てきそうだが、透過型proxyは、ネットワーク側でパケットをリダイレクトするなどして特定のホストの特定サービスを通過させることで、クライアントに設定を行わずとも、強制的にproxyを通過させることができる。検索すると、iptables（場合によっては＋ebtables）とSquidで構築する手順がよく引っかかる。

透過型proxyは、主に管理者が、

* 外部アクセスへは、強制的にproxyを経由させたい
* 利用者に毎回proxy設定させたくない

といった場合に利用する。

ということは、利用者目線（？）でこれを見たとき、

* 正直何か動かすのに常にproxyが問題になることが多いので、ダイレクトに外に出られるかのようにするために利用したい

という考えに容易に至る。

考え方としては特別変わったものでもなく、優れたソフトウェアが昔からある。

問題になっている環境に適用していいかどうかはいろいろ状況によるが、今回は外部に踏み台を準備してトンネリングするわけでもなく、普段proxyを通過するための設定を行わなくて良いようにする事だけを考える。

### 構築

Windowsの場合、少し毛色が異なるが、商用プロダクトで言うところのProxyCapなどが有名。
LinuxであればnetfilterとOSSを組み合わせて簡単に実現できる。

今回はLinuxでの構築と利用方法を記載する。ソフトウェアには色々あり、構築方法も様々な情報があるが、あえてredsocksを使って、全ての外向きTCPパケットをproxyに向けて、HTTP CONNECTで外に繋ぎにいこうとする（＝L5以上のプロトコルは関係なく、TCPとしてproxyが通過させてくれる宛先であればなんでもいい）ような仮想的なGatewayを構築する。

事前準備
--------

仮想GWはCentOS6.6で構築している。

まず、必要なものをインストールしておく。

```bash
$ yum -y install git gcc
```

libevent、redsocksのインストールと設定
----------------------------

redsocksはlibevent2.0以上を要求するので、まずlibeventをインストールする。

```bash:libeventのインストール
$ wget https://sourceforge.net/projects/levent/files/libevent/libevent-2.0/libevent-2.0.22-stable.tar.gz
$ tar zxvf libevent-2.0.22-stable.tar.gz
$ cd libevent-2.0.22-stable
$ ./configure
$ make
$ make install
```

その後、redsocksをインストールする。

```bash:redsocksのインストール
$ git clone https://github.com/darkk/redsocks.git
$ cd redsocks
$ make
$ mv redsocks /usr/local/bin/
$ #make installは無いので手で移動
```

設定ファイル（redsocks.conf）に、HTTP Proxyを通して接続にいく設定を記述する。

```text:/usr/local/etc/redsocks.conf
base {
  // debugログ
  log_debug = on;
  // infoログ
  log_info = on;
  // ログファイル
  log = "file:/var/log/redsocks.log";
  // daemonにする場合
  daemon = on;
  // パケットのリダイレクトに使うツール
  redirector = iptables;
}

redsocks {
  // redsocksがbindするip/port
  local_ip = 127.0.0.1;
  local_port = 12345;

  // 通過させたいproxyのip/port
  ip = ＜proxyのip＞;
  port = ＜proxyのport＞;

  // proxyのタイプ、今回はhttp-connect(HTTP Proxyを対象に、HTTP CONNECTで外に出る)
  // socks4, socks5, http-connect, http-relay
  type = http-connect;

  // proxyに認証がある場合
  // login = "***";
  // password = "***";
}
```

起動は以下のように行う。

```bash
$ redsocks -c /usr/local/etc/redsocks.conf
```

名前解決
-------------

proxy配下の環境では多くの場合、名前解決はproxyが行い、dnsは内部ネットワークのホストに対しての変換用に提供されているものであることが多い。つまり、外部向けのTCPパケットが透過型proxyにリダイレクトされるようにしただけでは外部ホストの名前解決ができないため、そもそもunknown hostで通信エラーになる。
これを解決する例として、pdsndなどを利用して名前解決のリクエスト先をredsocks経由で外部のDNSサーバ(8.8.8.8など)に向けるなどがある。
※この場合、前提として、proxyから外部のDNSサーバの53/tcpに向けて接続が可能でなくてはならない。

```bash:pdnsdのインストール
$ git clone https://gitorious.org/pdnsd/pdnsd.git
$ cd pdnsd
$ ./configure
$ make
$ make install
```

設定ファイルを、例えば以下のように編集する。

```text:/usr/local/etc/pdnsd.conf
global {
  perm_cache=1024;
  cache_dir = /var/cache/pdnsd;
  pid_file = /var/run/pdnsd.pid;
  run_as = nobody;
  server_ip = 0.0.0.0;
  server_port = 53;
  status_ctl = on;
  paranoid = on;
  query_method = tcp_only;
  min_ttl = 1h;
  max_ttl = 1w;
  timeout = 10;
  neg_domain_pol = on;
  udpbufsize = 1024;
}

server {
  label= "google public dns";
  ip = 8.8.8.8;
  port = 53;
  timeout = 4;
  uptest = ping;
  purge_cache = off;
  edns_query = no;
}
```

以下のコマンドで起動する。

```bash
$ pdnsd &
```

iptablesの設定
-------------

最後に、上記で準備したredsocksとpdnsdを連携して、外部ネットワークに直接出て行っているかのようなnetfilter設定を行う。
なお、IPルーティングするわけではないので/proc/sys/net/ipv4/ip_forwardは0のままでもいい。

```bash
$ sudo iptables -t nat -F
$ # prerouting
$ # 自分向きのものは通常処理
$ sudo iptables -t nat -A PREROUTING -p tcp -d 127.0.0.1/32 -j RETURN
$ sudo iptables -t nat -A PREROUTING -p tcp -d ＜仮想GWのIP＞/32 -j RETURN
$ # それ以外は全てredsocksに流す
$ sudo iptables -t nat -A PREROUTING -p tcp -j REDIRECT --to-ports 12345
$ # output
$ # pdnsdから外部DNSの53/tcpに出て行くものはredsocksに流す
$ sudo iptables -t nat -A OUTPUT -p tcp -d 8.8.8.8 --dport 53 -s ＜仮想GWのIP＞/32 -j REDIRECT --to-ports 12345
```

とりあえず目的を達成できそうなルールだけ設定しているが、セキュリティ的に色々足りないところが沢山あるため、より詳しくは[本家](https://github.com/darkk/redsocks)を参照。

このGWを利用する側のPCの設定
--------------------------

Default GatewayとDNSに仮想GWを指定する。

制限
----

* 遅そう
* 透過させようとしているproxyがそもそも宛先ポートを80と443に絞るなどしている場合、それ以外のポートに関してはこの構成で通過することはできず、また多くの場合はこのような制限が入っている。public DNSなどへのtcpも通過できるかは、構成による。

余談
----

このGWはDocker同士でも動く。(--cap-add=NET_ADMINなどを付けて動かす)
※redsocksのlocal_ipが127.0.0.1だとPREROUTINGでredsocksに届いているはずだがredsocksが反応しない。0.0.0.0にしたらうまく動作した。