チャットのサムネイルが s3_cdn_url をバイパスし、生きた S3 バケット URL を使用

最近、Cloudflare R2 アップロードバケットの設定を行い、チャットのサムネイル画像が壊れてしまいました。そこで調査を行い、設定の簡易的な修正を行いました。その後、このトピックを見つけました:https://meta.discourse.org/t/cloudflare-r2-image-url-display-issue-detailed-explanation-and-fix/358204。とにかく、他の S3 アップロードバケット設定を確認したところ、このバグは Cloudflare 固有の問題ではないことがわかりました。


説明

アップロード用に外部の S3 または互換性のあるオブジェクトストレージが設定されている場合、チャットのサムネイル画像は CDN をバイパスし、バケット URL から直接読み込まれます。

Cloudflare R2 のようなセキュアな外部 S3 互換バケットでは、チャットのサムネイルが壊れて表示されません。

根本的な問題は、チャットシリアライザーが s3_cdn_url 設定をサムネイルに適用できていないことです。設定された CDN 経由で画像をルーティングするのではなく、生の内部 S3 バケット URL がそのままブラウザのペイロードに漏洩しています。

再現手順

Meta や他の S3 アップロードバケットを使用しているサイトでも再現可能です:

  1. チャットまたはチャンネルに画像を投稿する
  2. コンソールでサムネイル画像の URL を確認する
  3. 画像をクリックして元の大きな画像を表示し、URL を確認する
  4. サムネイルと比較する

以下は Meta のチャットからの例です

サムネイル URL: バケットから

https://cdck-file-uploads-global.s3.dualstack.us-west-2.amazonaws.com/meta/optimized/4X/4/7/9/479815360e0e6e0cd9f4ba565891776e84aea532_2_375x500.jpeg

元の URL: CDN 経由

https://global.discourse-cdn.com/meta/original/4X/4/7/9/479815360e0e6e0cd9f4ba565891776e84aea532.jpeg

コンソールでは、サムネイルの HTML <img...data-large-src = CloudFront CDN URL、src = AWS バケット URL が含まれています。

スクリーンショット

影響:

  • Cloudflare R2 のようにデフォルトでセキュアであり、生バケットエンドポイントへの未認証アクセスをブロックする S3 互換ストレージの場合、チャットのサムネイル画像(最適化済み)が壊れます。
  • 生バケットエンドポイントへのアクセスを許可している AWS や他の S3 互換オブジェクトストレージバケットでは、チャットが CDN を完全にバイパスするため、帯域幅の漏洩が発生します。これにより、すべてのチャットサムネイルトラフィックに対して直接 S3 のエグレス料金が請求されます。
  • インフラストラクチャの漏洩:生バックエンドストレージ URL(内部バケット名や場合によってはアカウント ID を含む)がクライアントの JSON ペイロードに露出しています。

PR:

この問題を修正する PR を作成しました:

https://github.com/discourse/discourse/pull/40419

Sam がチャットコンポーザープレビューに getURLWithCDN を追加したようですが(Upload images in chat can't be show normally when use s3 CDN - #15 by sam

プロトコルの不一致(//https://)で getURLWithCDN がクラッシュするため、コンポーザーの修正が一部の S3 設定で失敗していた可能性もあるかもしれません。いずれにせよ、上記の PR は Sam の作業を拡張し、ストリームにラッパーを追加してプロトコル非依存にすることで対応しています。

一時的な回避策:

これが Cloudflare 固有の問題以上であることを理解する前に、軽量なテーマコンポーネントを作成しました。これは Chat DOM 内の生 S3 ドメインをインターセプトし、ブラウザがダウンロードを試みる前に適切な CDN ドメインに置き換えます。これにより、トラフィックが正しくルーティングされ、帯域幅の漏洩が解消されます。これを任意の S3 互換オブジェクトストレージで動作するように適応させました。設定は「Raw S3 バケット URL」と「S3 CDN URL」の 2 つだけです。

https://github.com/Lillinator/chat-s3-thumbnails-fix

(なぜか GitHub のワンボックスがここで壊れていましたが) 現在修正済み

「いいね!」 5

おそらくサイトの移動に関連しているでしょう……私は今すぐ再焼きしました。

「いいね!」 2

こんにちは、PR はすでにマージされましたか?

ありがとう

「いいね!」 2

discourse-triage-bot がいくつかのテストを修正してくれたので、今は人間のチームメンバーのレビューを待っているだけです :slight_smile:

https://github.com/discourse/discourse/pull/40419#issuecomment-4683409099

「いいね!」 5

マージされたようです。

「いいね!」 4

著者のリクエストによりオープン

カスタム絵文字を追加したサイトでは、修正がマージされたことでチャット内の絵文字が壊れています。

標準的な投稿画像(rake posts:rebake で修正可能)とは異なり、チャットのカスタム絵文字は /site.json を介してフロントエンドに動的に渡されます。

データベースにプロトコル(例://bucket.endpoint...)が欠落している S3 URL や、app.yml の環境変数と完全に一致しない仮想ホスト形式のドメインを使用している場合、Discourse の内部 CDN 置換処理は静かに失敗します。その結果、生のバケット URL がブラウザに渡され、チャット内のカスタム絵文字が壊れます。

修正方法:

この問題を永久的に修正するには、データベース内の生のバケット URL を CDN URL に強制的にリマップし、サイトキャッシュをクリアして /site.json を再生成する必要があります。

1. コンテナに入る:

サーバーに SSH で接続し、Discourse コンテナ(通常は app、2コンテナ構成の場合は web_only)に入ります。

cd /var/discourse
./launcher enter app

2. URL をリマップする:

Discourse 組み込みの remap ツールを実行します。移行スクリプトが残したことがある https:// 形式とスキームレスの // 形式の両方を捕捉するため、2回実行してください。

プレースホルダーを実際の生のバケット URL と実際の CDN URL に置き換えてください。

# 標準的な https:// URL を修正する
discourse remap "https://<your-bucket>.<your-endpoint>.com" "https://cdn.your-domain.com"

# スキームレスの // URL を修正する(これが通常カスタム絵文字を壊す原因となる)
discourse remap "//<your-bucket>.<your-endpoint>.com" "https://cdn.your-domain.com"

3. キャッシュをクリアする

/site.json は強力にキャッシュされているため、フォラムが新しい URLを提供させるために Rails キャッシュをクリアする必要があります。

Rails コンソールを開く:

rails c

次のコマンドを実行する:

Rails.cache.clear
Site.clear_cache
exit

4. 更新

ブラウザをハードリフレッシュしてください(まだ使用している場合はテーマコンポーネントのワークアラウンドを無効にしてください)。チャット内のカスタム絵文字は正常に修正され、CDN 経由で適切に読み込まれるはずです。

「いいね!」 3

リリーさん、素晴らしいお仕事を本当にありがとうございます。2回の再マッピングの後、私の古いアップロードとサムネイルが正常に動作するようになりました。その前は、“/admin/config/customize/themes” の画像でさえ動作していませんでした。今はすべて修正されています。素晴らしいです。

ありがとうございます。

「いいね!」 1