4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

CloudflareAdvent Calendar 2022

Day 23

Cloudflare Origin Rules を使って WebSocket 通信を別オリジンにルーティングする

Last updated at Posted at 2023-06-10

目的

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 に通信します。

/etc/httpd/conf.d/ssl.conf
# 追記
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つを準備します。

image.png
image.png

その上で、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.hosthttp.request.uri.path など、Fields reference · Cloudflare Ruleset Engine docs にあるフィールドを組み合わせて柔軟に条件設定できます。

image.png

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 となっていることが確認できます。

result.json
{
  "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 なし

image.png

この場合には、特に WebSocket サーバを用意していないので、

想定通りの 522 Connection timed out エラーになります。

% wscat verbose -c wss://wscat.example.com:8443
error: Unexpected server response: 522
>               

Origin Rules あり

image.png

以下のように疎通できたことが確認できます。

↓クライアント側

Connected (press CTRL+C to quit)
> hello websocket
> 

↓サーバ側

Listening on port 8000 (press CTRL+C to quit)
Client connected
< hello websocket
> 

ログ確認

以下のようにカスタムログフィールドを設定して、WebSocket 通信に関するログをわかりやすくします。

image.png
image.png

Origin Rules なし

image.png

"OriginIP": "1.2.3.4" には、特に WebSocket サーバを用意していないので、想定通りの 522: Origin Connection Time-out エラーになります。
"OriginResponseStatus": 0,"EdgeResponseStatus": 522, が確認できます。
クライアント側から接続を切った場合は、499: Client Closed Request となります。

websocket_log.json
{
    ...
    "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 あり

image.png

具体的には、以下のようなログが確認できます。

"OriginIP": "x.x.x.x" となっているため、正しく DNS レコードの上書きアクションが適用され、"OriginResponseStatus": 101,"EdgeResponseStatus": 101, から WebSocket 通信が確立(101: Switching Protocols)され、"RequestHeaders" にも WebSocket に関わるヘッダがあることがわかります。

websocket_log.json
{
    ...
    "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 通信に関する留意点はこちらをみてください。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?