しばやん雑記

ASP.NET Core とメイドさんが大好きなフリーランスのプログラマーのブログ

Azure Front Door を実運用する際に必要そうな設定を試した

最近は急激に Azure Front Door への興味が湧いてきたので一気に調べました。思っていたよりも長くなったので久し振りに目次記法を使いました。

このくらいの内容を抑えておけば、実運用で大体は困らないのではないかと思います。

全体的な注意ポイントとしては、設定を変更してから POP に反映されるまでに、多少時間がかかることです。CDN なので仕方ないですが、設定を間違ってしまった時に戻そうとしても時間がかかるので、あまり複雑なルールを作るのは避けたいところです。

カスタムドメインと HTTPS

カスタムドメインと HTTPS 設定については前回書いたので参考にしてください。Azure Portal からポチポチと設定すれば簡単に追加出来て、証明書も Key Vault からインポートできます。

最近になって Azure Portal から Front Door への Alias record set が作れそうな雰囲気が出てきましたが、実際に作ろうとするとエラーになってしまうので、今のところは Azure CLI から作るしかないです。

f:id:shiba-yan:20190419205502p:plain

近日中には直るのではないかと思いますが、Front Door 周りは Azure CLI も割とバグってて辛いですね。

ルーティング / URL Rewrite の基本

Front Door のルーティングは良くある優先度を付けたり、先に書いたものが勝つようなルールではなく、優先度を持たずマッチした URL がより長くて具体的なものを優先します。

ありていに言うと longest match なんだと思います。

ロードバランシングのアルゴリズムも少し変わっているので、ドキュメントには目を通しておいた方が良い感じです。ヘルスチェックが通ったものから選ぶのは勿論ながら、レイテンシも関わってくるので複雑です。

ちなみにバックエンドには優先度と重みを付けれるので、更に複雑に感じてきますが、その結果として最終的なバックエンドはその時に最適なものが選ばれます。

Front Door では 3 種類のトラフィックルーティングを設定できるので、柔軟ではあります。ドロップダウンとかで選ぶのではなく、自分で組み立てる必要があるので少し難しいですが。

ルーティングと同時に URL Rewrite が行えるので、多少はフォワーディング先のパスを変更することは出来ますが、IIS の URL Rewrite 程のルールは作れません。

出来てサブディレクトリを変えるぐらいなので、そこまで期待しない方が良いです。

もうちょっと柔軟なルールを作れるようになってくれると便利ですが、複雑なルールを作ってしまってメンテナンス不能になるのが目に見えてるので悩ましいところです。

送信元 IP を制限する

例えば CDN を前に置いた場合にアクセス元を CDN だけに限定し、元々の URL ではアクセス出来ないようにしたいという要求は多いはずです。

ホスト名でリクエストを蹴ったりリダイレクトさせたりしますが、Front Door は一応アクセス元の IP Range が公開されているので、これを使って簡単に制限をかけることが出来ます。

Front Door は IPv6 に対応しているので、それぞれで Range が公開されています。

IPv4 - 147.243.0.0/16
IPv6 - 2a01:111:2050::/44

変更される可能性はありますが、しばらくは変わらないでしょう。変わる場合はドキュメントが事前に公開されると思います。

今回はバックエンドに置くことが多いであろう App Service と Storage で設定を確認しておきました。

App Service の場合

App Service は IP 制限機能を組み込みで持っているので、先ほどの IP Range を許可するだけで終わります。ドキュメントもありますが、簡単なので読む必要ないかもしれません。

とりあえずサクッと Front Door の IP Range を追加します。App Service は IPv6 でのアクセスに対応していないので v4 の Range だけ追加すれば良いです。

f:id:shiba-yan:20190416192630p:plain

1許可するルールを 1 つ追加すると、暗黙的にそれ以外は拒否するルールが追加されます。

f:id:shiba-yan:20190416192640p:plain

設定が終わったので Front Door 経由と、元々の App Service の URL でアクセス出来るか試しておきます。ちゃんと Front Door 経由はアクセス出来ていますが、元々の URL では 403 が返ってきています。

f:id:shiba-yan:20190416193102p:plain

例によって IP Range は今後変わる可能性があるので注意が必要です。

Storage (Service Endpoint) の場合

Storage の場合も App Service と同じように IP Range で許可するように Service Endpoint に設定します。

Firewall に Front Door の IP Range を追加するだけなので簡単ですね。ちなみに下にある Microsoft からのトラフィックを許可するチェックを入れても Front Door には効果ありません。外しても良いです。

f:id:shiba-yan:20190416192738p:plain

これで先ほどと同じように Front Door 経由と Blob の URL でアクセスできるか確認します。ちゃんと Blob の URL を直接アクセスの場合は認証エラーが返ってきています。

f:id:shiba-yan:20190416193047p:plain

