目的
WebSocket 通信だけ、バックエンドサーバが異なるため、オリジンを切り替えたいケースがあります。
今回は、Cloudflare の Origin Rules を使って WebSocket 通信を別オリジンにルーティングします。
Origin Rules とは
以下の図のような形で条件に当てはまる場合にいくつかの上書きアクションをおこなうことができます。
今回は、以下のような想定で切り替えられることを確認します。
WebSocket サーバ準備
上記の通常 Web サーバに加えて、以下の設定を加えます。
WebSocket 通信のリバースプロキシができるように SELinux 設定を変更します。
sudo /usr/sbin/setsebool -P httpd_can_network_connect 1
これを設定しないと以下のようなエラーが出ます。
[Fri Jun 09 15:31:54.010523 2023] [proxy:error] [pid 940082:tid 140635314460416] (13)Permission denied: AH00957: HTTP: attempt to connect to 127.0.0.1:80 (localhost) failed [Fri Jun 09 15:31:54.010582 2023] [proxy_http:error] [pid 940082:tid 140635314460416] [client 172.68.118.174:19386] AH01114: HTTP: failed to make connection to backend: localhost
Cloudflare では 8443 での HTTPS 通信がサポートされているので、8443 ポートを使ってみます。
SSL 証明書を準備し、8443 ポートでは WebSocket 通信をリバースプロキシし、localhost:8000
に通信します。
# 追記
Listen 8443 https
<VirtualHost _default_:8443>
SSLEngine on
SSLCertificateFile /path/example.com.cer
SSLCertificateKeyFile /path/example.com.key
SSLCertificateChainFile /path/fullchain.cer
SSLCACertificateFile /path/ca.cer
RewriteCond %{HTTP:Connection} =Upgrade [NC]
RewriteCond %{HTTP:Upgrade} =WebSocket [NC]
RewriteRule /(.*) ws://localhost:8000/$1 [P,L]
ProxyPreserveHost On
ProxyPass / http://localhost:8000/
ProxyPassReverse / http://localhost:8000/
</VirtualHost>
最後に httpd
を再起動します。
sudo systemctl restart httpd
Origin Rules 設定
まずは DNS レコードとして以下の2つを準備します。
その上で、Origin Rules を以下のように設定します。
- Expression はこちらを使います。
any(http.request.headers["upgrade"][*] == "websocket") and any(http.request.headers["connection"][*] == "Upgrade") and any(http.request.headers["sec-websocket-version"][*] == "13")
- 追加で
http.host
やhttp.request.uri.path
など、Fields reference · Cloudflare Ruleset Engine docs にあるフィールドを組み合わせて柔軟に条件設定できます。
Request Trace API による検証
実際にトラフィックを流す前に、以下のようにテストパターンを用意すれば、
Request Trace API を使って、どのルールにマッチするかを簡単にテストできます。
export EMAIL='YOUR_EMAIL'
export APIKEY='YOUR_APIKEY'
export ACCOUNT_ID='YOUR_ACCOUNT_ID'
curl -s --request POST \
--url https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/request-tracer/trace \
-H 'Content-Type: application/json' \
-H "X-Auth-Email: $EMAIL" \
-H "X-Auth-Key: $APIKEY" \
--data '{
"headers": {
"upgrade": "websocket",
"connection": "Upgrade",
"sec-websocket-version": "13"
},
"method": "GET",
"protocol": "HTTP/1.1",
"url": "https://wscat.example.com/"
}' | jq -r '.result.trace[]| select ( .step_name == "http_request_origin")'
設定した Origin Rules に対して "matched": true
となっていることが確認できます。
{
"step_name": "http_request_origin",
"type": "phase",
"matched": true,
"trace": [
{
"step_name": "e0bd256e76ea46eca49fd0ddb438a295",
"type": "ruleset",
"matched": true,
"name": "My origin rules ruleset",
"kind": "zone",
"trace": [
{
"step_name": "8a34a3fe70764327a4820b817c585223",
"type": "rule",
"matched": true,
"action_parameter": {
"origin": {
"host": "resolve.example.com"
}
},
"expression": "any(http.request.headers[\"upgrade\"][*] == \"websocket\") and any(http.request.headers[\"connection\"][*] == \"Upgrade\") and any(http.request.headers[\"sec-websocket-version\"][*] == \"13\")",
"description": "wscat-dns-override",
"action": "route"
}
]
}
]
}
wscat
による疎通確認
以下のコマンドでインストールし、
sudo npm install -g wscat
WebSocket サーバ側でポート 8000 で待機します。
wscat -l 8000
クライアント側から以下のコマンドで疎通確認ができます。
wscat verbose -c wss://wscat.example.com:8443
Orign Rules なし
この場合には、特に WebSocket サーバを用意していないので、
想定通りの 522 Connection timed out エラーになります。
% wscat verbose -c wss://wscat.example.com:8443
error: Unexpected server response: 522
>
Origin Rules あり
以下のように疎通できたことが確認できます。
↓クライアント側
Connected (press CTRL+C to quit)
> hello websocket
>
↓サーバ側
Listening on port 8000 (press CTRL+C to quit)
Client connected
< hello websocket
>
ログ確認
以下のようにカスタムログフィールドを設定して、WebSocket 通信に関するログをわかりやすくします。
Origin Rules なし
"OriginIP": "1.2.3.4"
には、特に WebSocket サーバを用意していないので、想定通りの 522: Origin Connection Time-out
エラーになります。
"OriginResponseStatus": 0,
と "EdgeResponseStatus": 522,
が確認できます。
クライアント側から接続を切った場合は、499: Client Closed Request
となります。
{
...
"CacheCacheStatus": "unknown",
...
"ClientRequestHost": "wscat.example.com:8443",
"ClientRequestMethod": "GET",
"ClientRequestPath": "/",
"ClientRequestProtocol": "HTTP/1.1",
"ClientRequestReferer": "",
"ClientRequestScheme": "https",
"ClientRequestSource": "eyeball",
"ClientRequestURI": "/",
"ClientRequestUserAgent": "",
"ClientSSLCipher": "AEAD-AES256-GCM-SHA384",
"ClientSSLProtocol": "TLSv1.3",
"ClientSrcPort": 49511,
"ClientTCPRTTMs": 9,
"ClientXRequestedWith": "",
"Cookies": {},
...
"EdgeResponseStatus": 522,
...
"OriginDNSResponseTimeMs": 0,
"OriginIP": "1.2.3.4",
"OriginRequestHeaderSendDurationMs": 0,
"OriginResponseDurationMs": 0,
"OriginResponseHTTPExpires": "",
"OriginResponseHTTPLastModified": "",
"OriginResponseHeaderReceiveDurationMs": 0,
"OriginResponseStatus": 0,
"OriginSSLProtocol": "none",
"OriginTCPHandshakeDurationMs": 0,
"OriginTLSHandshakeDurationMs": 0,
"ParentRayID": "00",
"RayID": "7d51972ffa4480f0",
"RequestHeaders": {
"upgrade": "websocket",
"connection": "Upgrade",
"sec-websocket-version": "13",
"sec-websocket-key": "PdExxoiLWMfeq49HXEzcAw=="
},
"ResponseHeaders": {},
...
}
Origin Rules あり
具体的には、以下のようなログが確認できます。
"OriginIP": "x.x.x.x"
となっているため、正しく DNS レコードの上書きアクションが適用され、"OriginResponseStatus": 101,
と "EdgeResponseStatus": 101,
から WebSocket 通信が確立(101: Switching Protocols
)され、"RequestHeaders"
にも WebSocket に関わるヘッダがあることがわかります。
{
...
"CacheCacheStatus": "dynamic",
...
"ClientRequestHost": "wscat.example.com:8443",
"ClientRequestMethod": "GET",
"ClientRequestPath": "/",
"ClientRequestProtocol": "HTTP/1.1",
"ClientRequestReferer": "",
"ClientRequestScheme": "https",
"ClientRequestSource": "eyeball",
"ClientRequestURI": "/",
"ClientRequestUserAgent": "",
"ClientSSLCipher": "AEAD-AES256-GCM-SHA384",
"ClientSSLProtocol": "TLSv1.3",
"ClientSrcPort": 56790,
"ClientTCPRTTMs": 5,
"ClientXRequestedWith": "",
"Cookies": {},
...
"EdgeResponseStatus": 101,
...
"OriginDNSResponseTimeMs": 1,
"OriginIP": "x.x.x.x",
"OriginRequestHeaderSendDurationMs": 0,
"OriginResponseDurationMs": 20,
"OriginResponseHTTPExpires": "",
"OriginResponseHTTPLastModified": "",
"OriginResponseHeaderReceiveDurationMs": 10,
"OriginResponseStatus": 101,
"OriginSSLProtocol": "TLSv1.3",
"OriginTCPHandshakeDurationMs": 3,
"OriginTLSHandshakeDurationMs": 7,
"ParentRayID": "00",
"RayID": "7d4b23229a9b1f4f",
"RequestHeaders": {
"upgrade": "websocket",
"connection": "Upgrade",
"sec-websocket-version": "13",
"sec-websocket-key": "xnjFdJ9fsAy/TKNdusLTyg=="
},
"ResponseHeaders": {},
...
}
Cloudflare with WebSockets
その他、Cloudflare を利用する際の WebSocket 通信に関する留意点はこちらをみてください。