LoginSignup
5

More than 3 years have passed since last update.

posted at

updated at

nginx、怖いリバースプロキシの話(正規表現のキャプチャがURIデコードされて困った件)

自分への備忘録として。

http://example.com/api/hogehoge?foo=bar
にアクセスされたら、
http://example.com:8000/hogehoge?foo=bar
という形でリバースプロキシさせたいとき、ついついこんな風に書きたくなります。

location ~ ^/api/(.*)$ {
    proxy_pass http://example.com:8000/$1$is_args$args;
}

が、これは間違い。下のように書くべき。

location ~ ^/api/.*$ {
    rewrite ^/api/(.*)$ /$1 break;
    proxy_pass http://localhost:8000;
}

どういうことかというと、nginxのlocationディレクティブにおける正規表現のキャプチャは、uriデコードされたものがキャプチャされてしまうのです。

なので、もし
http://example.com/api/hoge%20hoge
こういうURIエンコード済みのURIだった場合、
"http://example.com:8000/hoge hoge"
こんな空白入りのURIになってしまい、502 Bad Requestとかで死にます。

それを阻止するには、デコード前の状態をキャプチャしないとダメで、それを行うにはrewriteディレクティブを使います。

これは、公式ドキュメントにも書いてあります。
http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass

If the proxy_pass directive is specified with a URI, then when a request is passed to the server, the part of a normalized request URI matching the location is replaced by a URI specified in the directive:
(中略)
When the URI is changed inside a proxied location using the rewrite directive, and this same configuration will be used to process a request (break):

location /name/ {
rewrite /name/([^/]+) /users?name=$1 break;
proxy_pass http://127.0.0.1;
}

In this case, the URI specified in the directive is ignored and the full changed request URI is passed to the server.

しかも、rewriteに後続する、proxy_passディレクティブには変数とかは使用してはいけません。余計なものを付けなければ、変更された完全なリクエストURIがサーバーに渡されます。

あー怖かった。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
5