Cloud Armor を利用して Google Cloud 上の Web アプリケーションを保護する際、Cloud Load Balancing の前段に Google Cloud 以外の CDN やプロキシが存在すると、Cloud Armor が参照するソース IP がすべて CDN の IP アドレスになってしまう課題があります。
今回は、Cloud Armorの「ユーザーIP参照設定」を活用し、X-Forwarded-For ヘッダー (以下、XFF) からクライアント IP を特定して、ログ出力やレート制限、フィルタリングを行う方法をまとめます。
外部 CDN 併用時の IP 参照問題
通常、Cloud Armor は接続元の IP(remoteIp)をベースにルール判定を行います。しかし、外部 CDN を経由する場合、Cloud Armor から見た接続元は常に CDN のノードになります。
例えば、以下のような構成の場合です。

この状態では、特定のユーザー IP をブロックしようとしても CDN の IP をブロックすることになり、正常な通信まで巻き添えにしてしまいます。また、ログにもユーザーの IP が残らないため、分析が困難になります。
1. ユーザーIPの参照設定
Cloud Armor のセキュリティポリシーには、特定の HTTP ヘッダーの値をユーザー IPとして扱う設定があります。
Google Cloud コンソールで設定する場合は、セキュリティポリシー編集画面の「User IP request headers configuration」にて、参照したいヘッダー名を追加します。

この設定を有効にすると、Cloud Load Balancing(CLB)のログの jsonPayload.securityPolicyRequestData 配下に userIpInfo という項目が追加されます。
設定前: remoteIp のみログに含まれる
{
// ...
"jsonPayload": {
// ...
"remoteIp": "192.0.2.1", // Cloud Armorが参照する接続元のIP(CDNのIP)
"securityPolicyRequestData": {
"remoteIpInfo": {
// ...
}
}
},
"httpRequest": {
// ...
"remoteIp": "192.0.2.1", // CDNのIP
// ...
},
// ...
}
設定後: jsonPayload.securityPolicyRequestData.userIpInfo.ipAddress に真のクライアント IP が出力される
{
// ...
"jsonPayload": {
// ...
"remoteIp": "192.0.2.1", // Cloud Armorが参照する接続元のIP(CDNのIP)
"securityPolicyRequestData": {
// ...
"userIpInfo": {
"ipAddress": "198.51.100.25", // X-Forwarded-Forから抽出された真のクライアントIP
"source": "X-Forwarded-For"
}
}
},
"httpRequest": {
// ...
"remoteIp": "192.0.2.1", // CDNのIP
// ...
},
// ...
}
なお、XFF は、複数のプロキシを経由した場合は 右側に追加されていく仕様 となっています。
User IP request headers configuration では最も左側の IP を参照します。
また、Cloud Armor のログ項目についてはこちらの記事でまとめています。
2. ルールの式でヘッダーIPを使用する
標準モード(IPを指定するルール)で単純に Allow/Block するルールが参照する IP アドレスは、ソース IP のままです。
ユーザー IP の参照設定をすることで、ユーザーIP (origin.user_ip) として記録されるため、詳細モードを用いて、この値を利用したカスタムルール式 (Common Expression Language) を定義することが可能になります。
例
inIpRange(origin.user_ip, '198.51.100.25/32')
- 参考:カスタムルール言語属性
ヘッダー IP を参照するブロックルールの検証
以下の2つの詳細ルールをプレビューモードで作成し、検知状況をログで確認しました。
origin.ipを参照する Action: Deny のルールorigin.user_ipヘッダー IP を参照する Action: Deny のルール
同じく接続元を拒否するルールで、参照する IP のみ origin.ip と origin.user_ip で異なっています。

CDN 経由でアクセスした場合、1番のルールにはヒットしませんが、2番の origin.user_ip を参照するルールでは正しく検知されることが確認できました。
ログ出力例
{
// ...
"jsonPayload": {
// ...
"remoteIp": "192.0.2.1", // Cloud Armorが参照する接続元のIP(CDNのIP)
"securityPolicyRequestData": {
// ...
"userIpInfo": {
"ipAddress": "198.51.100.25", // X-Forwarded-Forから抽出された真のクライアントIP
"source": "X-Forwarded-For"
}
},
// ...
"previewSecurityPolicy": { // プレビューモードで検知されたルール
"outcome": "DENY",
"priority": 2000,
"name": "armor-demo-policy",
"preview": true,
"configuredAction": "DENY"
}
},
"httpRequest": {
// ...
"remoteIp": "192.0.2.1", // CDNのIP
// ...
},
// ...
}
3. ヘッダーIPによるレート制限
特定のアクセス元からの短時間での大量アクセスを制限するレート制限ルールでは、1. の設定なしでもヘッダー IP を利用可能です。
- 参考:レート制限の概要
レート制限の設定例
ルールの Action の「Enforce on key configuration」において、Type: X-Forwarded-For IP address を選択します。

補足と注意事項
アクセス経路の制限
上記で紹介したレート制限ルールでは、XFF を持たないリクエストはレート制限されません。CLB へのリクエストが CDN 経由に限られる場合は、アクセスを CDN 経由に絞るルールを別途設定するなどの対策を推奨します。外部 CDN の IP 範囲のみを許可する、カスタムヘッダーによる検証を行う、などが考えられますが、ここでは詳細な設定方法は割愛します。
レート制限ルールの優先順位
ヘッダー参照の利用有無には関係ない仕様ですが、Cloud Armor のレート制限ルールでは、レート閾値を超えなかったリクエストは Action: Allow となり、それ以降のルールでは検査されません。
クライアントのトラフィック レートが rate_limit_threshold_count 以下の場合、リクエストには conform_action(これは常に allow アクションです)が適用されます。
そのため、レート制限ルールはその他のルールより後に評価されるように優先度の値を大きく設定する必要があります。
XFF の偽装
X-Forwarded-For などのヘッダーに含まれる IP アドレスは、クライアント側で自由に値を設定することが可能です。
IP を偽装してレート制限を回避する可能性を完全には排除できないため、ソース IP を参照する場合に比べると、確実な制限は難しくなります。
CDN を経由しないアクセスもある場合
構成としては少ないと思いますが、単純に Condition で Match * を設定した XFF をキーとするレート制限ルールの場合、指定したヘッダーが存在しないリクエストは、すべて「ALL」という1つの共通キーとしてカウントされます。
そのため、CDN を経由しないアクセスはすべて一つのグループとしてカウントされ、一括で制限がかかってしまいます。
has(request.headers['X-Forwarded-For']) という条件をルールに付与することで、ヘッダーありのリクエストのみをこの制限の対象にすることが可能です。
まとめ
外部 CDN と Google Cloud を組み合わせて利用する場合、CDN 側の WAF 機能を使用する選択肢もありますが、Cloud Armor のユーザー IP 参照設定を適切に行うことで、以下を実現可能です。
- CLB ログへのクライアント IP 出力
origin.user_ipを用いた特定のユーザーIPのブロック
また、レート制限ルールにおける X-Forwarded-For などのヘッダーをキーとした設定方法も紹介しました。
Google Cloud Armor 運用のお役に立てば幸いです。