Приложение Flask с прокси-сервером Apache Gunicorn не работает на HTTPS

#apache #flask #ssl #gunicorn #mod-proxy

Вопрос:

Обновления внизу, я вроде как решил это, но не уверен, что решение правильное.


У меня Apache работает на CentOS с прокси-сервером на локальный порт 8080, где у меня запущено приложение Flask с использованием Gunicorn. Эта настройка работает на порту Apache 80 (HTTP), и я могу подключиться к нему, используя свой домен http://example.com с помощью браузера, но теперь я попытался настроить SSL / HTTPS, и это просто не работает.

Переход к https://example.com пытается загрузить страницу в течение некоторого времени (например, 30 секунд), а затем показывает страницу с ошибкой 502:

 Proxy Error
The proxy server received an invalid response from an upstream server.
The proxy server could not handle the request GET /.
Reason: Error reading from remote server
 

Журнал ошибок Apache:

 [proxy_http:error] [pid 30209] (103)Software caused connection abort: [client xx.xxx.xxx.xxx:60556] AH01102: error reading status line from remote server localhost:8080
[proxy:error] [pid 30209] [client xx.xxx.xxx.xxx:60556] AH00898: Error reading from remote server returned by /
 

Журнал ошибок Gunicorn (первые 7 строк взяты из запуска Gunicorn, понятия не имею, почему эта информация находится в журнале ошибок, последние 3 строки — когда запрос HTTPS возвращает ошибку 502):

 [29478] [INFO] Listening at: http://127.0.0.1:8080 (29478)
[29478] [INFO] Using worker: sync
[29480] [INFO] Booting worker with pid: 29480
[29481] [INFO] Booting worker with pid: 29481
[29482] [INFO] Booting worker with pid: 29482
[29483] [INFO] Booting worker with pid: 29483
[29484] [INFO] Booting worker with pid: 29484
[29478] [CRITICAL] WORKER TIMEOUT (pid:29480)
[29480] [INFO] Worker exiting (pid: 29480)
[29554] [INFO] Booting worker with pid: 29554
 

Конфигурация Apache, которая работает для HTTP ( /etc/httpd/conf/httpd.conf ):

 Listen 80

#other default config values here

<VirtualHost *:80>
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/
</VirtualHost>

