Смешанный SSL после обновления

У меня была (была!) стандартная рабочая установка обратного прокси.
Всё обслуживалось по HTTPS, как и задумано.

Я перешёл на несколько версий выше и столкнулся с «смертельным обновлением», при котором экран остаётся пустым, как сообщается здесь: Upgrade discourse doesn't work - #6 by Andrew_Bond

Я вручную обновил Discourse с помощью команды ./launcher rebuild app и теперь нахожусь на сборке 2.9.0.beta14 (0da79561c3).

Я заметил, что при попытке сохранения черновика или при выполнении большинства других действий, таких как публикация или вход в систему, я получал ошибку 403 Forbidden (через инспектор Chrome).

Мой логотип отображается в левом верхнем углу сайта.

Если я вручную отключаю принудительное использование HTTPS через командную строку, логотип перестаёт загружаться, так как я оказываюсь в режиме смешанного контента, где HTTP-элементы блокируются. Все остальные элементы сайта продолжают работать.

Я не вносил никаких изменений в обратный прокси Nginx. Я пытался снова пересобрать приложение, но безрезультатно.

Я предполагаю, что в версиях, на которые я обновился, что-то изменилось в работе Discourse. Это был последний идентификатор сборки, с которым работала опция принудительного HTTPS: e9f53dbe0028ca7a5d8926711a1944765d34347

Зафиксированные ошибки сейчас:

Привет, Эндрю, спасибо за отчет. Эта проблема конкретно затрагивает конфигурации обратного прокси, где несколько заголовков X-Forwarded-Proto добавляются к запросу до того, как он попадает в контейнер приложения.

Я только что объединил исправление в ветку main — оно должно попасть в tests-passed примерно через 20 минут.

Как только оно появится в tests-passed, вам потребуется выполнить полную пересборку (изменения конфигурации nginx не применяются во время обновлений через интерфейс).

Большое спасибо за быстрый ответ и оперативное решение!

Итак, обновление.

Я пересобрал приложение с помощью команды ./launcher rebuild app.

Проблем при этом не возникло.

Ошибки исчезли, когда я принудительно включил HTTPS, за исключением одного случая:

Ошибки 403 (доступ запрещён) больше нет, но, например, при попытке войти в систему появляется окно входа, которое, казалось бы, принимает данные, после чего снова показывается пользователь без авторизации.

Та же ошибка, что и выше, сохраняется при перезагрузке страницы.

Я снова отключил принудительное использование HTTPS, и сайт работает — для ясности: логотипы всё ещё не отображаются.

Эта ошибка не связана с этим — она вызвана тем, что Cloudflare внедряет скрипты аналитики (а политика безопасности контента по умолчанию в Discourse блокирует это внедрение).

Можете рассказать подробнее о прокси, которые находятся между пользователями и Discourse? Предположительно, первый уровень — это Cloudflare? Что ещё находится между Cloudflare и контейнером приложения Discourse?

Конечно!

Вы абсолютно правы. Сначала срабатывает Cloudflare, а затем у нас есть обратный прокси-сервер nginx, предоставляемый CentminMod для Docker.

Можете поделиться вашей конфигурацией для этого? В частности, добавляет ли она дополнительный заголовок X-Forwarded-Proto?

Конечно!

# Руководство по началу работы с Centmin Mod
# обязательно прочитайте https://centminmod.com/getstarted.html
# Для настройки HTTP/2 SSL
# прочитайте https://centminmod.com/nginx_configure_https_ssl_spdy.html

# перенаправление с www на non-www с принудительным SSL
# раскомментируйте, сохраните файл и перезапустите Nginx для активации
# если не уверены, используйте return 302 перед использованием return 301
 server {
       listen   80;
       server_name exiges.com www.exiges.com;
       return 302 https://$server_name$request_uri;
 }

