先月、新規システムをCakePHPで作ったときに表題のことを久し振りにやったのですが、色々と忘れていたのでここにメモしておきます。
問題
CakePHPのControllerにて以下のようなリダイレクトを返すとき、リダイレクト先のURLをHTTPSつまり https://example.com/
で返して欲しいのに平文HTTPつまり http://example.com/
で返されてしまいます。
その結果、LBでは平文HTTPを受け付けていないので、Webブラウザはサイトにアクセスすることができなくなります。
return $this->redirect(['action'=>'index']);
なぜ、この問題が発生するのか?
一言で言うと、CakePHPの立場では「平文のHTTPでリクエストが来た」ように見えるから、です。
コードで言うと、アプリの config/bootstrap.php
にある fullBaseUrl
自動生成ロジックが、リクエストを平文HTTPであると認識してしまっていました。
// 出典 : https://github.com/cakephp/app/blob/472c9e21e51909dda05336a7072eb5394334eb6a/config/bootstrap.php#L145-L148 $s = null; if (env('HTTPS')) { $s = 's'; }
変数 $s の値がs
であるとき、作られるfullBaseUrlは https://
となりますが、前述の前提条件のもとで特段何も設定をしない場合は env('HTTPS')
の値はnullとなるので、平文扱いとなります。
解決策その1:環境変数を仕込む
上記のコードから、環境変数 HTTPS
を設定できれば解決することがわかります。
このメモを書き留めるにあたって、この環境変数の挙動の流れについて(Apache + mod_php 限定ですが)詳しく書かれた記事を見つけたのでリンク貼っておきます。
qiita.com
解決策その2:bootstrap.php をいじる
多くのLB、例えばAWSのALBであれば、外部からHTTPSで来たリクエストは、背後のアプリケーションサーバに送る際には、リクエストヘッダ X-Forwarded-Proto
を付加するようになっています。これを利用すると、前述の bootstrap.php を以下のように書き換えることで、LB配下で稼働する場合にHTTPS認定をうまくやってくれるようになります。
$s = null; if (env('HTTPS') || env('HTTP_X_FORWARDED_PROTO') === 'https') { $s = 's'; }
解決策その3:fullBaseUrl を仕込む
例えば、CLI(CakePHP流に言うと Console Command)からメールを配信する機能があって、そのメールには当該WebアプリのURLを記載するようにする要件がある場合、これがもっとも望ましい解決策でしょう。何故ならばHTTP/HTTPS云々以前に、ホスト名等の設定が必要であるからです。
筆者はどの解決策を採用したのか?
管理の手間等を踏まえると、解決策その2が一番簡単だと判断して採用していました。2.x系の頃の記憶はもう吹き飛びましたが、3.x系の頃からこうしてきました。(そして、久しぶりにやろうとして忘れていたので、メモとしてこの記事を書き始めました)。
…ところが最近になって、先月作ったアプリケーションにCLIからのメール送信の機能を追加することになりそうなので、そのアプリでは解決策その3に乗り換えようとしているところです。
なぜ解決策その2はデフォルトでは入っていないのか?
アプリケーションのフレームワークとしては、解決策その2の対応はデフォルトで入っていた方が良いのでは?と思われる方はそれなりにおられるのではないかと思いますが、デフォルトで入れておくと、ちょっとした問題があります。
github.com
HTTPSと平文HTTPとを両方使用している環境であり、かつ、LBを使っていない環境において、proxy鯖や中間者攻撃などによりリクエストヘッダがいじられることで、平文HTTPのURLを作るべきところで意図せずHTTPSのURLを作らせることができる、という攻撃が成立します。この攻撃によって何か被害が発生する構成というのは、現代では極めて稀(または筆者のセキュリティ能力や想像力の不足😇)だろうとは思いますが、上記の英語のコメントのように、フレームワークを作る側としては当時(2013年)はちょっと勇気が出なかったようです。
ちなみに、2022年3月19日(日本時間)に、アプリのスケルトン(テンプレート)が改修されて、少し楽になりました。
github.com
この修正によって、bootstrap.phpのロジックは↓のようになりました。
/*
* When using proxies or load balancers, SSL/TLS connections might
* get terminated before reaching the server. If you trust the proxy,
* you can enable `$trustProxy` to rely on the `X-Forwarded-Proto`
* header to determine whether to generate URLs using `https`.
*
* See also https://book.cakephp.org/4/en/controllers/request-response.html#trusting-proxy-headers
*/
$trustProxy = false;
$s = null;
if (env('HTTPS') || ($trustProxy && env('HTTP_X_FORWARDED_PROTO') === 'https')) {
$s = 's';
}
なので、3月19日以降にアプリケーションを新規に開発し始めた場合、つまり当日以降に composer create-project --prefer-dist cakephp/app:4.* hogehoge
を実行したプロジェクトは、この trustProxy
を true
に変えるだけで、解決策その2を適用できることになります。
できればこの trustProxy
の値は、ServerRequestのtrustProxyと連動して欲しいとは思うものの、まぁ開発が面倒なのはわかります。。