benoitc / gunicorn

gunicorn 'Green Unicorn' is a WSGI HTTP Server for UNIX, fast clients and sleepy applications.
http://www.gunicorn.org
Other
9.73k stars 1.74k forks source link

Gunicorn 23 schannel: failed to receive handshake, SSL/TLS connection failed #3279

Open juparker37 opened 1 month ago

juparker37 commented 1 month ago

I am trying to configure TLS support for my Gunicorn and Django app. Reviewing https://docs.gunicorn.org/en/stable/settings.html and configure the gunicorn.conf.py file or using the CLI for TLS cert/key/cacerts does not work.

I think the settings documentation could be improved showing what config file varaibles are needed and an example to get TLS 1.2 working and TLS 1.3 ssl_context working.

Is their an example out their to go by? My requirements are that traffic between the Nginx reverse proxy and Gunicorn use TLS. I have a 3rd party CA signed certificate, dir below.

Below is the curl trace and gunicorn.conf.py file.

#!/usr/bin/python
#  /home/djangoweb, gunicorn -c cloudmonitor/gunicorn.conf.py --error-logfile -,
#  tail -f /home/djangoweb/logs/httpd_error.log
import multiprocessing
#
wsgi_app = "cloudmonitor.wsgi:application"
bind = "192.168.46.69:9450"
daemon = 'True'
default_proc_name = 'gunicorn_httpd'
reload = 'True'
#
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'sync'
worker_connections = 1000
timeout = 30
keepalive = 2
#
errorlog = '/home/djangoweb/logs/httpd_error.log'
accesslog = '/home/djangoweb/logs/httpd_access.log'
#
ssl_version = 'TLS_SERVER'
cert_reqs = "2"
certfile = '/home/djangoweb/certs/star.mydomain.com.pem'
keyfile = '/home/djangoweb/certs/private/star.mydomain.key'
cacert = '/home/djangoweb/certs/cacerts/DigiCertCA.crt'
def ssl_context(conf, default_ssl_context_factory):
    import ssl
    context = default_ssl_context_factory()
    context.minimum_version = ssl.TLSVersion.TLSv1_3
    return context
do_handshake_on_connect = 'true'
curl -v --trace - https://server01.mydomain.com:9450/dashboard
Warning: --trace overrides an earlier trace/verbose option
== Info: Host server01.mydomain.com:9450 was resolved.
== Info: IPv6: (none)
== Info: IPv4: 192.168.46.69
== Info:   Trying 192.168.46.69:9450...
== Info: Connected to inap-aws-cm01.mydomain.com (192.168.46.69) port 9450
== Info: schannel: disabled automatic use of client certificate
== Info: ALPN: curl offers http/1.1
== Info: schannel: failed to receive handshake, SSL/TLS connection failed
== Info: Closing connection
== Info: schannel: shutting down SSL/TLS connection with server01.mydomain.com port 9450
curl: (35) schannel: failed to receive handshake, SSL/TLS connection failed
ls -la /home/djangoweb/certs/
total 24
drwxr-xr-x  4 django django 4096 Aug 16 00:21 .
drwx------ 13 django django 4096 Aug 16 01:07 ..
drwxr-xr-x  2 django django 4096 Aug 16 00:20 cacerts
drwxr-xr-x  2 django django 4096 Aug 16 00:22 private
-rw-r--r--  1 django django 7831 Aug 16 00:21 star.mydomain.com.pem
total 12
drwxr-xr-x 2 django django 4096 Aug 16 00:22 .
drwxr-xr-x 4 django django 4096 Aug 16 00:21 ..
-rw------- 1    600    600 3272 Aug 16 00:21 star.mydomain.com.key
ls -la /home/djangoweb/certs/cacerts/
total 40
drwxr-xr-x 2 django django  4096 Aug 16 00:20 .
drwxr-xr-x 4 django django  4096 Aug 16 00:21 ..
-rw-r--r-- 1 django django 30720 Aug 16 00:19 DigiCertCA.crt
pajod commented 1 month ago

Are you certain you meant to configure cert_reqs, your curl test is not using client auth. Also, reread the logs emitted by Gunicorn. If you have not seen the warning telling you ssl_version is deprecated and ignored, maybe you missed something else there?

juparker37 commented 1 month ago

Are you certain you meant to configure cert_reqs, your curl test is not using client auth. Also, reread the logs emitted by Gunicorn. If you have not seen the warning telling you ssl_version is deprecated and ignored, maybe you missed something else there?

I am trying to configure mTLS actually between Ngnix reverse proxy and Gunicorn. I assume the cert_reqs would be needed and both sides need the TLS CA certificate installed.

Yes, you are correct on the Curl command used. I changed it to just a Curl request without options. But when trying to hit the proxied "/dashboard/" via 9443 it has a 301 redirect but the TLS connection fails.

curl https://server01.mydomain.com:9443/dashboard
<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.14.1</center>
</body>
</html>

curl https://server01.mydoamin.com:9450/dashboard curl: (35) schannel: failed to receive handshake, SSL/TLS connection failed

juparker37 commented 1 month ago

Are we assuming that Gunicorn does not support mTLS (mutual TLS) to secure the backend instead of terminating the TLS connection a Nginx and the plaintext talking to the app?

juparker37 commented 1 month ago

When I go to https://192.168.46.69:9450/dashboard using Incognito directly and bypass proxy, the Gunicorn TLS config is still not working. Browser is still saying connection not secure.

I tried to comment out all lines in the gunicorn.conf.py and use the cli

gunicorn --certfile /home/djangoweb/certs/star.mydomain.com.pem --keyfile /home/djangoweb/certs/private/star.mydoamin.com.key -c cloudmonitor/gunicorn.conf.py --error-logfile -
2024/08/16 11:54:54 [error] 4169609#0: *1 peer closed connection in SSL handshake (104: Connection reset by peer) while SSL handshaking to upstream, client: 10.206.10.11, server: server01.oversightsystems.com, request: "GET /dashboard/ HTTP/1.1", upstream: "https://192.168.46.69:9450/dashboard/", host: "server01.mydomain:9443"

For sanity, I may try my Django app with uwsgi to see if I can reproduce the issue or not.