server {
  listen 443 ssl http2;
  server_name exiges.com www.exiges.com;

  ssl_dhparam /usr/local/nginx/conf/ssl/exiges.com/dhparam.pem;
  ssl_certificate      /usr/local/nginx/conf/ssl/exiges.com/exiges.com.crt;
  ssl_certificate_key  /usr/local/nginx/conf/ssl/exiges.com/exiges.com.key;
  include /usr/local/nginx/conf/ssl_include.conf;

  # сертификат для аутентифицированного источника Cloudflare community.centminmod.com/threads/13847/
  #ssl_client_certificate /usr/local/nginx/conf/ssl/cloudflare/exiges.com/origin.crt;
  #ssl_verify_client on;
  
  
  
  # рекомендации Mozilla
  ssl_ciphers TLS13-AES-128-GCM-SHA256:TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS;
  ssl_prefer_server_ciphers   on;
  #add_header Alternate-Protocol  443:npn-spdy/3;

  # перед включением HSTS прочитайте строку ниже на centminmod.com/nginx_domain_dns_setup.html#hsts
  #add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
  #add_header X-Frame-Options SAMEORIGIN;
  add_header X-Xss-Protection "1; mode=block" always;
  add_header X-Content-Type-Options "nosniff" always;
  #add_header Referrer-Policy "strict-origin-when-cross-origin";
  #add_header Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()";
  #spdy_headers_comp 5;
  ssl_buffer_size 1369;
  ssl_session_tickets on;
  
  # включить stapling OCSP
  #resolver 8.8.8.8 8.8.4.4 1.1.1.1 1.0.0.1 valid=10m;
  #resolver_timeout 10s;
  #ssl_stapling on;
  #ssl_stapling_verify on;
  #ssl_trusted_certificate /usr/local/nginx/conf/ssl/exiges.com/exiges.com-trusted.crt;

# ngx_pagespeed и обработчик ngx_pagespeed
#include /usr/local/nginx/conf/pagespeed.conf;
#include /usr/local/nginx/conf/pagespeedhandler.conf;
#include /usr/local/nginx/conf/pagespeedstatslog.conf;

  # limit_conn limit_per_ip 16;
  # ssi  on;

  access_log /home/nginx/domains/exiges.com/log/access.log combined buffer=256k flush=5m;
  error_log /home/nginx/domains/exiges.com/log/error.log;

  include /usr/local/nginx/conf/autoprotect/exiges.com/autoprotect-exiges.com.conf;
  root /home/nginx/domains/exiges.com/public;
  # раскомментируйте включение cloudflare.conf, если используете Cloudflare для
  # сервера и/или виртуального хоста
  #include /usr/local/nginx/conf/cloudflare.conf;
  include /usr/local/nginx/conf/503include-main.conf;

 
location / {
       proxy_pass http://unix:/var/discourse/shared/standalone/nginx.http.sock:;
       proxy_set_header        X-Forwarded-Proto https;
       proxy_http_version 1.1;
       proxy_set_header X-Forwarded-Proto $scheme;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       proxy_set_header X-Real-IP $remote_addr;
   }   

  include /usr/local/nginx/conf/php.conf;
  
  #include /usr/local/nginx/conf/pre-staticfiles-local-exiges.com.conf;
  #include /usr/local/nginx/conf/pre-staticfiles-global.conf;
  #include /usr/local/nginx/conf/staticfiles.conf;
  #include /usr/local/nginx/conf/drop.conf;
  #include /usr/local/nginx/conf/errorpage.conf;
  #include /usr/local/nginx/conf/vts_server.conf;
}

Я вижу ссылку на заголовок x-proto ..

   proxy_set_header X-Forwarded-Proto $scheme;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

Как Cloudflare подключается к вашему источнику? Используется ли SSL/TLS? В идеале здесь следует использовать режим «Full» или «Full (Strict)».

Гибко. Я был ленив и не использовал серверные сертификаты.

Я рекомендую настроить один из защищенных вариантов SSL от Cloudflare. Режим «Flexible» означает, что трафик между Cloudflare и вашим сервером не зашифрован.

Посмотрим ещё раз на конфигурацию:

Похоже, вы устанавливаете заголовок в значение https, а затем снова устанавливаете его двумя строками ниже в $scheme (которое будет http в соответствии с вашей конфигурацией Cloudflare). Если вы удалите второе установление, то Discourse будет сообщено, что соединение защищено (хотя на самом деле это не так).

Интересно.

Хотя мне кажется странным, что раньше это работало.

