リバースプロキシとHTTPSに関するDiscourseの議論

DiscourseをApacheリバースプロキシの背後に設定しようとしていますが、httpsでの動作がうまくいきません。

ここまで来るのに多くの問題がありました。現在、Discourseをサーバー上で実行し、その前にリバースプロキシとして機能するApacheサーバーを配置しています。当初、Discourseが常にapp.yamlで設定されたホスト名へのリダイレクトを求めていたため、リバースプロキシの背後で実行させることに多くの問題がありました。

何とか動作するようになりましたが、ブラウザで「Mixed-content(混合コンテンツ)」の警告が表示されます。
Apacheにはhttpからhttpsへのリダイレクトを設定しており、それは正常に機能しています。しかし、Discourseはいくつかのコンテンツをhttpで提供しており、それをhttpsに変更する方法がわかりません。

例えば、ファビコンがhttpで提供されており、これを変更する方法がわかりません。

Discourseがhttpsトラフィックを直接処理することなく、すべてのリンクをhttpsに変更させることはできますか?

Apacheで以下を試してみました:

Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"

しかし、これは効果がないようです。

Discourseの「force https」フラグをチェックしても改善せず、httpでのリクエストを無視してしまうためサイトが破損してしまいます。

混合コンテンツの問題を解決するために、何をすべきでしょうか?

Apache2 は多くの問題を引き起こす可能性があります。nginx、caddy、traefik、または haproxy への切り替えを検討してください。

「いいね!」 2

私は、Apache2 をコンテナ内の Unix ソケットへのリバースプロキシとして使用したテスト環境で、Apache2 を「問題なく」動作させることができました。

私が確認した唯一の違い(注:数時間のテストのみで、完全な検証ではありません)は以下の通りです。

  • Apache2 は、コンテナ内の共有ボリュームにある Unix ソケットへのシンボリックリンクでは動作しません。
  • 大まかなテストでは Apache2 はやや遅かったものの、その差はほとんどありませんでした。

個人的には、技術に関する宗教戦争は好まないため、「Apache2 は多くの問題を引き起こす」という意見には賛成できません。私のテスト中、Apache2 に起因する負の事象は一切発生しませんでした。

以下は、私が Apache2 で使用したコア設定です(HTTP 環境ですが、LETSENCRYPT とも問題なく動作しました)。

# cat discourse.example.conf
<VirtualHost *:80>
  ServerAdmin webmaster@localhost
  ServerName  discourse.example.com
  DocumentRoot /website/discourse

  RewriteEngine On
  ProxyPreserveHost On
  ProxyRequests Off
  ProxyPass / unix:/var/discourse/shared/socket-only/nginx.http.sock|http://localhost/
  ProxyPassReverse  / unix:/var/discourse/shared/socket-only/nginx.http.sock|http://localhost/
  ErrorLog /var/log/apache2/discourse.error.log
  LogLevel warn
  CustomLog /var/log/apache2/discourse.access.log combined

  RewriteCond %{SERVER_NAME} =discourse.example.com
  RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

注:force_https が設定されているにもかかわらず HTTP でサービスが提供されてしまう事象が発生したのは、/uploads ディレクトリにファイルが存在しない場合のみでした。これはもちろん、Apache2 と nginx のどちらをリバースプロキシとして使用するかの問題とは無関係です。

「いいね!」 1

返信ありがとうございます。ただ、私の環境ではApacheとDiscourseが同じサーバー上にあるわけではないのです。その点が少し不明確だったかもしれません。
既存のApacheサーバーには複数のウェブサイトがホストされており、Discourseは別のサーバーに配置されているため、リバースプロキシとして設定する必要があります。そのため、ソケットの使用はできません。

ありがとう。

この方法は試していませんが、リモートファイルシステムをマウントして、Unixソケットにアクセスできるか試してみることをお勧めします。特に、サーバーが同じデータセンター内にあり、広域ネットワークでのネットワークパフォーマンスが問題ない場合です。

アーキテクチャ、オペレーティングシステムの詳細、ネットワーク設定などを投稿しない限り、回答するのは難しく、Meta Discourseの範囲外かもしれません。

創造的になりましょう!

「いいね!」 1

