Edited at
SymfonyDay 12

TrustedProxy, getClientIps などロードバランサ配下での挙動について

※この投稿はSymfony3.4.20で調べた内容を元に書いています。


本番リリースしたとたん絶対URLがhttpsにならない(焦

ロードバランサー(SSLオフロード機能有効)配下にSymfonyのアプリケーションを配置してアプリケーション内でURLを生成したところ、URLがhttpで生成されてしまいました。


IndexController.php

class IndexController extends Controller

{
public function indexAction(Request $request)
{
$absoluteUrl = $this->generateUrl('ROUTE_NAME', [], UrlGeneratorInterface::ABSOLUTE_URL);
// http://... になってしまった
}
}

対応策は、web/app.phpで


app.php

$request = Request::createFromGlobals();

Request::setTrustedProxies(
['CIDR表記'],
Request::HEADER_X_FORWARDED_ALL
);
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

をセットすることです。

ロードバランサー配下では、クライアントがロードバランサーへの接続に使用したプロトコルは$_SERVER['HTTPS']ではなく、X-Forwarded-Protoヘッダに記録されます。

そう、SymfonyはRequest::setTrustedProxies()を設定しさえすれば、HTTPS接続かを見る箇所を切り替えてくれるのです。なんと親切なんでしょう。

Symfonyのソースコードを眺めてみます。

Symfony\Component\HttpFoundation\Requestクラスに


Request.php

    /**

* Gets the request's scheme.
*
* @return string
*/

public function getScheme()
{
return $this->isSecure() ? 'https' : 'http';
}

isSecureの中身は


Request.php

    public function isSecure()

{
if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_CLIENT_PROTO)) {
return in_array(strtolower($proto[0]), array('https', 'on', 'ssl', '1'), true);
}

$https = $this->server->get('HTTPS');

return !empty($https) && 'off' !== strtolower($https);
}


Request::setTrustedProxies()の設定にひっかかれば、前半のif文の中の処理で動作しています。


IPアドレスでも同じことが…

ロードバランサー配下では、クライアントがロードバランサーへの接続に使用したIPアドレスは$_SERVER['REMOTE_ADDR']ではなく、X-Forwarded-Forヘッダに記録されます。

Request::geClientIp()もきちんと対応してくれています。


Request.php

    public function getClientIp()

{
$ipAddresses = $this->getClientIps();

return $ipAddresses[0];
}
public function getClientIps()
{
$ip = $this->server->get('REMOTE_ADDR');

if (!$this->isFromTrustedProxy()) {
return array($ip);
}

return $this->getTrustedValues(self::HEADER_CLIENT_IP, $ip) ?: array($ip);
}


getClientIp()は、getClientIps()で配列で返ってきたIPの先頭を返しています。

ということは、getClientIps()がIPアドレスを複数返すことがあるんです。

これはどういうケースかというと WAF → LB → アプリケーション のようにプロキシを2段かましたときに、X-Forwarded-ForにIP1, IP2のように記録されるからです。

SymfonyはこんなIPにもきっちり対応して、カンマでIPを分解して処理してくれています。