最近の環境では、Dockerなどを用いて環境を構築することが多くなり、自前でOS及び周辺のミドルウェアを構築することは少なくなったと思います。

そのため、自前のDockerイメージを作成し、開発・運用共に同じイメージを使うなどは日常茶飯時です。

僕の環境では、開発時はHTTPで行い、運用時はHTTPSを利用します。(ローカルでHTTPSを利用できなくもないですが。。。。証明書関係が面倒なので。。)

環境

グローバルにNGINXで構築したプロキシサーバーがあります。インターネットからのアクセスがプロキシに届くと、イントラネット側のアプリケーションサーバーを呼び出し応答します。

「利用者」->「awesome.app」->「awesome01.local」といった順でリクエストは流れていきます。

今回は、プロキシサーバーとして「Nginx Proxy Manager」を利用しています。

自前のLBや、AWSのALBでもおそらく考え方は同じです。(細かく見ると違いますが。)

良くない対応策

  • URL::forceScheme()secure_url()などURLを強制的にSSL化する。
  • 自前のミドルウェアを用意して判別するロジックを作る

良い対応策

Laravelには、標準で「TrustedProxies」というミドルウェアが存在します。TrustedProxiesとプロキシサーバーを噛み合うように設定することでrouteassetで生成されるリンクがHTTPSになります。

詳しくは、公式ドキュメントを読んでみてください。

「Nginx Proxy Manager」で詰む

「Nginx Proxy Manager」をプロキシサーバーとし、設定を行ったところページは表示されるが、アセット類やrouteを使用した箇所で生成されたリンクが「https://sample.com:80/auth」と誤って生成されていました。

Laravel ソースコードを読み解く

ソースコードを追って調べてみたところ仕組みとしては、「Illuminate\Routing\RouteUrlGenerator」クラスの「addPortToDomain」メソッドに原因があることがわかりました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/**
 * Add the port to the domain if necessary.
 *
 * @param  string  $domain
 * @return string
 */
protected function addPortToDomain($domain)
{
    $secure = $this->request->isSecure();

    $port = (int) $this->request->getPort();

    return ($secure && $port === 443) || (! $secure && $port === 80)
                ? $domain : $domain.':'.$port;
}

$secure$portを用いてポート番号を付けるか付けないかを判断しているロジックですが、HTTPSでアクセスした際、アプリケーションサーバー側をHTTPで運用している場合でも、セキュアなアクセスとして認識されます。ポート番号もプロキシサーバーが受け付けたポート番号になるのが正常な状態です。これは、「X-Forwarded-*」ヘッダーをLaravelがうまく処理してくれるおかげです。

それゆえに、「X-Forwarded」ヘッダーの情報が誤っているとうまくプロキシとの情報が噛み合わなくなり結果、意味不明なURLを生成するような状態になってしまいます。

原因としては、「Nginx Proxy Manager」の設定してる「X-Forwarded-Port」の値が誤っている。が結論です。

セキュアなアクセスだが、ポート番号は80番と認識してしまったため、「https://sample.com:80/auth」と生成してしまったようです。

おわりに

フレームワークで便利になる部分はしっかり使いこなし、依存しすぎないバランスの取れた使い方ができるように精進したいものです。