From 7e9f8b5b02f132643f63941c02f06cf230287e5f Mon Sep 17 00:00:00 2001 From: John Hensley Date: Sun, 6 Feb 2011 11:06:30 -0500 Subject: [PATCH] Change handling of headers that indicate SSL requests. Instead of hardcoding X-Forwarded-Protocol and X-Forwarded-SSL, make the header and value configurable, with no default that would enable a client to spoof secure requests if the reverse proxy is not configured to strip the header used. --- doc/htdocs/configure.html | 56 ++++++++++++++++++++++++++++----------- gunicorn/config.py | 29 ++++++++++++++++++++ gunicorn/http/wsgi.py | 7 ++--- 3 files changed, 73 insertions(+), 19 deletions(-) diff --git a/doc/htdocs/configure.html b/doc/htdocs/configure.html index 498220eb..f7df8dfa 100644 --- a/doc/htdocs/configure.html +++ b/doc/htdocs/configure.html @@ -336,6 +336,29 @@ int(value, 0) (0 means Python guesses the base, so values like "0",

This path should be writable by the process permissions set for Gunicorn workers. If not specified, Gunicorn will choose a system generated temporary directory.

+ +
+

secure_scheme_headers

+ +

A dictionary containing headers and values that the front-end proxy +uses to indicate HTTPS requests. These tell gunicorn to set +wsgi.url_scheme to "https", so your application can tell that the +request is secure.

+ +

The dictionary should map upper-case header names to exact string +values. The value comparisons are case-sensitive, unlike the header +names, so make sure they're exactly what your front-end proxy sends +when handling HTTPS requests.

+ +

It is important that your front-end proxy configuration ensures that +the headers defined here can not be passed directly from the client.

+
@@ -549,28 +572,29 @@ the just-exited Worker.

  • group
  • umask
  • tmp_upload_dir
  • +
  • secure_scheme_headers
  • -
  • Logging
      -
    • logfile
    • -
    • loglevel
    • -
    • logconfig
    • +
    • Logging
    • -
    • Process Naming
        -
      • proc_name
      • -
      • default_proc_name
      • +
      • Process Naming
      • -
      • Server Hooks diff --git a/gunicorn/config.py b/gunicorn/config.py index d1f4a73e..6f156ef9 100644 --- a/gunicorn/config.py +++ b/gunicorn/config.py @@ -179,6 +179,11 @@ def validate_bool(val): else: raise ValueError("Invalid boolean: %s" % val) +def validate_dict(val): + if not isinstance(val, dict): + raise TypeError("Value is not a dictionary: %s " % val) + return val + def validate_pos_int(val): if not isinstance(val, (types.IntType, types.LongType)): val = int(val, 0) @@ -522,6 +527,30 @@ class TmpUploadDir(Setting): temporary directory. """ +class SecureSchemeHeader(Setting): + name = "secure_scheme_headers" + section = "Server Mechanics" + validator = validate_dict + default = { + "X-FORWARDED-PROTOCOL": "ssl", + "X-FORWARDED-SSL": "on" + } + desc = """\ + + A dictionary containing headers and values that the front-end proxy + uses to indicate HTTPS requests. These tell gunicorn to set + wsgi.url_scheme to "https", so your application can tell that the + request is secure. + + The dictionary should map upper-case header names to exact string + values. The value comparisons are case-sensitive, unlike the header + names, so make sure they're exactly what your front-end proxy sends + when handling HTTPS requests. + + It is important that your front-end proxy configuration ensures that + the headers defined here can not be passed directly from the client. + """ + class Logfile(Setting): name = "logfile" section = "Logging" diff --git a/gunicorn/http/wsgi.py b/gunicorn/http/wsgi.py index a25ee31b..313a7adf 100644 --- a/gunicorn/http/wsgi.py +++ b/gunicorn/http/wsgi.py @@ -68,6 +68,8 @@ def create(req, sock, client, server, cfg): url_scheme = "http" script_name = os.environ.get("SCRIPT_NAME", "") + secure_headers = getattr(cfg, "secure_scheme_headers") + for hdr_name, hdr_value in req.headers: if hdr_name == "EXPECT": # handle expect @@ -75,9 +77,8 @@ def create(req, sock, client, server, cfg): sock.send("HTTP/1.1 100 Continue\r\n\r\n") elif hdr_name == "X-FORWARDED-FOR": forward = hdr_value - elif hdr_name == "X-FORWARDED-PROTOCOL" and hdr_value.lower() == "ssl": - url_scheme = "https" - elif hdr_name == "X-FORWARDED-SSL" and hdr_value.lower() == "on": + elif (hdr_name.upper() in secure_headers and + hdr_value == secure_headers[hdr_name.upper()]): url_scheme = "https" elif hdr_name == "HOST": server = hdr_value