今回は使う予定が無かったので書かなかったですが、VM などの VNET 内に居るサービスの場合は NSG で設定すれば良いので、特に難しいことはないはずです

X-Forwarded-* ヘッダーを正しく扱う

Front Door は他の L7 のロードバランサーと同様に X-Forwarded-* ヘッダーに対応しています。

それ以外にも Front Door 固有のヘッダーも用意されていて、特に X-Azure-Ref はアクセスログと簡単に紐づけが出来るので、Application Insights のカスタムプロパティとして送信しておくと便利になります。

バックエンドへのアクセスは設定したプロトコルやホスト名でアクセスされるので、適切に扱っておかないと URL 生成時などで地味にはまることになります。

もちろんクライアント IP もそのままだと Front Door の IP になってしまいます。この辺りの対応ですが、今回は ASP.NET Core で設定を確認しておきます。

ASP.NET Core での対応

特に対応せずに ASP.NET Core アプリケーションをバックエンドとして登録してアクセスすると、アプリケーションは元々のホスト名でアクセスされたものとして認識します。

実際に Request.Host の値と Url.Link で生成した URL を出力してみると、アクセスしたホスト名が認識されていないことが分かります。

f:id:shiba-yan:20190417005025p:plain

もちろんクライアント IP アドレスも Front Door のものになったままなので、ASP.NET Core に組み込まれている ForwardedHeadersMiddleware を利用して解決していきます。

L7 のロードバランサー下で動くことは想定されているので、ドキュメントもちゃんとあります。

デフォルト設定だと X-Forwarded-ForX-Forwarded-Proto しか見てくれないので、以下のように少し Front Door 向けに設定を追加する必要があります。

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<ForwardedHeadersOptions>(options =>
    {
        // X-Forwarded-For に IP が 2 つ入ってくるので 2 以上にしないと意図しない値になる
        options.ForwardLimit = 2;
        // X-Forwarded-For/Proto/Host の全てを有効化する
        options.ForwardedHeaders = ForwardedHeaders.All;
        // Front Door の IP Range を登録しておかないとオーバーライドが動作しない
        options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("147.243.0.0"), 16));
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // パイプラインの先頭に置く
    app.UseForwardedHeaders();

    // 省略
}

はまりそうなポイントとしては ForwardLimitKnownNetworks の設定ですね。App Service で動かしている場合は L7 のロードバランサーが 2 つ挟まることになるので、デフォルトの 1 のままだと値が狂います。

そして KnownNetworks で Front Door の IP Range を追加しておかないと X-Forwarded-Host のオーバーライドが行われないので、小一時間は悩むことになります。

f:id:shiba-yan:20190417004438p:plain

これでデプロイすると、ASP.NET Core が認識するホスト名がアクセスしているものになります。

こういう系はローカル開発中は気が付きにくいので、大体は本番で発覚して非常に辛い思いをします。

HTTPS へのリダイレクトを行う

公式ドキュメントには HTTP から HTTPS へのリダイレクトは未対応と書いていますが、GA 後の Front Door はリダイレクトルールを追加できるため、Front Door だけで HTTPS リダイレクトを実現出来ます。

HTTP から HTTPS へのリダイレクトを行うルールは以下のように用意します。受け付けるプロトコルを HTTP に限定し、リダイレクトに使うプロトコルを HTTPS 限定にするだけです。

f:id:shiba-yan:20190417004152p:plain

基本的にはプロトコル以外を全て維持してリダイレクトさせたいので、それ以外の設定は全て Preserve にしておけば良いです。

そして既存のルールは全て HTTPS だけを受け付けるように変更します。

f:id:shiba-yan:20190417004141p:plain

これで HTTP で来た場合にはリダイレクトのルールが、HTTPS 出来た場合にはこれまでのルールが使われるので、HTTPS を強制することが出来るようになります。

実際に HTTP でアクセスすると、HTTPS にリダイレクトされていることが確認できます。

f:id:shiba-yan:20190417004453p:plain

Front Door は受け付けるプロトコルとホスト名もルールとして簡単に使えるのが面白いです。サブドメインを追加しておいて、別のバックエンドにルーティングするといったことも簡単です。

セッションアフィニティ

Front Door にはフロントエンド単位でセッションアフィニティの設定が行えます。有効にするとクッキーと設定した TTL 以内の場合は同じバックエンドにリクエストが割り振られます。

ステートフルなアプリケーションの時に役に立つこともあるでしょう。ユーザー単位でキャッシュを持っている場合には、利用効率を最適化出来ることもあります。

良く組み合わされるであろう App Service にもセッションアフィニティ機能があるので、それらを組み合わせた時の動作を確認しておきました。

Front Door 側の設定

Front Door のセッションアフィニティ設定は前述したようにフロントエンド単位なので、追加済みのホスト毎に有効化が行えます。