以前、その設定を Apache2 で使用した際、Discourse のメッセージバスへの接続エラーが発生しました。それはもう1年以上前のことです。サイト自体は正常に読み込まれているようですが、F12の開発者コンソールには、初期のいくつかの試みの後に WSS 接続がタイムアウトしたと明記されていました。

「いいね!」 1

私が投稿した設定例をご覧いただければ明らかな通り、wss は使用していません。

wssapache2 リバースプロキシ は異なります(これは方法の一つに過ぎず、私たちは wss を使用していません)。

実際、私たちは以下の理由から、unix ドメインソケット を使用した nginx および apache2 のリバースプロキシ設定のみを使用しています。

  • 手抜きで、シンプルかつデバッグしやすい設定が好きだから。
  • unix ドメインソケット はシンプルでデバッグしやすい。
  • nginx では、シンボリックリンクを使ってリバースプロキシと任意のコンテナを切り替えられる。
  • apache2(コンテナへのリバースプロキシ)はシンボリックリンクに対応していないため、Web サーバーの再起動が必要になる。

ただし、@Grunskin さんは、まだ設定していないことについて質問されました。つまり、あるホストでリバースプロキシを実行し、別のホストでコンテナを実行する方法です。

時間ができたら、同じデータセンター内で nginxapache2 両方についてこれをテストし、リモートファイルシステムをマウントして unix ソケット を使用することで動作するかどうかを確認します。

それまでは……

注:私見ですが、この問題は nginxapache2 には直接関係ありません。これらは単にリバースプロキシとして機能するだけです(ただし、前述の通り、リモートアクセス設定はまだテストしていないため、それ以上コメントすることはできません)。

「いいね!」 1

なぜこれが必要なのでしょうか?

Discourse はウェブサイトではなくアプリケーションです。初期の JavaScript ペイロードがブラウザに配信されると、多くの機能は Discourse サーバーとの高速な接続に依存します。他のシステムを介してプロキシすると、遅延が発生し、ユーザー体験が著しく損なわれます。

ご要望の背景にある理由をご説明いただけますか?

「いいね!」 2

リバースプロキシを使用する理由は多数あります。
例えば、パブリックIPが1つしかない場合、複数のウェブサーバーをポート80/443でパブリックにする必要があるが、この例ではその特定のウェブサーバーでDiscourseを実行できないようなケースです。
SSLオフローディングとして使用し、エンドサーバーが暗号化を処理する必要がないようにすることもできます。
エンドサーバーをリバースプロキシの背後に配置することで、攻撃対象領域を削減できます。
これには正当な理由が多数あり、なぜこれが不可能なのか理解できません。

しばらくして、Apacheサーバーの背後でDiscourseを実行している別のサーバーがすでにあり、少なくとも2年以上正常に動作していることを思い出しました。新しいDiscourseも同じように設定しましたが、一部のコンテンツをHTTPで配信してしまうのを止められません。2つのDiscourseサーバーの違いは、古い方が2.4、新しい方が2.5で動作している点のみです。そこに違いがあるかどうかはわかりません。

最初の投稿で述べたとおり、force_httpsを有効にするとサイトが破損し、ログインや招待の受け入れなどが不可能になります。おそらくHTTPで配信されているため、一部のJavaScriptが実行されないようです。
force_httpsはHTTPリンクを破棄するのではなく、すべてをhttpsに書き換えるようにする方が理にかなっているのではないでしょうか。少なくともオプションとして実装すべきです。

Discourseをパブリックに設定する推奨方法はどのようなものですか?DMZに独自の外部IPを持つサーバーを構築するのでしょうか?

Traefik を見てみることをお勧めします。
非常に優れており、SSL 証明書も自動的に管理します。

リバースプロキシの背後で実行する理由はたくさんあります。Discourse.org のインフラが HAproxy の背後で動いているのは間違いありません。

私は Traefik と Caddy Server を使っており、過去には nginx、HAproxy、さらには Apache も動作させたことがあります(サブフォルダインストールで WordPress をクエリする場合など)。

これがあなたの問題です。force_https を有効にした上で、なぜ壊れているのかを特定する必要があります。無効にするのは選択肢ではありません。無料のサポートを求めましたが、回答した人々には Apache 向けの解決策がないようです。したがって、あなたは Apache 陣営のリーダーになる必要があります。

