サーバーがロードバランサー配下にある場合の対処法

Katz Ueno
Katz Ueno

久しぶりのブログ投稿するカツです。

concrete5 を本番公開した瞬間に下記のようなエラーに直面して頻繁にログアウトされてしまうことはありませんか?

Session Invalidated. Session IP "XXX.XXX.XXX.XXX" did not match provided IP "XXX.XXX.XXX.XXX".

concrete5 の前に AWS の ELB のようなロードバランサーを配置する場合、config で trusted_proxies を設定する必要があります。

以前の記事「ロードバランサーを通してconcrete5のセッションを維持する」や「Cloud9 で concrete5 開発環境を作る」では invalidate_on_ip_mismatch を false にしたり、$_SERVER['REMOTE_ADDR'] を trusted_proxies として設定しています。本番環境ではNGなやり方です。正規なやり方として、ロードバランサーの IP アドレスのみを trusted_proxies として登録してください。

 

設定サンプル /application/config/concrete.php

設定ファイルにロードバランサーの IP アドレスを、IP アドレスや CIDR 形式で登録してください。

複数 IP アドレスがある場合は複数行で登録できます。

 
<?php
return [
    'security' => [
        'trusted_proxies' => [
            'ips' => [
                '【ロードバランサーのIPアドレス】',
            ],
        ]
    ]
];

ファイルアップロード後、concrete5 のキャッシュをクリアすることも忘れずに。

 

AWS の ELB (Elastic Load Balancer) の場合

AWS では VPC の設定を参照し、EC2 が存在する VPC の CIDR ブロックを設定します。これは CLB (Classic Load Balancer) でも ALB (Application Load Balancer) でも同じです。

 

ELB にどのようなIPアドレスが振り分けられるのか・・・、 AWS の公式ドキュメントによると

ロードバランサーが正しくスケーリングできるように、ロードバランサー毎に各サブネットの CIDR ブロックを、最低でも /27 ビットマスク (例: 10.0.0.0/27) にし、少なくとも 8 個の空き IP アドレスを用意してください。ロードバランサーはこれらの IP アドレスを使用して、インスタンスとの接続を確立します。

各 Subnet に 30個以上の IP アドレスの空きを作ってほしいということ。そして VPC の Subnet に8個以上の IP が振り分けられると書いてあります。具体的にどの IP に振り分けられるとは書いていないので、VPC の CIDR ブロックを設定することになります。

※セキュリティグループを強固にするのも忘れずに。

 

例:自分の VPC の CIDR ブロックを見つける方法

VPC の CIDR ブロックの範囲が下記の AWS Management Console で使用しているEC2インスタンスの VPC を見つけます。

そこの「IPv4 CIDR」の項目を探しましょう。

vpc.png

 

/application/config/concrete.php 内の設定は下記のようになります。

<?php
return [
    'security' => [
        'trusted_proxies' => [
            'ips' => [
                '10.0.0.0/16',
            ],
        ]
    ]
];

 

この設定を concrete5 サイトにアップロードしてください。

ファイルアップロード後、concrete5 のキャッシュをクリアすることも忘れずに。

 

LB で SSL 証明書を設定している場合

ロードバランサー側のみで SSL 証明書を設定している場合、カノニカル URL の設定をして強制的に https になるように設定します。

コマンドラインツールからは下記のように設定します。必要に応じて sudo -u apache/nginx オプションを付けて、ウェブサーバーと同じユーザーとして実行してください。

/concrete/bin/concrete5 c5:config set -g site.sites.default.seo.canonical_url [URL]

もしくは /application/config/site.php に下記のように設定します。

<?php
return [
    'sites' => [
        'default' => [
            'seo' => [
                'canonical_url' => '[URL]',
                ],
            ],
        ],
    ],
];

この設定を完了しないと、うまくサイトの遷移がいかなくなる場合があります。

 

頻繁にログアウトが起きてしまう原因と詳しい対策の内容

ちょっと専門的になってしまうので、読み飛ばしていただいても結構です。

concrete5 がベースとして使用している PHP フレームワーク Symfony のセキュリティ機能の仕様によるものです。

concrete5 では、ユーザーのアクセス元 IP アドレスが変わると、強制ログアウトするというセキュリティ機能があります。

例えば、悪意ある人物が、何らかの方法で正しくログインしたユーザーのセッション情報を盗み取り、違う IP アドレスからアクセスするというケースがあるからです。

ロードバランサーを介すると、concrete5 から見えるアクセス元の IP アドレスはロードバランサーの IP アドレスになってしまいます。

ロードバランサーによっては複数台運営されている場合があります。同じユーザーがアクセスしていても別のロードバランサーを介してアクセスしてしまいます。つまり 違う IP アドレスからのアクセスになるので concrete5 でセキュリティ機能が働き、ログアウトされます。

ただ、ロードバランサーでは、通常 X-Forwarded-For というヘッダーを追加し、元々の IP アドレスを情報に追加しています。

concrete5 に、このIP アドレスのロードバランサーは、信頼できるプロキシーサーバー (trusted proxies) だから、X-Forwarded-For の情報を信用して良いよという設定情報を追加します。

こうすることで concrete5 は、ロードバランサーの IP アドレスではなく X-Forwarded-For のヘッダー情報に書かれている IP アドレスをユーザーの IP アドレスとして認識し、突然のログアウト現象が解決されます。

なぜこういう設定を追加する必要があるかと言うと X-Forwarded-For の IP アドレス情報は誰でも任意に追加できます。つまり改ざんが可能だからです。

 

また、SSL の設定にも原因がある場合があります。

SSL 証明書の設定はロードバランサーで行い、ネットワーク内の通信は 80 番ポートの非SSL通信している場合があります。

AWS の ELB に、無料 SSL 証明書を割り当て、ELB -> EC2 の間は80番ポートの非SSL通信をするなどが具体的な例です。

その場合、concrete5 側は http:// で通信が来ているので、ページを出力する際に、あらゆる場所が http として出力されてしまいます。

https なログインページのフォームから、http なページにログイン情報を投げることになり、ELB でのリダイレクトが走って、ログインの POST データがトランズアクション内で消えてしまうという現象も起こりかねます。

 

以上