Я подозреваю (но пока не подтвердил на 100%), что ранее контейнер Discourse с NGINX обращался к «первому» заголовку X-Forwarded-Proto. Теперь же он обращается к «последнему» заголовку X-Forwarded-Proto, что, по сути, безопаснее, поскольку этот заголовок поступает от «наиболее свежего» прокси, обладающего наибольшей информацией о входящем запросе.

Таким образом, до обновления система обращалась к заголовку X-Forwarded-Proto, добавленному Cloudflare (https). Сейчас же она обращается к заголовку X-Forwarded-Proto, добавленному конфигурацией вашего обратного прокси «centminmod» (http).

Я удалил строку proxy_set_header X-Forwarded-Proto $scheme; из конфигурации, и теперь всё работает.

Для тех, кто читает и настраивает Centmin Mod, важно знать: для Nginx существует ДВЕ конфигурационные файла.

Один для HTTP, другой для HTTPS.

Я удалил вторую строку proxy_set_header X-Forwarded-Proto $scheme; из конфигурации HTTP-сайта, и теперь всё работает как положено.

Вот мой файл exiges.com.conf:

# Руководство по началу работы с Centmin Mod
# необходимо прочитать https://centminmod.com/getstarted.html

# Перенаправление с non-www на www
# раскомментируйте, сохраните файл и перезапустите Nginx для активации
# если не уверены, используйте return 302 перед использованием return 301
#server {
#            listen   80;
#            server_name exiges.com;
#            return 301 $scheme://www.exiges.com$request_uri;
#       }

server {
  server_name exiges.com www.exiges.com;
#return 301 https://exiges.com.com$request_uri;
#return 301 $scheme://www.exiges.com$request_uri;

# ngx_pagespeed и обработчик ngx_pagespeed
#include /usr/local/nginx/conf/pagespeed.conf;
#include /usr/local/nginx/conf/pagespeedhandler.conf;
#include /usr/local/nginx/conf/pagespeedstatslog.conf;

  #add_header X-Frame-Options SAMEORIGIN;
  add_header X-Xss-Protection "1; mode=block" always;
  add_header X-Content-Type-Options "nosniff" always;
  #add_header Referrer-Policy "strict-origin-when-cross-origin";
  #add_header Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()";

  # limit_conn limit_per_ip 16;
  # ssi  on;

  access_log /home/nginx/domains/exiges.com/log/access.log combined buffer=256k flush=5m;
  error_log /home/nginx/domains/exiges.com/log/error.log;

  include /usr/local/nginx/conf/autoprotect/exiges.com/autoprotect-exiges.com.conf;
  root /home/nginx/domains/exiges.com/public;
  # раскомментируйте включение cloudflare.conf, если используете Cloudflare для
  # сервера и/или виртуального хоста
  #include /usr/local/nginx/conf/cloudflare.conf;
  include /usr/local/nginx/conf/503include-main.conf;

  # предотвращение доступа к директориям и файлам, начинающимся с точки
  #location ~ (?:^|/)\. {
  # deny all;
  #}

  location / {
 proxy_pass http://unix:/var/discourse/shared/standalone/nginx.http.sock:;
		    proxy_set_header        X-Forwarded-Proto https;	
                #proxy_set_header Host $http_host;
                proxy_http_version 1.1;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                #proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header X-Real-IP $remote_addr;
  #include /usr/local/nginx/conf/503include-only.conf;

# блокировка распространенных эксплойтов, SQL-инъекций и т.д.
#include /usr/local/nginx/conf/block.conf;

  # Включает listing директорий, если файл index не найден
  #autoindex  on;

  # Отображает время списка файлов как локальное
  #autoindex_localtime on;

  # Пример постоянных ссылок WordPress
  #try_files $uri $uri/ /index.php?q=$uri&$args;

  }

  include /usr/local/nginx/conf/php.conf;
  
  #include /usr/local/nginx/conf/pre-staticfiles-local-exiges.com.conf;
  #include /usr/local/nginx/conf/pre-staticfiles-global.conf;
  #include /usr/local/nginx/conf/staticfiles.conf;
  #include /usr/local/nginx/conf/drop.conf;
  #include /usr/local/nginx/conf/errorpage.conf;
  #include /usr/local/nginx/conf/vts_server.conf;
}

А вот мой файл exiges.com.conf.ssl:

