diff --git a/examples/example_config.py b/examples/example_config.py index 83c3ffbc..5a399a49 100644 --- a/examples/example_config.py +++ b/examples/example_config.py @@ -215,32 +215,26 @@ def worker_int(worker): def worker_abort(worker): worker.log.info("worker received SIGABRT signal") -def ssl_context(conf): +def ssl_context(conf, default_ssl_context_factory): import ssl - def set_defaults(context): - context.verify_mode = conf.cert_reqs - context.minimum_version = ssl.TLSVersion.TLSv1_3 - if conf.ciphers: - context.set_ciphers(conf.ciphers) - if conf.ca_certs: - context.load_verify_locations(cafile=conf.ca_certs) + # The default SSLContext returned by the factory function is initialized + # with the TLS parameters from config, including TLS certificates and other + # parameters. + context = default_ssl_context_factory() - # Return different server certificate depending which hostname the client - # uses. Requires Python 3.7 or later. + # The SSLContext can be further customized, for example by enforcing + # minimum TLS version. + context.minimum_version = ssl.TLSVersion.TLSv1_3 + + # Server can also return different server certificate depending which + # hostname the client uses. Requires Python 3.7 or later. def sni_callback(socket, server_hostname, context): if server_hostname == "foo.127.0.0.1.nip.io": - new_context = ssl.SSLContext() + new_context = default_ssl_context_factory() new_context.load_cert_chain(certfile="foo.pem", keyfile="foo-key.pem") - set_defaults(new_context) socket.context = new_context - context = ssl.SSLContext(conf.ssl_version) context.sni_callback = sni_callback - set_defaults(context) - - # Load fallback certificate that will be returned when there is no match - # or client did not set TLS SNI (server_hostname == None) - context.load_cert_chain(certfile=conf.certfile, keyfile=conf.keyfile) return context diff --git a/gunicorn/config.py b/gunicorn/config.py index 3f397d12..a9265374 100644 --- a/gunicorn/config.py +++ b/gunicorn/config.py @@ -1970,11 +1970,11 @@ class OnExit(Setting): class NewSSLContext(Setting): name = "ssl_context" section = "Server Hooks" - validator = validate_callable(1) + validator = validate_callable(2) type = callable - def ssl_context(config): - return None + def ssl_context(config, default_ssl_context_factory): + return default_ssl_context_factory() default = staticmethod(ssl_context) desc = """\ @@ -1983,7 +1983,10 @@ class NewSSLContext(Setting): Allows fully customized SSL context to be used in place of the default context. - The callable needs to accept a single instance variable for the Config. + The callable needs to accept an instance variable for the Config and + a factory function that returns default SSLContext which is initialized + with certificates, private key, cert_reqs, and ciphers according to + config and can be further customized by the callable. The callable needs to return SSLContext object. """ diff --git a/gunicorn/sock.py b/gunicorn/sock.py index 1481c23b..37631e6b 100644 --- a/gunicorn/sock.py +++ b/gunicorn/sock.py @@ -212,8 +212,7 @@ def close_sockets(listeners, unlink=True): os.unlink(sock_name) def ssl_context(conf): - context = conf.ssl_context(conf) - if context is None: + def default_ssl_context_factory(): context = ssl.SSLContext(conf.ssl_version) context.load_cert_chain(certfile=conf.certfile, keyfile=conf.keyfile) context.verify_mode = conf.cert_reqs @@ -221,8 +220,9 @@ def ssl_context(conf): context.set_ciphers(conf.ciphers) if conf.ca_certs: context.load_verify_locations(cafile=conf.ca_certs) + return context - return context + return conf.ssl_context(conf, default_ssl_context_factory) def ssl_wrap_socket(sock, conf): return ssl_context(conf).wrap_socket(sock, server_side=True,