Cloudflare プロキシーで HTTP host 書き換えどこでやる?
はじめに
Cloudflare でのプロキシーで SNI/HTTP host を書き換えたいとき、どこで書いたらいいか試します。(Enterprise プランで実施)
HTTP host 書き換え場所
ユースケースに応じた書き換え場所を書いときます。
複数の機能で書き換えたときに最後に勝ちそうなところを優先してみました。
ただ、デザインによって適切な場所は変わりますので、あくまで一例ということで。
参考までにトラフィックシーケンス。
テストパターン
Load Balancing なし
直接接続(非 cloudflared )
curl
curl https://eyeball.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "eyeball.oymk.work",
"sni": "eyeball.oymk.work"
}
Origin Rules(Host Header のみ)
Origin Rules で Host Header を変えます。
SNI も同様に変換したリクエストがでました。
curl
curl https://eyeball.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "o.oymk.work",
"sni": "o.oymk.work"
}
Origin Rules(Host Header と SNI)
Origin Rules で Host Header と SNI を個別に変換してリクエストがでました。
curl
curl https://eyeball.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "o.oymk.work",
"sni": "s.oymk.work"
}
Snippets
Snippets で宛先指定しリクエストすると SNI も変換されました。
curl
curl https://eyeball.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "snippet.oymk.work",
"sni": "snippet.oymk.work"
}
リクエスト先の hostname が DNS 解決できないとエラーとなります。
error
< HTTP/2 530
:
<span data-translate="error">Error</span>
<span>1016</span>
:
Cloudflare is currently unable to resolve your requested domain (snippet.oymk.work).
#### エラー内容と回避法
#### code 530
{
"id": "530",
"desc": "HTTP error 530 is returned with an accompanying 1XXX error displayed. Search for the specific 1XXX error within the Cloudflare Help Center for troubleshooting information."
}
#### code 1016
{
"id": "1016",
"desc": "Origin DNS error: [Common cause] Cloudflare cannot resolve the origin web server’s IP address. Common causes for Error 1016 are: 1. A missing DNS A record that mentions origin IP address. 2. A CNAME record in the Cloudflare DNS points to an unresolvable external domain. 3. The origin host names (CNAMEs) in your Cloudflare Load Balancer default, region, and fallback pools are unresolvable. Use a fallback pool configured with an origin IP as a backup in case all other pools are unavailable. 4. When creating a Spectrum app with a CNAME origin, you need first to create a CNAME on the Cloudflare DNS side that points to the origin. Please see Spectrum CNAME origins for more details [Resolution] To resolve error 1016: 1. Verify your Cloudflare DNS settings include an A record that points to a valid IP address that resolves via a DNS lookup tool. 2. For a CNAME record pointing to a different domain, ensure that the target domain resolves via a DNS lookup tool."
}
Origin Rules と Snippets の併用
どちらが強いか。(動作を見るため違う値に設定)
Snippets でした。
curl
curl https://eyeball.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "snippet.oymk.work",
"sni": "snippet.oymk.work"
}
cloudflared 接続
転送先を cloudflared tunnel にします。
curl
curl https://replica0.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "replica0.oymk.work",
"sni": "replica0.oymk.work"
}
cloudflared(SNIのみ)
Tunnels 設定の TLS Origin Server Name で SNI だけ変換されました。
curl
curl https://replica0.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "replica0.oymk.work",
"sni": "tun-sni.oymk.work"
}
cloudflared(SNI と hostname)
Tunnels 設定の HTTP Host Header で HTTP host も個別に書き換えられました。
curl
curl https://replica0.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "tun-host.oymk.work",
"sni": "tun-sni.oymk.work"
}
cloudflared と Origin Rules の併用
どちらが強いか。(動作を見るため違う値に設定)
Cloudflare から 404 Not found が返りました。(回避法は後述)
回避策を取った場合 cloudflared のほうが勝ちました。
curl
curl https://replica0.oymk.work/index.php -v
< HTTP/2 404
< cf-cache-status: DYNAMIC
< server: cloudflare
cloudflared と Snippets の併用
どちらが強いか。(動作を見るため違う値に設定)
Snippets でした。
curl
curl https://replica0.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "snippet.oymk.work",
"sni": "snippet.oymk.work"
}
Load Balancing
Load Balancing を利用した場合です。
直接接続(非 cloudflared )
Pool endpoint は IP アドレスまたは FQDN 指定
curl
curl https://eyeball.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "eyeball.oymk.work",
"sni": "eyeball.oymk.work"
}
Load Balancing(Host header)
Host Header を書き換えると、SNI も書き換わります。
curl
curl https://eyeball.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "lb.oymk.work",
"sni": "lb.oymk.work"
}
Load Balancing と Origin Rules の併用
どちらが強いか。(動作を見るため違う値に設定)
Load Balancing でした。
Load Balancing で書き換えしなければ、Origin Rules が活きました。
curl
curl https://eyeball.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "o.oymk.work",
"sni": "o.oymk.work"
}
Trace
Trace を見ると Origin Rulesに Hit しています。
Load Balancing で Host Header を書き換えると、Origin Rules が上書きされました。
curl
curl https://eyeball.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "lb.oymk.work",
"sni": "lb.oymk.work"
}
Load Balancing と Snippets の併用
どちらが強いか。(動作を見るため違う値に設定)
Snippets が全部持っていきます。
curl
curl https://eyeball.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "snippet.oymk.work",
"sni": "snippet.oymk.work"
}
cloudflared 接続
Pool endpoint は cloudflared UUID
Load Balancing と cloudflared の併用
Load Balancing での Host Header は設定せず、cloudflared で上書き設定をします。
cloudflared に書き換わりました。
curl
curl https://replica0.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "tun-host.oymk.work",
"sni": "tun-sni.oymk.work"
}
Load banancing で Host Header を書き換えます。
どちらが強いか。(動作を見るため違う値に設定)
Cloudflare から 404 Not found が返りました。
error
< HTTP/2 404
cloudflared の live logs を見ると、Load banancing で書き換えた HTTP host に 404 を返しています。cloudflared に設定がない( hostname に lb がない)ので、当然でした。
cloudflared にワイルドカードを追加してリクエストを拾うと、オリジンに届きました。
clooudflared のほうが Load balancer より強いです。
curl
curl https://replica0.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "wild-host.oymk.work",
"sni": "wild-sni.oymk.work"
}
Load Balancing、cloudflared、Origin Rules の併用
どちらが強いか。(動作を見るため違う値に設定)
これも同じように 404 となるので、ワイルドカードで救ってみます。
cloudflared のほうが Origin Rules より強いです。
curl
curl https://replica0.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "wild-host.oymk.work",
"sni": "wild-sni.oymk.work"
}
Load Balancing、cloudflared、Snippets の併用
どちらが強いか。(動作を見るため違う値に設定)
Snippets が全部持っていきます。
curl
curl https://replica0.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "snippet.oymk.work",
"sni": "snippet.oymk.work"
}
Load Balancing のオリジンが Private Nework(Virtual networks)の場合
cloudflared が Private Network の場合は、cloudflared での変換はできません。
Load Balancing で変更します。
curl
curl https://private.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "0147.oymk.work",
"sni": "0147.oymk.work"
}
Snippets は全部持っていきます。
curl
curl https://private.oymk.work/index.php -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni"}'
{
"host": "snippet.oymk.work",
"sni": "snippet.oymk.work"
}
その他の Header や Body の変換
実際には HTTP その他 Header や Body も変換する必要があるかもしれません。
Header の例
下記の例では referer が変換されずオリジンに到達しています。
referer
curl https://replica0.oymk.work/index.php -H "referer: https://replica0.oymk.work" -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni", referer: .Referer}'
{
"host": "snippet.oymk.work",
"sni": "snippet.oymk.work",
"referer": "https://replica0.oymk.work"
}
Transform Rules を利用すると任意のリクエストヘッダーを置換することができます。
referer
curl https://replica0.oymk.work/index.php -H "referer: https://replica0.oymk.work" -s |\
jq '{host: .Host, sni: ."X-Ssl-Sni", referer: .Referer}'
{
"host": "snippet.oymk.work",
"sni": "snippet.oymk.work",
"referer": "https://snippet.oymk.work"
}
同様のことは Workers や Snippets でも可能です。
Body の例
また Header だけでなく Body の中身を逆変換したいこともあるでしょう。
下記の例だとレスポンス HTML の link が内部名で外に漏れているので、変換したいです。
body
curl https://replica0.oymk.work/index.html -H "referer: https://replica0.oymk.work" -s
<a href="https://snippet.oymk.work/index.html">hoge</a>
Workers や Snippets で変換することができます。
先に使った Snippets を変更して Body を書き換えてみます。
Link 部分が期待のものに置換され、URL バーと整合性が取れました。
body
curl https://replica0.oymk.work/index.html -H "referer: https://replica0.oymk.work" -s
<a href="https://replica0.oymk.work/index.html">hoge</a>
その他の Ruleset はどれを参照するのか
WAF や Cache など Ruleset の各 phase では、トラフィックをマッチために field を利用します。
field では HTTP host(http.host)も参照できますが、元の値、書き換え後の値、どれを設定すればマッチするか確認します。
WAF の例
WAF Custom Rules(Phase http_request_firewall_custom)を確認しました。
HTTP host 書き換え場所 | WAF Custom Rules が参照する HTTP host |
---|---|
どこで書き換えても | 元の HTTP host |
HTTP host については元の値を参照していました。
Dev docs によれば phase 間で filed の値が変わる可能性もある、ということですが HTTP host はその範囲ではないようです。
While evaluating rules for a given request/response, the values of all request and response fields are immutable within each phase. However, field values may change between phases.
Custom Rule に書き換え後の HTTP host を書いても、
マッチしません。
WAF の Event には元の HTTP host のみが表示されます。
HTTP host については下記の raw のような参照をデフォルトでしているように見えました(評価フロー全体を通して immutable )。なお、http.host に raw 付きはなかったです。
If you want to use the original field values in rules evaluated later, you can use raw fields (for example, raw.http.request.uri.path) in their expressions. These special fields are immutable during the entire request evaluation workflow. For a list of raw fields, refer to Fields.
Cache の例
Workers(Snippets)で書き換えるかどうかで話が変わりました。
HTTP host 書き換え場所 | Cache Rules が参照する HTTP host |
---|---|
Workers または Snippets | fetch() の HTTP host |
その他 | 元の HTTP host |
Workers/Snippets 以外
元の HTTP host を指定することで Cache 対象となりました。
書き換え後の HTTP host を指定しても、Cache 対象とはなりませんでした。
Workers/Snippets
Workers や Snippets で書き換える場合、サブリクエストのほうが Cache 判断の対象になってました。
具体的には Cache Rules でサブリクエストの HTTP host が Cache 対象になっているかで動きが変わりました。
サブリクエスト(snippet)を Cache 対象にしてみます。
Instant Logs を確認します(サブリクエストも表示される)。
同じリクエストでサブリクエストが Cache Hit しています。
サブリクエストを Cache 対象から外すと Cache されません。
なお Cache Analytics で Host 単位でみてもサブリクエスト(snippets)は表示されず、元のリクエストのみ表示されました。
まとめ
SNI/HTTP host の書き換えをいくつかのプロダクトで実装することができました。
その他の Header や Body も変換の方法がありました。
デザインやシナリオで最適な方法は変わると思うので、臨機応変に実装いただければと思います。
Discussion