# Руководство по началу работы с Centmin Mod
# необходимо прочитать https://centminmod.com/getstarted.html
# Для настройки HTTP/2 SSL
# прочитайте https://centminmod.com/nginx_configure_https_ssl_spdy.html

# Принудительное перенаправление с www на non-www с SSL
# раскомментируйте, сохраните файл и перезапустите Nginx для активации
# если не уверены, используйте return 302 перед использованием return 301
 server {
       listen   80;
       server_name exiges.com www.exiges.com;
       return 302 https://$server_name$request_uri;
 }

server {
  listen 443 ssl http2;
  server_name exiges.com www.exiges.com;

  ssl_dhparam /usr/local/nginx/conf/ssl/exiges.com/dhparam.pem;
  ssl_certificate      /usr/local/nginx/conf/ssl/exiges.com/exiges.com.crt;
  ssl_certificate_key  /usr/local/nginx/conf/ssl/exiges.com/exiges.com.key;
  include /usr/local/nginx/conf/ssl_include.conf;

  # Сертификат аутентифицированного получения от Cloudflare community.centminmod.com/threads/13847/
  #ssl_client_certificate /usr/local/nginx/conf/ssl/cloudflare/exiges.com/origin.crt;
  #ssl_verify_client on;
  
  
  
  # Рекомендации Mozilla
  ssl_ciphers TLS13-AES-128-GCM-SHA256:TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS;
  ssl_prefer_server_ciphers   on;
  #add_header Alternate-Protocol  443:npn-spdy/3;

  # Перед включением HSTS прочитайте центминмод.com/nginx_domain_dns_setup.html#hsts
  #add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
  #add_header X-Frame-Options SAMEORIGIN;
  add_header X-Xss-Protection "1; mode=block" always;
  add_header X-Content-Type-Options "nosniff" always;
  #add_header Referrer-Policy "strict-origin-when-cross-origin";
  #add_header Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()";
  #spdy_headers_comp 5;
  ssl_buffer_size 1369;
  ssl_session_tickets on;
  
  # Включение OCSP stapling
  #resolver 8.8.8.8 8.8.4.4 1.1.1.1 1.0.0.1 valid=10m;
  #resolver_timeout 10s;
  #ssl_stapling on;
  #ssl_stapling_verify on;
  #ssl_trusted_certificate /usr/local/nginx/conf/ssl/exiges.com/exiges.com-trusted.crt;

# ngx_pagespeed и обработчик ngx_pagespeed
#include /usr/local/nginx/conf/pagespeed.conf;
#include /usr/local/nginx/conf/pagespeedhandler.conf;
#include /usr/local/nginx/conf/pagespeedstatslog.conf;

  # limit_conn limit_per_ip 16;
  # ssi  on;

  access_log /home/nginx/domains/exiges.com/log/access.log combined buffer=256k flush=5m;
  error_log /home/nginx/domains/exiges.com/log/error.log;

  include /usr/local/nginx/conf/autoprotect/exiges.com/autoprotect-exiges.com.conf;
  root /home/nginx/domains/exiges.com/public;
  # раскомментируйте включение cloudflare.conf, если используете Cloudflare для
  # сервера и/или виртуального хоста
  include /usr/local/nginx/conf/cloudflare.conf;
  include /usr/local/nginx/conf/503include-main.conf;

 
location / {
       proxy_pass http://unix:/var/discourse/shared/standalone/nginx.http.sock:;
       proxy_set_header        X-Forwarded-Proto https;
       proxy_http_version 1.1;
       proxy_set_header X-Forwarded-Proto $scheme;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       proxy_set_header X-Real-IP $remote_addr;
   }   

  include /usr/local/nginx/conf/php.conf;
  
  #include /usr/local/nginx/conf/pre-staticfiles-local-exiges.com.conf;
  #include /usr/local/nginx/conf/pre-staticfiles-global.conf;
  #include /usr/local/nginx/conf/staticfiles.conf;
  #include /usr/local/nginx/conf/drop.conf;
  #include /usr/local/nginx/conf/errorpage.conf;
  #include /usr/local/nginx/conf/vts_server.conf;
}

@david, огромное спасибо за ваше время, потраченное на решение этой проблемы.

Кстати, я теперь полностью настроен и не готов идти на компромиссы в отношении сертификатов.