「いいね!」 2

はい。完全に同意します。

現在、メインの移行をステージングしているサーバーを除き、すべてのサイト(本番、テスト、ステージング)で「リバースプロキシ付きの2コンテナ構成」を採用しています。

これは、Postgres、MySQL、Discourse のアプリコードを単一のコンテナ内にまとめておくことで、さまざまな移行スクリプトを実行しやすくなるためです。これは非本番環境であり、デバッグも容易です。ただし、問題がなければ、バックアップを本番環境に移してリストアします。

この方法の問題点は、DB が 1 つしかないため、DB リストア中のダウンタイムを、スーパー・デューパーな 2 コンテナ+リバースプロキシ構成であっても補うことができないことです。

将来的には、2 つの DB コンテナを持つ構成を試して、一方をリストアしながらデータを切り替えられるようにするかもしれません。

あるいはもっと良い方法として、異なる名前のデータベースにリストアし、リアルタイムで切り替える方法もありますが、まだそのやり方がわかりません。もし、アプリを再構築せずに、本番 DB の名前を discourse から discourse2 に変更できるコードの場所をご存知であれば、非常に助かります :slight_smile: 創造的で革新的なスーパーコンサルタントの @pfaffman さんならご存知かもしれませんね?

更新:templates/postgres.template.yml にありました :slight_smile:

(ここには確かに興味深い mv postgres フォルダ関連の処理が見られます :slight_smile: :). )

スタンドアロン構成とマルチコンテナ構成にはそれぞれ長所と短所がありますが、本番環境では完全に「リバースプロキシ付きの 2 コンテナ構成」を採用しています。移行テストやステージングでは、スタンドアロン構成が私たちには最適です。

Apache2 については、リバースプロキシを 1 つのサーバーに、コンテナを別のサーバーに設定してテストする時間があればよかったのですが、申し訳ありません。

また、2 コンテナ構成で DB リストアを行っている間もサイトを稼働させ続ける方法を考えておく必要があります。(今になって気づきましたが)templates/postgres.template.yml テンプレートは、単一のスタンドアロンコンテナ構成向けに作られています。

「いいね!」 1

補足ですが、Discourse は WSS を使用していません。Message Bus はチャンクエンコーディング(ストリーミング)によるロングポーリングを使用しています。

「いいね!」 4

そのホスト名は、Discourse にアクセスするために使用されるホスト名に設定する必要があります。app.yml 内のドメイン名が、ユーザーがブラウザに入力してサイトにアクセスするものではない場合、機能しません。

app.yml に SSL または Let’s Encrypt のテンプレートが含まれていませんよね?

force_https をオンにする必要があります。

また、ロングポーリングを機能させるために、いくつかの工夫が必要です。具体的な方法は覚えていません。

「いいね!」 1

@Grunskin さん、こんにちは。

お困りの状況は理解できます。しかし、以下の設定を行った場合:

force_https = true

これが 問題 ではありません。前述の @pfaffman さん(Meta のトップクラス移行専門家)が少なくとも 2 回指摘している通りです:

force_https = true を維持したまま、「真の問題」を特定する必要があります。

私の立場なら、私が読んだ内容に基づき、まず次のようにします。

まず、問題の単純化のため、Discourse コンテナと同じサーバー上にリバースプロキシを設定します(テストとトラブルシューティングのためだけのものです)。この単純なテストケースが完全に動作することを確認してください。その後、同じホストで動作するようになったら、そのテスト用のリバースプロキシを無効にし、ご希望の 2 サーバー構成に移行します。

これは興味深い問題です。force_https = true を維持しつつ、構造化された段階的なトラブルシューティング手法を適用すれば、解決可能です。このような問題は、まさに解決を待ち望む IT のパズルです。

きっと解決できます。


PS: もしこのパズルにうんざりしたり、退屈したりしたら、@pfaffman さんや他の経験豊富な Meta 関係者に謝礼を支払って、この行き詰まりを乗り越え、明るい未来へ進む手助けをしてもらうこともできます。


