2012年1月アーカイブ

hb qp bp study 新年LT&ビアバッシュ2012に参加して発表してきました。

資料はこちら

内容は今年の目標と俺俺waresの紹介です。

ビール飲んでピザ食べて、面白いLT聞けて、みなさん大人でとても良い会でした。

Reverse Proxyの後ろでApplication Serverを動かす際に、REMOTE_HOSTを本当のアクセス元に書き換えてくれる仕組みはいくつかありますが^1、Plackでは壇上氏の Plack::Middleware::ReverseProxy がそれにあたります。

^1 例えば mod_extract_forwarded http://www.openinfo.co.uk/apache/

PM::ReverseProxy のSYNOPSISでもそうなってますが、このような仕組みを使う場合、REMOTE_HOSTを指定するのが安全です。

builder {
    enable_if { $_[0]->{REMOTE\_ADDR} eq '127.0.0.1' } 
        "Plack::Middleware::ReverseProxy";
    $app;
};

拙作の Plack::Builder::Conditionals を使うと以下のように書けます。

use Plack::Builder;
use Plack::Builder::Conditionals;

builder {
    enable match_if addr(['192.168.0.0/24','127.0.0.1']),
        "Plack::Middleware::ReverseProxy";
    $app
};

このようにREMOTE_HOSTを指定するのは、改ざんされたリクエストを防ぐためです。

PM::ReverseProxyの仕組みは以下の図のようになります。

reverseproxy1.png

ブラウザ(IPアドレス:v.x.y.z)からReverse Proxyに対してリクエストを行うと、Reverse Proxyは「X-Forwarded-For」ヘッダにREMOTE_ADDRを追加してApplication Serverにリクエストします。Application Serverでは当然REMOTE_ADDRがReverse Proxyの物(ここではlocalhost)になりますが、PM::ReverseProxyはX-Forwarded-Forをみて、REMOTE_ADDRを上書きします。これにより本来のアクセス元がApplication ServerでもREMOTE_ADDRで取得できます。

ここで、Application Server側でREMOTE_ADDRを確認しないと、以下のような事態がおきます。

reverseproxy2.png

X-Forwarded-Forヘッダに任意のIPアドレス(v.x.y.z)を追加し、Application Serverに直接アクセスするとPM::ReverseProxyは同じようにREMOTE_ADDRを設定します。本来REMOTE_ADDRは「192.168.9.41」にも関わらず、「v.x.y.z」と名乗れてしまいます。もし、v.x.y.zからしか閲覧することができないコンテンツがあっても、この方法で見る事ができてしまいます。

use Plack::Builder;
use Plack::Builder::Conditionals;

builder {
    enable 'ReverseProxy';
    mount '/private' => builder {
        enable match_if addr('!','v.x.y.z'), 
            sub { sub { [403,['Content-Type'=>'text/plain'],['Forbidden']] } };
        $private_app;
    };
    $app
};

↑こういうの危険。

Application Serverに直接アクセスできないのは普通だと思いますが、なんらかの設定漏れでアクセスができてしまうことも考えられます。念をいれてREMOTE_ADDRをチェックするのをお勧めします。

Plack::Middleware::AccessLog は Apacheライクなログが残せる便利ミドルウェアなんですが使う上で一つ注意点があります。

use Plack::Builder;

builder {
    enable "AccessLog", format => "combined";
    sub { die };
};

これで、500エラーのログが残ることを期待するかもしれませんが、実際は記録されません。

例外がMiddleware層を飛ばしてServerまで伝わる為で、何らかの形で例外を補足してあげる必要があります。

例えば、Plack::Middleware::HTTPExceptions

builder {
    enable "AccessLog", format => "combined";
    enable "HTTPExceptions";
    sub { die };
};

もしくは自前でMiddlewareを書く方法。

use Plack::Builder
use Try::Tiny

builder {
    enable "AccessLog", format => "combined";
    enable_if { ($ENV{PLACK_ENV} || '') ne 'development'} sub {
        my $app = shift;
        sub {
            my $env = shift;
            try {
                $app->($env);
            } catch {
                $env->{'psgi.errors'}->print($_);
                [500,['Content-Type'=>'text/plain'],['Internal Server Error']];
            }
        }
    };
    sub { die };
};

やってることはHTTPExceptionsとほぼ同じです

Middlewareを使う場合は、miyagawaさんのslideにも登場する。この図を思い浮かべるといいと思います。

pylons_as_onion.png

pylonsのドキュメントから転載です