Azure Hub&Spoke構成とAppGWとAzure FirewallとX-Forwarded-Forと私
久しぶりすぎる更新です。イミフなタイトルは生暖かい目で見てくださいw
昨今ではAzure vNET Hub&Spoke構成がだんだん主流になってきましたね。それもそのはずCAFやWAF(Well-Archited Frameworkのほう)といったAzure上でシステムを構成する際に気を付けるべき観点や、ベストプラクティスとなる基本構成などがオフィシャルサイトでも登場し徐々に浸透してきたからでしょうか。(そうだろう、きっと、そう思うようにしようw)
ただ、一方でHub&Spoke構成をとった際に、今まで通りにロードバランサを入れてバランシングしたい、といったシンプルな要求に対して、Hub&Spoke構成だとSpoke-vNET側ではUDR(User Defined Route)でスタティックルーティングを入れているからAppGWをソースIPとした通信がAzure Firewallに通信が丸投げされてAzure Firewall側でパケットドロップされちまうがな、TCPが確立しない、といった非対称ルーティングの問題が起きやすくなっていることも裏ではあります。
オフィシャルサイトでは以下のような例も出てきています。
Azure Firewall と Azure Standard Load Balancer を統合する | Microsoft Docs
ここではStandard LBとAzure Firewallを共存させる構成で描かれていますが、ぶっちゃけこの構成が取れるならStandard LBにPublic IPは不要です。Public Standard LBではなくInternal Standard LBにし、Internal Standard LBはSpoke-vNET側に配置します。また、Azure FirewallからInternal SLBのPrivate IPアドレスへ許可ルールを指定してあげれば済む話です。
いや、それだとWebサーバーを担当するエンジニア(Spoke-vNET側のWebサーバーを管理する側のエンジニアという意味)からすると、Webサーバー側に吐き出されるAccess_logにX-Forwarded-Forの値を出力させて見れないじゃん、バーストアクセスが来た時に制御したくてもお手上げじゃん、という話が出てきます。Azure Firewallを管理するエンジニアとWebサーバーを管理する人が同一であれば別にAzure FirewallのアプリケーションルールやネットワークルールをLog Analyticsやblobに配置させ通信ログデータを見ればいいぢゃん、という話になりますが、大抵どこの企業さんでも統合的なインフラリソースを管理する情報システム部門とプロジェクト部門は分かれており、お互いのニーズをマッチさせることが難しいことが起きます。
ということで、今回はHub-vNET側にAppGW(Application Gateway WAF v2)を最上位に構え、Azure Firewallをプライベート通信で経由させSpoke-vNET側に配置されているUbuntuのWebサーバーへバランシングし、かつX-forwarded-forのクライアントIPアドレスを出力させる、という構成をとりました。
参考にするサイトはここです。
仮想ネットワークの Azure Firewall と Application Gateway - Azure Example Scenarios | Microsoft Docs
アドレス空間がバラバラ出てくるため以下にまとめておきます。
●AppGW (WAF v2)
Public IP Address 20.78.120.100
Private IP Address 192.168.2.4 (AppGatewaySubnetは192.168.2.0/24)
●Azure Firewall
Public IP Address 20.78.57.122(使いません)
Private IP Address 192.168.0.4
●Spoke-vNET側のVM
Private IP Address 172.16.0.14 (1台ですが複数台でもOK)
補足:
AppGWでWAFはONにしています。
Rewriteルールを設定し、X-Forwarded-For:Port番号のPort番号は削除する設定を入れています。
Azure Application Gateway で HTTP ヘッダーと URL を書き換える | Microsoft Docs
80 port/tcpで受け付けし、バックエンドプールにも80port/tcpで流すシンプルな構成です。
Azure Firewallは、もともとSpoke-vNET側の外部通信を全て集約するように設定してあります。つまりAzure Firewallが配置されているHub-vNETとPrivate IPしか持たないVMがSpoke-vNETにあり、その間をvNET Peeringで設定してあります。また、Spoke-vNET側には0.0.0.0/0をアドレスプレフィックスとする通信は全てAzure FirewallのPrivate IP Addressへ丸投げするようスタティックルーティングのUDRをつけてあります。
Spoke-vNETのVMはUbuntuです。nginx 1.10.3を入れてあり、nginx.confの設定でlog formatにX-forwarded-forをAccess_log行の最終カラムに追加するように設定してあります。
では最初から見ていき、、の前に、最初に結果から見せます。
結果1:ちゃんとnginxのドキュメントルートである/var/www/html/index.html(初期ページをコピー)が表示されています。
結果2:アクセスログです。106.71.xxの箇所がクライアントIPになり、X-fowarded-forの設定した通りログの最終カラムに出力されています。
結果3:Azure Firewallを経由し、ネットワークルールコレクションで許可しているログが出ています。
結果は以上ですが、それぞれの設定情報を見ていきましょう。
まずはApplication Gatewayから。
AppGWは作成時にPublic IPを持つ構成かPrivate IPを持つ構成か、どちらもかを選択できます。ここでは最上位をAppGWとして裏の通信はPrivate通信にしたいので両方を選んでデプロイしています。
以下はバックエンドプールの設定です。1台しか登録していませんが、ロードバランサですので本来は複数台登録します。
HTTPの設定です。特に個別の設定は入れていませんが、タイムアウト20秒はちょっと短いかもしれませんね。デフォ値ですが気になる方はTCPの再送間隔も踏まえて30秒以上がよろしいかと。
リスナーの設定です。80port/tcpをPublicで受け付ける、といういたって普通の設定のままです。
ルール設定です。今回はバックエンドプールへバランシングしたいだけなのでリダイレクトではなくバックエンドプールへ促せ、と設定しています。
X-Forwarded-Forの書き換え設定です。これをしておかないと、nginx側のaccess_logでは以下のようなログが出力され、ポート番号がX-Forwarded-Forの後ろについてきます。
192.168.2.5 - - [01/Jun/2021:05:00:42 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gec
ko) Chrome/91.0.4472.77 Safari/537.36 Edg/91.0.864.37" "106.73.2.64:54591"
コロンでデリミタ指定できるので文字列整形すればよいんですが、アクセス解析するたびにsed/awk/cutなどで繰り抜くのも面倒なのでこの設定を入れておきました。
最後に正常性プローブの設定です。設定時にテスト通信ができますのでAzure FirewallもSpoke-vNET側のUDRも設定済の場合はテスト通信で通ります。が、まだ設定できていない場合は、ここではテスト通信せずに設定保存だけしておきます。※ちなみにこの正常性プローブの通信が異常の場合はバックエンドプールへの通信がされません。AppGWくんは対象との通信が成功している場合に、対象への通信を促そうとしますので、ここが異常だと動作してくれません。
次にAppGWを配置したappgwsubnetのUDR設定です。Spoke-vNET側のSubnet空間(172.16.0.0/24)をアドレスプレフィックスの対象とする通信は全てAzure FirewallのPrivate IP Address(192.168.0.4)へ丸投げしろ、というルートテーブル(UDR)の設定をappgwsubnet(Hub-vNETにあるAppGW専用サブネット)に関連付けさせています。
AppGW側の設定は以上です。続いて同じhub-vNETに配置されているAzure Firewall側の設定を見ていきます。
Azure Firewallは簡単です。ネットワークルールコレクションに以下を追加して保存するだけです。192.168.2.0/24(AppGWのサブネット空間)から、172.16.0.0/24(Spoke-vNET側のnginxがいるサブネット空間)のアドレスへの通信は許可してね、という設定だけです。
少し補足しておくと、http(80port/tcp)なのでアプリケーションルールで設定しても問題ないかと思いますが、その場合はVM側にFQDNアクセスできるWebサーバーの設定をしておく必要があります。Azure Firewallは、もともとNSGではできなかったFQDNフィルタリングのアクセス制御ができる位置づけで登場してます。また、アプリケーションルールの設定は全てSNATされることになるため、先ほど記載したnginx側のaccess_logの行頭のIP AddressはAppGWのPrivate IPではなくAzure FirewallのPrivate IPに変わります。ネットワークルールコレクションでPrivate通信の場合はSNATが無視されます。あとネットワークルールコレクションとアプリケーションルールコレクションの定義では、ネットワークルールコレクションが優先されます。
では最後にSpoke-vNET側を見ましょう。
Hub&Spoke構成ですので、もともと0.0.0.0/0をアドレスプレフィックスとした宛先の通信は全てAzure FirewallのPrivate IPへ行け、とスタティックルートの設定が入っています。ここでは192.168.2.0/24(appgwsubnet)をアドレスプレフィックスとした宛先の通信は全てAzure FirewallのPrivate IPへ行け、という設定を追加しています。
ここも補足しておくと、ルートテーブルは複数定義することができますが、複数定義された場合はロンゲストマッチで優先度のルーティングが判定されます。0.0.0.0/0は、ほぼほぼ優先度最低の定義になるためappgwsubnetの定義が優先判定されます。
//おまけ情報
Azureでのルーティング判定は以下のサイトに記載されています。
Azure 仮想ネットワーク トラフィックのルーティング | Microsoft Docs
UDR>BGP>システムルート(NSG)の順で優先度が決まるとありますが、正確には以下です。
● Azureの機能についている専用経路>UDR>BGP>システムルート の順です。
Azureの機能についている専用経路って何やねん!って話だと思いますが、例えばvNET Service Endpointなどが該当します。これがvNETに設定されているとUDRが設定されていようが関係なくvNET Service Endpointの経路で通信されます。(vNET Service Endpointはログが出ない仕様なので、制御できない&受け側でIP Addressが検知できるだけですが)
// おまけ ここまで
最後にnginx側の設定です。Azureの箇所ではないので参考まで。
/etc/nginx/nginx.conf のx-forwarded-forの設定箇所です。(上がhttp ディレクティブの箇所、下がserver ディレクティブの箇所)
※access log定義の最終行にlog_formatで定義した main を入れ忘れないように!
ちなみにnginx 1.10.3を使っています。ちなみにnginxのX-Forwarded-Forの設定はngx_http_realip_moduleというモジュールを使っているので、nginx -Vのコマンドでこのモジュールがincludeされているか確認してから設定してくださいね。
長々といつも通り長文になりましたが、最後です。
最近Azure Firewall Premiumなるものがプレビューで出てきています。上記はAzure Firewall標準のものを試していますが、違いとして1番のメリットはIDPS(IDS/IPS機能)がついていることでしょうね。
ここでAzure Firewall Premiumを使い、Spoke-vNET側からの外部通信をAzure Firewall Premium経由で集中管理させているのが主流になってくると思いますが、上記のようにAppGWでWAFをONにしていると、クライアントからの通信はAppGW側でOWASPのWAFエンジンルールで判定され、丸投げされたAzure Firewall Premiumでも同じくIDPSがONのため2重の防御チェックがされレイテンシやhttpレスポンスの遅延など様々なことが想定されます。そうならないように、Azure Firewall PremiumではIDPSバイパス機能がついており、検査対象外とするセグメント情報を登録しておくことで2重のチェックをしないようにすることができます。
Azure Firewall Premium プレビューの機能 | Microsoft Docs
が!!記載されている通りTLS通信であるhttps通信の場合はこの機能はスルーされる既知の問題があるため、そこは注意しましょう。(まだ、ぷれびゅーですからねw)
いじょ。