最近はコードスニペットやtipsの管理にGistBoxを使っていて、ブログを書く機会がめっきり減ってしまいました。
気づけば今年初の記事なんですね。誰かの役に立ちそうなtipsは積極的にブログにも書いていきたいものです。
今回はAWSのELB(ロードバランサー)で、ポート443からポート80にポートフォワーディングしている場合にコントローラの$this->request->is('ssl')でSSL通信を判定できなくなる問題を解消します。
ちなみにSecurityコンポーネントのrequireSecureに追加したSSL判定にもrequest->isを使っているため、ELBでポートフォワーディングすると判定できなくなりますが、それも解消されます。
検証:CakePHP2.6.1
ELBでポートフォワーディングするとis(‘ssl’)で判定できなくなるワケ
コントローラの$this->request->isつまり、CakeRequestのisメソッドでは同クラスの$_detectorsというプロパティの値を利用して判定しています。
SSLの判定の場合には'ssl' => array('env' => 'HTTPS', 'value' => 1),の部分が利用され、環境変数(ENV)のHTTPS*1) が1の場合はtrue,そうでなければfalseを返すというふうになっています。
ELBにかぎらずですが、ポート443へのアクセスをポート80にフォワーディングしている場合、WEBサーバではhttp(ポート80)で答えるため、アプリケーションで取得できる環境変数HTTPSは0になるというわけです。
ただ、ELBではポートフォワーディングしたときにリクエストヘッダーにX-Forwarded-ProtoやX-Forwarded-Portを追加してくれるため、それを利用してSSL通信を判定することができます。
もちろん、これをそのまま利用して独自にSSL判定の処理を書くことも出来ますが、ここでは既存のCakeRequest::isを利用できるようにしてみます。
isの判定条件を変更する
CakeRequest::isの判定条件には$_detectorsプロパティを使っているので、これを修正します。
プロパティはprotectedですが、addDetectorで新しい判定条件*2 を追加することもできますし、既存の判定条件を上書きすることもできます。
isメソッドを使う前、たとえばAppControllerのbeforeFilterなどに
if (ELB経由のアクセスなら) {
$this->request->addDetector('ssl', array('env' => 'HTTP_X_FORWARDED_PROTO', 'value' => 'https'));
}
としてあげると、ELBが追加した環境変数をチェックするようになるので正しく判定できます。
その他にもさくらインターネットの共有SSLなどを利用する場合にも、HTTPSでは判定できず独自の環境変数HTTP_X_SAKURA_FORWARDED_FORが付与されるようなので、このtipsが利用できると思います。
参考:CakePHP(2.x)のSecurityComponentをさくらの共有SSLに対応させる – Qiita
応用編
CakeRequest::addDetectorを使うと、独自の判定も追加することができます。
例えば、私は本番環境のサーバにPRODUCT_ENVという環境変数を追加して1をセットしているのですが、これを利用して
$this->request->addDetector('product', array('env' => 'PRODUCT_ENV', 'value' => 1));
とすると本番環境を判定することができますね。
$_detectorsを見ると環境変数がキーになれば正規表現や複数の値のマッチにも対応しているようなので、他にも自分なりの判定条件を色々書けそうなのでチェックしてみてください。
参考サイト
Elastic Load Balancing の概念 – Elastic Load Balancing
CakePHPでロードバランサ配下のサーバでHTTPSのリクエストが正常に判断されない問題 – 【鋭利団体】PK-Brothers
CakePHP(2.x)のSecurityComponentをさくらの共有SSLに対応させる – Qiita
いつも先人の皆様の知恵に感謝しています!
[20160602追記]ポートフォワーディングされたSSLで強制的にhttpsにリダイレクトする
どうもCakePHP+ELB環境でhttpでのアクセスをhttpsにリダイレクトする直接的な方法を求めて来られる方が多そうなので、方法を追記しておきます。
まずAppControllerのbeforeFilterで一括でhttpアクセスをhttpsにする方法。
// AppController
public function beforeFilter() {
parent::beforeFilter();
// ロードバランサへHTTPでアクセスされた場合
if ( isset($_SERVER['HTTP_X_FORWARDED_PORT']) && $_SERVER['HTTP_X_FORWARDED_PORT'] == 80) {
// ベースURLをHTTPSに書き直す
$this->redirect('https://' . env('SERVER_NAME') . $this->here);
exit;
}
}
特定のコントローラ、アクションのみ対応させたい場合はAppControllerのbeforeFilterで、$this->request->params['controller']とか$this->request->params['action']を判定することもできるけど、この記事の最初のほうでやってる$this->request->addDetector('ssl', ...)で$this->request->is('ssl')判定できるようにしておけば、各アクションや各コントローラのbeforeFilterで上記のコードを書くことで対象のコントローラ、アクションにだけ簡単に処理を追加できます。
あとがき
ひさびさに対外向けの文章を書きましたが、ひどく冗長ですね。。。
昔のほうが馬鹿だったけど思い切りいい記事をかいてたなあと思います。
今年に入って色々と変化が出てきたので、このブログにもそういう良いところが出せていけたらいいな〜なんて思ってます。
