Skip to content

リバース プロキシで TLS を終端する構成で、X-Forwarded-Proto が見られていない #549

Description

@OsscJpDevInfra

何をしたいか

リバース プロキシで TLS を終端する構成でも、X-Forwarded-Proto を見て
「HTTPS で来ている」と判断できるようにする。

#548(コンテナ化)を検討する中で見つかった。
コンテナや Kubernetes では前段で TLS を終端し、アプリへは HTTP で転送するのが定石だが、
現状のフレームワークはその構成を想定していない。

なぜ必要か

#536 で入れた Cookie の Secure 属性が、この構成では立たない。

#536 は「HTTPS のときだけ Secure を立てる」という形にした。
ところが TLS を前段で終端すると、アプリから見た接続は HTTP なので
利用者のブラウザは HTTPS で繋いでいるのに、Secure が付かない。

セキュリティ対応を入れたつもりで、最も本番らしい構成でだけ効かない、という状態になっている。

現象(実測)

MVC_Sample (net10.0)X-Forwarded-Proto: https を付けて要求しても、
応答の Cookie に secure は付かない。

[素の HTTP]                  .AspNetCore.Cookies=<値>; path=/; samesite=strict; httponly
[X-Forwarded-Proto: https]   .AspNetCore.Cookies=<値>; path=/; samesite=strict; httponly

調べたこと

該当箇所は 4 つ(FxCmnFunction.cs のみ)

スキームで分岐しているのはこの 4 か所だけで、いずれも
セッション タイムアウト検出用 Cookie の生成・削除である。

対象 実装
Framework/Util/FxCmnFunction.cs:971 net10.0 cookieOptions.Secure = MyHttpContext.Current.Request.IsHttps;
Framework/Util/FxCmnFunction.cs:1010 net10.0 同上
Framework/Util/FxCmnFunction.cs:1049 net48 newCookie.Secure = HttpContext.Current.Request.IsSecureConnection;
Framework/Util/FxCmnFunction.cs:1088 net48 同上

net48 / net10.0 の両方が対象。 条件コンパイル(#if (NETSTD || NETCOREAPP))で
実装は分かれているが、どちらもプロキシの転送を見ていない点は同じ。

UseForwardedHeaders はリポジトリのどこにも無い

$ grep -rn 'UseForwardedHeaders' --include=*.cs .
(該当なし)

X-Forwarded-ForFramework/Util/GetClientIpAddress.cs
Business/Presentation/MyBaseAsyncApiController.csヘッダを直読みしているため、
クライアント IP の取得は動く。X-Forwarded-Proto だけが誰にも見られていない。

帰結は 3 つ

  1. #536Secure 自動設定が効かない(上記)
  2. appSettings__CookieSecurePolicy=always#541)が、この構成では実質必須になる。
    あちらは IsHttps を見ずに明示的に立てるため、迂回策として機能する
  3. UseHttpsRedirection を on にすると無限リダイレクトになる。
    アプリが「HTTP で来た」と判断して HTTPS へ 307 → プロキシが HTTP で転送 → 繰り返し。
    #541 で「前段で終端するなら off」と書いた根拠がこれ

net10.0 側 : UseForwardedHeaders

app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor
});

パイプラインの先頭近くUseHttpsRedirection より前)に置く必要がある。

踏みやすい罠 : KnownProxies / KnownNetworks を設定しないと黙って無視される。
既定ではループバックからの転送しか信用しない。
コンテナや Kubernetes では前段が別アドレスになるため、設定しないと何も起きない。
逆に無条件に信用すると、クライアントがヘッダを詐称できるSecure の判断を
外から操作されることになる)ので、どちらにも倒せない。ここは慎重に決めたい。

置き場所も選択肢がある。

利点 欠点
サンプル(Startup.cs)に置く フレームワークの挙動を変えない 利用者が自分で書く必要がある
フレームワーク側で面倒を見る 何もしなくても正しく動く **既定の挙動が変わる。**下位互換の検討が要る

**まずはサンプル側で、設定で on/off できる形(#541 と同じ流儀)**が穏当だと思う。

net48 側 : IIS の設定

net48 は HttpContext.Current.Request.IsSecureConnection で、
コードからは変えられない(読み取り専用)。
IIS + ARR なら URL Rewrite で HTTP_X_FORWARDED_PROTO を見て
サーバ変数 HTTPS を立てる、という定番の対処がある。

コードではなく Web.config と手順書の話になるので、
Samples/WebApp_sample/MVC_SampleWeb.config にコメントを置く形を想定している
#541machineKey について同じことをした)。

影響

  • 利用者への影響: 案(サンプル側・既定 off)なら無し。
    フレームワーク側で面倒を見る案を採る場合は、既定の挙動が変わるため
    下位互換の検討が要る
  • net48 / net10.0: 両方。ただし対処が異なる
    (net10.0 はコード、net48 は IIS の設定)
  • 深刻度は高くない。 Secure が付かない対象はセッション タイムアウト検出用 Cookie で、
    値は Environment.TickCount である(資格情報ではない)。
    認証 Cookie 自体は ASP.NET Core の Cookie 認証 / FormsAuthentication 側の管轄で、
    そちらは #541CookieSecurePolicyrequireSSL#536)で立てられる
  • 検証: 3_SmokeTest.ps1 は素の HTTP で回るため、既定 off なら影響を受けない

関連

  • #536 — Cookie の SecureIsHttps で立てる形にした(本件の前提)
  • #541CookieSecurePolicy / UseHttpsRedirection を設定で切り替える形にした
  • #548 — コンテナ化。この構成を検討する中で見つかった

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions