1.本記事について
本記事は、Apache(version:2.2)のProxyPass、ProxyPassReverseについてHTTPヘッダやログを見ながら実際に挙動を確認した内容になります。
ProxyPassについては、公式ドキュメント[1]のページを見れば大体内容がわかりましたが、
ProxyPassReverseについては分らなかったので、実際にサーバを動かして確認してみました。
2.試す
以下のようにVagrantとVirtualBoxで確認用のサーバを作りました。
また、確認用のリソースとして、webサーバにapp/foo.htmlとapp/redirect_to.htmlを準備しています。
構成は以下の通り。
Vagrant.configure(2) do |config|
config.vm.define "client" do |node|
node.vm.box = "opscode-centos-6.7"
node.vm.hostname = "client"
node.vm.network "private_network", ip: "192.168.100.100"
end
config.vm.define "proxy" do |node|
node.vm.box = "opscode-centos-6.7"
node.vm.hostname = "proxy"
node.vm.network "private_network", ip: "192.168.100.200"
end
config.vm.define "web" do |node|
node.vm.box = "opscode-centos-6.7"
node.vm.hostname = "web"
node.vm.network "private_network", ip: "192.168.110.200"
end
end
Vagrantについては、皆さんよくご存じでVagrantfileを見れば雰囲気はわかると思いますので、詳細は割愛します。
今回作成したサーバの役割は
- client:リクエストするclientサーバ
- proxy :ゲートウェイであるproxyサーバ。clientからのリクエストをwebに転送する。
- web :各種リソースが存在する内部webサーバ。clientからの直接のリクエストは受け付けない。
になります。
3.まずProxyPass
ProxyPassについては公式ドキュメントのページに以下のようにあります。
このディレクティブはリモートサーバをローカルサーバの名前空間に マップできるようにします。
ローカルサーバは通常の意味でのプロキシと しては動作せず、リモートサーバのミラーとして振る舞います。
ローカルサーバはしばしば リバースプロキシ や ゲートウェイ と呼ばれます。
path はローカルの仮想パスの名前です。
url は リモートサーバの部分 URL になり、クエリー文字列を含むことはできません。
やりたいことは、proxyサーバ経由でwebのapp/foo.htmlを取得できるようにすることです。
以下のようにproxyサーバをconfigします。
ProxyRequests Off
ProxyPass /app/ http://192.168.110.200/app/
configが完了したので、clientサーバからリクエストして確認します。
[vagrant@client ~]$ curl -v http://192.168.100.200/app/foo.html
* About to connect() to 192.168.100.200 port 80 (#0)
* Trying 192.168.100.200... connected
* Connected to 192.168.100.200 (192.168.100.200) port 80 (#0)
> GET /app/foo.html HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Host: 192.168.100.200
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sun, 17 Dec 2017 13:41:12 GMT
< Server: Apache/2.2.15 (CentOS)
< Last-Modified: Sun, 17 Dec 2017 03:11:48 GMT
< ETag: "2003f3-8c-56080982c2783"
< Accept-Ranges: bytes
< Content-Length: 140
< Content-Type: text/html; charset=UTF-8
< Connection: close
<
<!DOCTYPE HTML>
<html lang="ja">
<head>
<title>HTMLのテストページ</title>
</head>
<body>
内部WEBサーバです!
</body>
</html>
* Closing connection #0
clientには問題なくコンテンツが返却されました。
またproxyサーバにはclientサーバ、webサーバにはproxyサーバ経由でリスエストが飛んでいることがわかります。
[root@proxy ~]# tail -f /var/log/httpd/access_log
192.168.100.100 - - [17/Dec/2017:13:48:00 +0000] "GET /app/foo.html HTTP/1.1" 200 140 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
[root@web ~]# tail -f /var/log/httpd/access_log
192.168.110.1 - - [17/Dec/2017:13:48:01 +0000] "GET /app/foo.html HTTP/1.1" 200 140 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
4.続いてProxyPassReverse
以下は公式ドキュメントのページからの引用です。
このディレクティブは Apache に HTTP リダイレクト応答の Location, Content-Location, URI ヘッダの調整をさせます。
これは、Apache がリバースプロキシ (ゲートウェイ) として使われているときに、
リバースプロキシを通らないアクセスを防止するのに重要です。
このようなアクセスは、リバースプロキシの背後にいるバックエンドサーバへの HTTP リダイレクトが原因で起きます。
上記の特別なリダイレクト用の HTTP レスポンスヘッダのみが書き換えられます。
Apache は他のレスポンスヘッダを書き換えたり、HTML ページの中の URL 参照を 書き換えたりすることはありません。
つまり、リバースプロキシされた HTML ページ内に 絶対 URL 参照が存在すると、
プロキシを通さずにアクセスする可能性があります。
この説明だけ見てもなにやらわかりませんので、こちらも実際にconfigして挙動を確認します。
ポイントは、 webサーバ(=公式ドキュメントの説明でいうところのバックエンドサーバ)でリダイレクト が発生する場合です。
リダイレクトが発生しない場合はProxyPassの時に確認できたように、コンテンツをproxyサーバから取得できました。
webサーバのconfigは以下。app/redirect_from.htmlからapp/redirect_to.htmlにリダイレクトされるようにしました。
Redirect permanent /app/redirect_from.html /app/redirect_to.html
仮にproxyサーバのProxyPassReverseをconfigしない場合にどのような挙動になるのか確認します。
※なおclientサーバから直接webサーバへアクセスできないように
※clientサーバで事前に
[root@client ~]# route add 192.168.110.200 reject
※としています。
それでは、clientサーバからリクエストします。
[vagrant@client ~]$ curl -v -L http://192.168.100.200/app/redirect_from.html
* About to connect() to 192.168.100.200 port 80 (#0)
* Trying 192.168.100.200... connected
* Connected to 192.168.100.200 (192.168.100.200) port 80 (#0)
> GET /app/redirect_from.html HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Host: 192.168.100.200
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Date: Sun, 17 Dec 2017 14:12:24 GMT
< Server: Apache/2.2.15 (CentOS)
< Location: http://192.168.110.200/app/redirect_to.html
< Content-Length: 332
< Content-Type: text/html; charset=iso-8859-1
< Connection: close
<
* Closing connection #0
* Issue another request to this URL: 'http://192.168.110.200/app/redirect_to.html'
* About to connect() to 192.168.110.200 port 80 (#0)
* Trying 192.168.110.200... Failed to connect to 192.168.110.200: Network is unreachable
* Success
* couldn't connect to host
* Closing connection #0
curl: (7) Failed to connect to 192.168.110.200: Network is unreachable
proxyとwebには以下のようにログが出力されています。
[root@proxy ~]# tail -f /var/log/httpd/access_log
192.168.100.100 - - [17/Dec/2017:14:13:28 +0000] "GET /app/redirect_from.html HTTP/1.1" 301 332 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
[root@web ~]# tail -f /var/log/httpd/access_log
192.168.110.1 - - [17/Dec/2017:14:13:29 +0000] "GET /app/redirect_from.html HTTP/1.1" 301 332 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
これは、clientサーバからリクエストし、proxyサーバ経由でwebサーバにアクセスしたときwebサーバでリダイレクトが発生しているため、
clientサーバに戻されたHTTPヘッダのLocationがリダイレクト先のwebサーバのURLになっていることが原因です。
実際にcurlの出力で Location: http://192.168.110.200/app/redirect_to.html となっています。
clientサーバ側でLocation先のURLにリダイレクトしようとするが、
clientサーバからのwebサーバへの直接のネットワーク経路が存在しないことから、clientサーバからのリクエストが届いていません。
ここでproxyサーバのconfigにProxyPassReverseを追加し、再度clientサーバから同じリクエストを飛ばします。
ProxyPassReverse /app/ http://192.168.110.200/app/
[vagrant@client ~]$ curl -v -L http://192.168.100.200/app/redirect_from.html
* About to connect() to 192.168.100.200 port 80 (#0)
* Trying 192.168.100.200... connected
* Connected to 192.168.100.200 (192.168.100.200) port 80 (#0)
> GET /app/redirect_from.html HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Host: 192.168.100.200
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Date: Sun, 17 Dec 2017 14:27:34 GMT
< Server: Apache/2.2.15 (CentOS)
< Location: http://192.168.100.200/app/redirect_to.html
< Content-Length: 332
< Content-Type: text/html; charset=iso-8859-1
< Connection: close
<
* Closing connection #0
* Issue another request to this URL: 'http://192.168.100.200/app/redirect_to.html'
* About to connect() to 192.168.100.200 port 80 (#0)
* Trying 192.168.100.200... connected
* Connected to 192.168.100.200 (192.168.100.200) port 80 (#0)
> GET /app/redirect_to.html HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Host: 192.168.100.200
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sun, 17 Dec 2017 14:27:34 GMT
< Server: Apache/2.2.15 (CentOS)
< Last-Modified: Sun, 17 Dec 2017 11:43:55 GMT
< ETag: "200407-a7-56087bfa32aae"
< Accept-Ranges: bytes
< Content-Length: 167
< Content-Type: text/html; charset=UTF-8
< Connection: close
<
<!DOCTYPE HTML>
<html lang="ja">
<head>
<title>HTMLのテストページ</title>
</head>
<body>
内部WEBサーバでリダイレクトしました!
</body>
</html>
* Closing connection #0
192.168.100.100 - - [17/Dec/2017:14:27:34 +0000] "GET /app/redirect_from.html HTTP/1.1" 301 332 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
192.168.100.100 - - [17/Dec/2017:14:27:34 +0000] "GET /app/redirect_to.html HTTP/1.1" 200 167 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
192.168.110.1 - - [17/Dec/2017:14:27:34 +0000] "GET /app/redirect_from.html HTTP/1.1" 301 332 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
192.168.110.1 - - [17/Dec/2017:14:27:34 +0000] "GET /app/redirect_to.html HTTP/1.1" 200 167 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
curlから出力されたLocation句が、 Location: http://192.168.100.200/app/redirect_to.html となっており、
clientサーバで、リダイレクトされたリクエストがproxyサーバ経由で再度リクエストされています。
結果として、clientサーバにリダイレクト先のコンテンツがproxyサーバ経由で取得できました。
5.まとめ
ApacheのProxyPass、ProxyPassReverseについて、HTTPヘッダやログを中心に挙動を確認しました。
ProxyPassReverseについてはproxy先のサーバでリダイレクトが発生した時にHTTPヘッダのLocation句を調整していることが分かりました。
参考
- [1]Apache HTTP サーバ バージョン 2.2:https://httpd.apache.org/docs/2.2/ja/mod/mod_proxy.html#proxypass
- [2]mod_proxy再入門:https://dev.classmethod.jp/server-side/server/introduction_mod_proxy/
- [3]鶴長鎮一著,「サーバ構築の実際がわかるApache運用/管理」,技術評論社,2012