注:Apache2 を Unix ドメインソケット経由で同じサーバー上のリバースプロキシとして動作させる際には、全く問題ありませんでした。2 つの異なるサーバーで Apache2 リバースプロキシ方式を動作させるために、2 サーバー構成を設定する個人的な時間が取れないことを心よりお詫び申し上げます。現在、最終的な移行タスクとして「異常な BBCode の乱用」を Markdown 問題に修正する作業に追われており、当初の予想よりも時間がかかっています。

「いいね!」 1

DiscourseをSSL付きのlocalhostのポート8443で動作させ、UFW(ファイアウォール)で8443をロックし、Apache2で443を8443へプロキシする方式を採用することにしました。現在試行中です。

                ProxyPass / "https://localhost:8443"
                ProxyPassReverse  / "https://localhost:8443"
「いいね!」 1

Discourse を新しいセットアップに移行しましたが、ログイン中の HTTP 403 セッションの問題が発生しています。CSRF の問題だと推測していますが、現時点ではデバッグの開始方法がまったくわからず、/var/log/discourse-var-log/production.logproduction_error.log はどちらも 0 バイトです…

これを適切にデバッグする方法についてのアイデアはありますか?

現在のセットアップ:
1. haproxy を中央の HTTPS ロードバランサー/アクセラレーターとして使用(Discourse およびその他のサービスの両方)

forum >> develd apache rev. proxy p.82
backend forum-backend
   mode http
   server forum.netzwissen.de 10.10.10.14:83 cookie A check
   http-request set-header X-Forwarded-Port %[dst_port]
   http-request add-header X-Forwarded-Proto https if { ssl_fc }
   # HSTS header, 16000000 秒: 6 か月強
   http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;"

2. ローカル Apache をリバースプロキシとして使用

   <IfModule proxy_module>
    ## <https://meta.discourse.org/t/running-other-websites-on-the-same-machine-as-discourse/17247>
    ProxyPreserveHost On
    # ProxyRequests Off     
    RequestHeader set X-Forwarded-Proto expr=%{REQUEST_SCHEME}
    RequestHeader set X-Real-IP expr=%{REMOTE_ADDR}
    ProxyPass /  unix:/var/discourse/shared/web-only/apache.http.sock|http://localhost/
    ProxyPassReverse  / unix:/var/discourse/shared/web-only/apache.http.sock|http://localhost/
    </IfModule>

3) Discourse は、web_onlydata コンテナーを分離して動作しており、web_only- "templates/web.socketed.template.yml" でデプロイされています。

ログイン時に失敗するセッションリクエストは次のとおりです。

POST /session HTTP/1.1
Host: forum.netzwissen.de
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0
Accept: */*
Accept-Language: de,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Content-Length: 89
X-CSRF-Token: dtV0N6faVQSWZsg6z9ZGOxQBjuTpBZk6tAMRxaXJdwozF1kObw9UuiFnxbLf5OGDeL1DWDgZ5W3oJP7CY+LwRw==
Discourse-Present: true
X-Requested-With: XMLHttpRequest
Origin: https://forum.netzwissen.de
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Referer: https://forum.netzwissen.de/
Connection: keep-alive

web_only コンテナの webserver からの応答:

HTTP/1.1 403 Forbidden
date: Sun, 13 Mar 2022 16:41:56 GMT
server: nginx
content-type: text/plain; charset=utf-8
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
x-content-type-options: nosniff
x-download-options: noopen
x-permitted-cross-domain-policies: none
referrer-policy: strict-origin-when-cross-origin
vary: Accept
x-request-id: 778da942-3c1c-493b-946b-478984f53a8c
x-runtime: 0.003623
transfer-encoding: chunked
strict-transport-security: max-age=16000000; includeSubDomains; preload;

CSRFのことはよくわからないし、nginx(外部ウェブサーバーはApache 2.4です)もよくわかりませんが、ここで問題になっているのはCSRFだと確信しています。なぜなら、Discourseはログインなしでは正常に動作し、ログインに使用されるPOSTリクエストのみがここで失敗するからです。私の中心的なHAProxyの内部IPは10.10.10.21なので、web_onlyコンテナのdiscourse.confのymlに次のように記述しました。

set_real_ip_from 10.10.10.21/24;

デフォルトの127.0.0.1/24;も試しましたが、どちらもログイン時に同じ403エラーが発生します。