f:id:shiba-yan:20190417025011p:plain

これで設定が反映されればセッションアフィニティが使えそうですが、Front Door がセッションアフィニティ用のクッキーを書き出すにはいくつかの条件があります。

以下の条件に合致しない場合はクッキーが書き出されないので悩むことになります。

  • The response has specific values set for the Cache-Control header that prevent caching, such as "private" or no-store".
  • The response contains an Authorization header that has not expired.
  • The response has an HTTP 302 status code.
Azure Front Door Service - traffic routing methods | Microsoft Docs

よくある CDN がキャッシュを行わない時の条件とほぼ同じです。ドキュメントにはレスポンスの Authorization ヘッダーとありますが、恐らくこれは間違っていて実際はリクエストです。

ASP.NET Core で Cache-Control: no-store を返すページを用意してアクセスしてみると ASLBSA というクッキーが書き出されました。

f:id:shiba-yan:20190419105731p:plain

これが Front Door のセッションアフィニティ用クッキーとなります。

同様にリクエストに適当な Authorization ヘッダーを付けても、クッキーが書き出されました。ヘッダーを付けない場合は書き出されなくなります。

f:id:shiba-yan:20190419105744p:plain

ドキュメントにもあるように、用途はユーザーセッションの維持なので挙動としては納得です。

Azure Portal では設定できないですが、ARM を叩けばセッションアフィニティの TTL を設定できます。しかし Azure CLI の Front Door 拡張に不具合があり、今のところは上手く設定できません。

App Service 側の設定

そして App Service 側のセッションアフィニティですが、新規作成するとデフォルトでオンになっています。しかし Front Door と組み合わせる場合には、今はオフにしておくべきです。

f:id:shiba-yan:20190417025024p:plain

理由としては、ARR が X-Forwarded-Host を認識することが出来ず、常にオリジナルのドメイン向けのクッキーを書き出してしまうからです。

f:id:shiba-yan:20190419204128p:plain

Front Door のアフィニティクッキーのように domain 指定がなければ動作しますが、ブラウザで破棄されてしまい保存されないのでオフが良いという話です。

改善されるとアクセスするリージョンは Front Door が、リージョン内でのインスタンスは App Service によってある程度は固定出来るようになるはずです。

アクセスログを保存する

最後はアクセスログです。Front Door の各種ログとメトリクスは Azure Monitor を使って書き出されるようになっています。CDN や Application Gateway と同じですね。

出力先は Storage / Event Hub / Log Analytics が選べるので好きなものを選んでください。

f:id:shiba-yan:20190417022745p:plain

Event Hub に送ると Stream Analytics で簡単に解析できるので面白そうです。WAF ログとかは素早くモニタリングしたいと思うので、リアルタイム性の高いものが良いものを選ぶことになるでしょう。

そしてアクセスログは以下のように行単位で JSON になったデータとして出力されます。

f:id:shiba-yan:20190418155522p:plain

Azure Monitor を使った時のお決まりのフォーマットという感じです。JSON を整形すると以下のような形式になります。必要な情報は入っています。

{
  "Tenant": "Edge-Prod-TYO01r3",
  "time": "2019-04-18T06:04:01.5487839Z",
  "resourceId": "/SUBSCRIPTIONS/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/RESOURCEGROUPS/DARUTEST-RG/PROVIDERS/MICROSOFT.NETWORK/FRONTDOORS/DARUYANAGI1",
  "category": "FrontdoorAccessLog",
  "operationName": "Microsoft.Network/FrontDoor/AccessLog/Write",
  "properties": {
    "trackingReference": "0URO4XAAAAAA8ELM1L4RgQJvMp8EaO1VjVFlPMDFFREdFMDUyMQA4ZjkyOTY1Ni1mODk2LTQyMjAtOTE1OC0zZTNlZTQ0ZGI5ZjQ=",
    "httpMethod": "GET",
    "httpVersion": "2.0",
    "requestUri": "https://daruyanagi1.azurefd.net:443/",
    "requestBytes": "484",
    "responseBytes": "2644",
    "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36",
    "clientIp": "xxx.xxx.xxx.xxx",
    "timeTaken": "0.002",
    "securityProtocol": "TLS 1.2",
    "routingRuleName": "daruyanagi1.azurefd.net_default_d8187127-c6ba-4537-9bd7-b1dfd39a5a22",
    "httpStatusCode": "200",
    "httpStatusDetails": "200"
  }
}

中でも trackingReferenceX-Azure-Ref の値となるので、保存しておけばアクセスログと突き合わせての障害調査に役に立つはずです。

CSV とか TSV ではないので割と解析系のサービスとの親和性が高いと思います。Blob のパスに関してもちょっと長いですが、解析系サービスを意識した命名になっています。