読者です 読者をやめる 読者になる 読者になる

てきとうなメモ

本の感想とか技術メモとか

X-Forwarded-Hostヘッダの扱い

Web Ruby Python Perl Rails Plack Django

Railsで自分のサーバ名を知ろうとしてrequest.host_with_portを使おうとして、多段reverse proxy下だったのでめんどうなことになった。

Rails(Rack)だと

    def host_with_port
      if forwarded = @env["HTTP_X_FORWARDED_HOST"]
        forwarded.split(/,\s?/).last
      else
        @env['HTTP_HOST'] || "#{@env['SERVER_NAME'] || @env['SERVER_ADDR']}:#{@env['SERVER_PORT']}"
      end 
    end 

となっていてX-Forwarded-Hostヘッダがあればこれの最後の値を利用しようとしている。X-Forwarded-Hostはproxyが受け取ったHostヘッダを本当の宛先に送るヘッダ(非標準)。以下のような多段proxyの場合

client -> proxy1 -> proxy2 -> ... -> proxyN -> server

serverが受け取るX-Forwarded-Hostは

proxy1, proxy2, ..., proxyN

という形式になる。

そのため、host_with_portはproxyNを返すのだがこちらとしてはproxy1が外から見えるホスト名なのでproxy1を返してほしい。

アプリ側はいじれなかったのでApacheの設定で無理矢理proxyNがサーバに

Host: proxy1

と送るようにしたけど。

なんで最後を返すのかがわからずぐぐってみたがやっぱりわからない

を見てredirectがうまくいくようにということみたいだけども、redirectの場合もproxy1を返してほしい気がする。

で他のフレームワークはどうだろうと思ってちょっとしらべてみる

Django

    def get_host(self):
        """Returns the HTTP host using the environment or request headers."""
        # We try three options, in order of decreasing preference.
        if settings.USE_X_FORWARDED_HOST and (
                'HTTP_X_FORWARDED_HOST' in self.META):
            host = self.META['HTTP_X_FORWARDED_HOST']
        elif 'HTTP_HOST' in self.META:
            host = self.META['HTTP_HOST']
        else:
            # Reconstruct the host using the algorithm from PEP 333.
            host = self.META['SERVER_NAME']
            server_port = str(self.META['SERVER_PORT'])
            if server_port != ('443' if self.is_secure() else '80'):
                host = '%s:%s' % (host, server_port)

設定でUSE_X_FORWARDED_HOSTを有効にしている場合のみX-Forwarded-Hostを利用している。これについては以下で議論されている

確かにX-Forwarded-Hostをどこまで信用すべきなのかという話もあるし、forward proxyの情報が削除されていない場合はどうするのとかもあるし、あんまりフレームワークががんばってもしょうがない気がする。

あと、複数値ある場合には対応していないっぽい

Plack

sub _uri_base {
    my $self = shift;

    my $env = $self->env;

    my $uri = ($env->{'psgi.url_scheme'} || "http") .
        "://" .
        ($env->{HTTP_HOST} || (($env->{SERVER_NAME} || "") . ":" . ($env->{SERVER_PORT} || 80))) .
        ($env->{SCRIPT_NAME} || '/');

    return $uri;
}

X-Forwarded-Hostは利用していないみたい。

まとめ

まあ、X-Forwarded-Hostはフレームワークによっては対応していないし、あんまり信用すべきでもない。