IncludeOptional conf.d/*.conf
 

Конфигурация Apache, которая не работает для HTTPS ( /etc/httpd/conf.d/ssl.conf ):

 Listen 443 https

#other default config values here

<VirtualHost *:443>

#other default config values here too

SSLCertificateFile /etc/pki/tls/certs/cert.pem
SSLCertificateKeyFile /etc/pki/tls/private/cert.key

SSLProxyEngine on
ProxyPass / https://localhost:8080/
ProxyPassReverse / https://localhost:8080/

</VirtualHost>
 

If I remove/comment the SSLProxyEngine , ProxyPass amd ProxyPassReverse lines and restart Apache I get the default Apache welcome page and HTTPS works just fine so clearly the problem is in the Proxy somehow?

The flask app is started with Gunicorn by:

gunicorn --config gunicorn_config.py app:app

gunicorn_config.py:

 workers = 5
bind = '127.0.0.1:8080'
umask = 0o007
reload = True
accesslog = 'log_gunicorn_access.txt'
errorlog = 'log_gunicorn_error.txt'
 

app.py:

 from flask import Flask

app = Flask(__name__)

@app.route('/')
    def hello_world():  
        return 'Hello world!'

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8080)
 

And once again, this works when navigating to my domain using HTTP but doesn’t work when using HTTPS.

Any help?


UPDATE:

I managed to get another error. Now navigating to https://example.com loads instantly and shows 500 error page:

 Proxy Error
The proxy server could not handle the request GET /.
Reason: Error during SSL Handshake with remote server
 

Apache error log:

 [proxy:error] [pid 32385] (502)Unknown error 502: [client xx.xxx.xxx.xxx:50932] AH01084: pass request body failed to [::1]:8080 (localhost)
[proxy:error] [pid 32385] [client xx.xxx.xxx.xxx:50932] AH00898: Error during SSL Handshake with remote server returned by /
[proxy_http:error] [pid 32385] [client xx.xxx.xxx.xxx:50932] AH01097: pass request body failed to [::1]:8080 (localhost) from xx.xxx.xxx.xxx ()
 

No more any errors in Gunicorn error log.

Я добавил эти две строки в gunicorn_config.py:

 keyfile = '/etc/pki/tls/private/cert.key'
certfile = '/etc/pki/tls/certs/cert.pem'
 

и убедился, что оба файла доступны пользователю, запускающему Gunicorn (chmod o r cert.key /pem).

Понятия не имею, должен ли я изменить его таким образом, поскольку я думал, что трафик должен выглядеть так: клиент — https-> Apache, а затем Apache -http-> Gunicorn.

Также HTTP (http://example.com ) больше не работает и выдает предыдущую страницу с ошибкой 502, но я предполагаю, что запуск Gunicorn с конфигурациями сертификата больше не разрешает HTTP и должен будет дважды запускать приложение с разными конфигурациями).


ОБНОВЛЕНИЕ 2:

Я добавил больше протоколирования Apache, добавив эту строку во /etc/httpd/conf.d/ssl.conf внутренний виртуальный хост:

LogLevel info

И теперь я получил дополнительную информацию в журнале ошибок Apache:

 [ssl:info] [pid 3808] [remote 127.0.0.1:8080] AH02411: SSL Proxy: Peer certificate does not match for hostname localhost
 

Затем я добавил новую строку на /etc/httpd/conf.d/ssl.conf внутренний виртуальный хост:

SSLProxyCheckPeerName off

И теперь я получил еще одну ошибку Apache:

 [ssl:info] [pid 3999] [remote 127.0.0.1:8080] AH02005: SSL Proxy: Peer certificate CN mismatch: Certificate CN: example.com Requested hostname: localhost
 

Добавлена новая строка для /etc/httpd/conf.d/ssl.conf внутреннего виртуального хоста:

SSLProxyCheckPeerCN off

Aaa И теперь переходим к https://example.com правильно работает, и я получаю «Привет, мир» обратно из приложения!

Теперь, я думаю, мой вопрос также нуждается в обновлении: это плохая практика, неправильная или небезопасная в использовании SSLProxyCheckPeerName off и SSLProxyCheckPeerCN off в этом контексте? Или есть лучший способ, поскольку я не думаю, что есть способ заказать официальный SSL-сертификат на localhost?

Ответ №1:

Вы используете

 ProxyPass / http://localhost:8080/
 

и

 ProxyPass / https://localhost:8080/
 

(обратите внимание на разницу в 1 букву).

Вы localhost:8080 будете обслуживать либо http, либо https. Основываясь на вашем описании (и общих ожиданиях), оно обслуживает http. Если вы прокси-сервер даже вашего виртуального хоста : 443 для http, он будет работать лучше.

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

Комментарии:

1. Ах, это имеет смысл, да, я хотел обслуживать только client -> Apache через https, а не обрабатывать его с помощью внутреннего Apache -> Gunicorn / Flask. Не думайте, что приложение заботится о том, обслуживается ли оно через http / https. Спасибо!

2. ну, некоторым приложениям не все равно — хотя бы для того, чтобы иметь возможность генерировать ссылки, использующие тот же протокол. Или отказаться от принятия любых незашифрованных учетных данных. Повезло (в некотором масштабе), если вы не столкнетесь с этим. С другой стороны, это привело бы к обнаружению целого класса ошибок.

3. Я буду иметь это в виду, если у меня возникнут какие-либо проблемы! Знаете ли вы какие-либо ссылки / ресурсы, где эта проблема объясняется более подробно, мне интересно узнать больше об этом и возможных способах ее решения.