Skip to content

Commit 3f56d76

Browse files
authored
Merge pull request #3192 from pajod/patch-allowed-script-name
22.0.0 regression: We need a better default treatment of SCRIPT_NAME
2 parents 06d537d + 256d474 commit 3f56d76

File tree

7 files changed

+179
-53
lines changed

7 files changed

+179
-53
lines changed

docs/source/deploy.rst

+11-7
Original file line numberDiff line numberDiff line change
@@ -246,20 +246,23 @@ to the newly created unix socket:
246246
After=network.target
247247

248248
[Service]
249+
# gunicorn can let systemd know when it is ready
249250
Type=notify
251+
NotifyAccess=main
250252
# the specific user that our service will run as
251253
User=someuser
252254
Group=someuser
253-
# another option for an even more restricted service is
254-
# DynamicUser=yes
255-
# see http://0pointer.net/blog/dynamic-users-with-systemd.html
255+
# this user can be transiently created by systemd
256+
# DynamicUser=true
256257
RuntimeDirectory=gunicorn
257258
WorkingDirectory=/home/someuser/applicationroot
258259
ExecStart=/usr/bin/gunicorn applicationname.wsgi
259260
ExecReload=/bin/kill -s HUP $MAINPID
260261
KillMode=mixed
261262
TimeoutStopSec=5
262263
PrivateTmp=true
264+
# if your app does not need administrative capabilities, let systemd know
265+
# ProtectSystem=strict
263266

264267
[Install]
265268
WantedBy=multi-user.target
@@ -272,11 +275,12 @@ to the newly created unix socket:
272275
[Socket]
273276
ListenStream=/run/gunicorn.sock
274277
# Our service won't need permissions for the socket, since it
275-
# inherits the file descriptor by socket activation
276-
# only the nginx daemon will need access to the socket
278+
# inherits the file descriptor by socket activation.
279+
# Only the nginx daemon will need access to the socket:
277280
SocketUser=www-data
278-
# Optionally restrict the socket permissions even more.
279-
# SocketMode=600
281+
SocketGroup=www-data
282+
# Once the user/group is correct, restrict the permissions:
283+
SocketMode=0660
280284

281285
[Install]
282286
WantedBy=sockets.target

docs/source/faq.rst

+7-1
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,14 @@ How do I set SCRIPT_NAME?
1111
-------------------------
1212

1313
By default ``SCRIPT_NAME`` is an empty string. The value could be set by
14-
setting ``SCRIPT_NAME`` in the environment or as an HTTP header.
14+
setting ``SCRIPT_NAME`` in the environment or as an HTTP header. Note that
15+
this headers contains and underscore, so it is only accepted from trusted
16+
forwarders listed in the :ref:`forwarded-allow-ips` setting.
1517

18+
.. note::
19+
20+
If your application should appear in a subfolder, your ``SCRIPT_NAME``
21+
would typically start with single slash but contain no trailing slash.
1622

1723
Server Stuff
1824
============

docs/source/news.rst

+21-12
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,29 @@ Changelog
55
23.0.0 - unreleased
66
===================
77

8-
* minor docs fixes (:pr:`3217`, :pr:`3089`, :pr:`3167`)
9-
* worker_class parameter accepts a class (:pr:`3079`)
10-
* fix deadlock if request terminated during chunked parsing (:pr:`2688`)
11-
* permit receiving Transfer-Encodings: compress, deflate, gzip (:pr:`3261`)
12-
* permit Transfer-Encoding headers specifying multiple encodings. note: no parameters, still (:pr:`3261`)
13-
* sdist generation now explicitly excludes sphinx build folder (:pr:`3257`)
14-
* decode bytes-typed status (as can be passed by gevent) as utf-8 instead of raising `TypeError` (:pr:`2336`)
15-
* raise correct Exception when encounting invalid chunked requests (:pr:`3258`)
8+
- minor docs fixes (:pr:`3217`, :pr:`3089`, :pr:`3167`)
9+
- worker_class parameter accepts a class (:pr:`3079`)
10+
- fix deadlock if request terminated during chunked parsing (:pr:`2688`)
11+
- permit receiving Transfer-Encodings: compress, deflate, gzip (:pr:`3261`)
12+
- permit Transfer-Encoding headers specifying multiple encodings. note: no parameters, still (:pr:`3261`)
13+
- sdist generation now explicitly excludes sphinx build folder (:pr:`3257`)
14+
- decode bytes-typed status (as can be passed by gevent) as utf-8 instead of raising `TypeError` (:pr:`2336`)
15+
- raise correct Exception when encounting invalid chunked requests (:pr:`3258`)
16+
- the SCRIPT_NAME and PATH_INFO headers, when received from allowed forwarders, are no longer restricted for containing an underscore (:pr:`3192`)
17+
- include IPv6 loopback address ``[::1]`` in default for :ref:`forwarded-allow-ips` and :ref:`proxy-allow-ips` (:pr:`3192`)
18+
19+
** NOTE **
20+
21+
- The SCRIPT_NAME change mitigates a regression that appeared first in the 22.0.0 release
22+
- Review your :ref:`forwarded-allow-ips` setting if you are still not seeing the SCRIPT_NAME transmitted
23+
- Review your :ref:`forwarder-headers` setting if you are missing headers after upgrading from a version prior to 22.0.0
1624

1725
** Breaking changes **
18-
* refuse requests where the uri field is empty (:pr:`3255`)
19-
* refuse requests with invalid CR/LR/NUL in heade field values (:pr:`3253`)
20-
* remove temporary `--tolerate-dangerous-framing` switch from 22.0 (:pr:`3260`)
21-
* If any of the breaking changes affect you, be aware that now refused requests can post a security problem, especially so in setups involving request pipe-lining and/or proxies.
26+
27+
- refuse requests where the uri field is empty (:pr:`3255`)
28+
- refuse requests with invalid CR/LR/NUL in heade field values (:pr:`3253`)
29+
- remove temporary ``--tolerate-dangerous-framing`` switch from 22.0 (:pr:`3260`)
30+
- If any of the breaking changes affect you, be aware that now refused requests can post a security problem, especially so in setups involving request pipe-lining and/or proxies.
2231

2332
22.0.0 - 2024-04-17
2433
===================

docs/source/settings.rst

+49-13
Original file line numberDiff line numberDiff line change
@@ -1208,7 +1208,7 @@ temporary directory.
12081208

12091209
A dictionary containing headers and values that the front-end proxy
12101210
uses to indicate HTTPS requests. If the source IP is permitted by
1211-
``forwarded-allow-ips`` (below), *and* at least one request header matches
1211+
:ref:`forwarded-allow-ips` (below), *and* at least one request header matches
12121212
a key-value pair listed in this dictionary, then Gunicorn will set
12131213
``wsgi.url_scheme`` to ``https``, so your application can tell that the
12141214
request is secure.
@@ -1232,17 +1232,23 @@ the headers defined here can not be passed directly from the client.
12321232

12331233
**Command line:** ``--forwarded-allow-ips STRING``
12341234

1235-
**Default:** ``'127.0.0.1'``
1235+
**Default:** ``'127.0.0.1,::1'``
12361236

12371237
Front-end's IPs from which allowed to handle set secure headers.
1238-
(comma separate).
1238+
(comma separated).
12391239

1240-
Set to ``*`` to disable checking of Front-end IPs (useful for setups
1241-
where you don't know in advance the IP address of Front-end, but
1242-
you still trust the environment).
1240+
Set to ``*`` to disable checking of front-end IPs. This is useful for setups
1241+
where you don't know in advance the IP address of front-end, but
1242+
instead have ensured via other means that only your
1243+
authorized front-ends can access Gunicorn.
12431244

12441245
By default, the value of the ``FORWARDED_ALLOW_IPS`` environment
1245-
variable. If it is not defined, the default is ``"127.0.0.1"``.
1246+
variable. If it is not defined, the default is ``"127.0.0.1,::1"``.
1247+
1248+
.. note::
1249+
1250+
This option does not affect UNIX socket connections. Connections not associated with
1251+
an IP address are treated as allowed, unconditionally.
12461252

12471253
.. note::
12481254

@@ -1369,13 +1375,19 @@ Example for stunnel config::
13691375

13701376
**Command line:** ``--proxy-allow-from``
13711377

1372-
**Default:** ``'127.0.0.1'``
1378+
**Default:** ``'127.0.0.1,::1'``
13731379

1374-
Front-end's IPs from which allowed accept proxy requests (comma separate).
1380+
Front-end's IPs from which allowed accept proxy requests (comma separated).
13751381

1376-
Set to ``*`` to disable checking of Front-end IPs (useful for setups
1377-
where you don't know in advance the IP address of Front-end, but
1378-
you still trust the environment)
1382+
Set to ``*`` to disable checking of front-end IPs. This is useful for setups
1383+
where you don't know in advance the IP address of front-end, but
1384+
instead have ensured via other means that only your
1385+
authorized front-ends can access Gunicorn.
1386+
1387+
.. note::
1388+
1389+
This option does not affect UNIX socket connections. Connections not associated with
1390+
an IP address are treated as allowed, unconditionally.
13791391

13801392
.. _raw-paste-global-conf:
13811393

@@ -1498,6 +1510,26 @@ Use with care and only if necessary. Deprecated; scheduled for removal in 24.0.0
14981510

14991511
.. versionadded:: 22.0.0
15001512

1513+
.. _forwarder-headers:
1514+
1515+
``forwarder_headers``
1516+
~~~~~~~~~~~~~~~~~~~~~
1517+
1518+
**Command line:** ``--forwarder-headers``
1519+
1520+
**Default:** ``'SCRIPT_NAME,PATH_INFO'``
1521+
1522+
A list containing upper-case header field names that the front-end proxy
1523+
(see :ref:`forwarded-allow-ips`) sets, to be used in WSGI environment.
1524+
1525+
This option has no effect for headers not present in the request.
1526+
1527+
This option can be used to transfer ``SCRIPT_NAME``, ``PATH_INFO``
1528+
and ``REMOTE_USER``.
1529+
1530+
It is important that your front-end proxy configuration ensures that
1531+
the headers defined here can not be passed directly from the client.
1532+
15011533
.. _header-map:
15021534

15031535
``header_map``
@@ -1515,9 +1547,13 @@ the same environment variable will dangerously confuse applications as to which
15151547

15161548
The safe default ``drop`` is to silently drop headers that cannot be unambiguously mapped.
15171549
The value ``refuse`` will return an error if a request contains *any* such header.
1518-
The value ``dangerous`` matches the previous, not advisabble, behaviour of mapping different
1550+
The value ``dangerous`` matches the previous, not advisable, behaviour of mapping different
15191551
header field names into the same environ name.
15201552

1553+
If the source is permitted as explained in :ref:`forwarded-allow-ips`, *and* the header name is
1554+
present in :ref:`forwarder-headers`, the header is mapped into environment regardless of
1555+
the state of this setting.
1556+
15211557
Use with care and only if necessary and after considering if your problem could
15221558
instead be solved by specifically renaming or rewriting only the intended headers
15231559
on a proxy in front of Gunicorn.

gunicorn/config.py

+64-15
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import copy
1010
import grp
1111
import inspect
12+
import ipaddress
1213
import os
1314
import pwd
1415
import re
@@ -402,6 +403,17 @@ def validate_list_of_existing_files(val):
402403
return [validate_file_exists(v) for v in validate_list_string(val)]
403404

404405

406+
def validate_string_to_addr_list(val):
407+
val = validate_string_to_list(val)
408+
409+
for addr in val:
410+
if addr == "*":
411+
continue
412+
_vaid_ip = ipaddress.ip_address(addr)
413+
414+
return val
415+
416+
405417
def validate_string_to_list(val):
406418
val = validate_string(val)
407419

@@ -1238,7 +1250,7 @@ class SecureSchemeHeader(Setting):
12381250
12391251
A dictionary containing headers and values that the front-end proxy
12401252
uses to indicate HTTPS requests. If the source IP is permitted by
1241-
``forwarded-allow-ips`` (below), *and* at least one request header matches
1253+
:ref:`forwarded-allow-ips` (below), *and* at least one request header matches
12421254
a key-value pair listed in this dictionary, then Gunicorn will set
12431255
``wsgi.url_scheme`` to ``https``, so your application can tell that the
12441256
request is secure.
@@ -1262,18 +1274,24 @@ class ForwardedAllowIPS(Setting):
12621274
section = "Server Mechanics"
12631275
cli = ["--forwarded-allow-ips"]
12641276
meta = "STRING"
1265-
validator = validate_string_to_list
1266-
default = os.environ.get("FORWARDED_ALLOW_IPS", "127.0.0.1")
1277+
validator = validate_string_to_addr_list
1278+
default = os.environ.get("FORWARDED_ALLOW_IPS", "127.0.0.1,::1")
12671279
desc = """\
12681280
Front-end's IPs from which allowed to handle set secure headers.
1269-
(comma separate).
1281+
(comma separated).
12701282
1271-
Set to ``*`` to disable checking of Front-end IPs (useful for setups
1272-
where you don't know in advance the IP address of Front-end, but
1273-
you still trust the environment).
1283+
Set to ``*`` to disable checking of front-end IPs. This is useful for setups
1284+
where you don't know in advance the IP address of front-end, but
1285+
instead have ensured via other means that only your
1286+
authorized front-ends can access Gunicorn.
12741287
12751288
By default, the value of the ``FORWARDED_ALLOW_IPS`` environment
1276-
variable. If it is not defined, the default is ``"127.0.0.1"``.
1289+
variable. If it is not defined, the default is ``"127.0.0.1,::1"``.
1290+
1291+
.. note::
1292+
1293+
This option does not affect UNIX socket connections. Connections not associated with
1294+
an IP address are treated as allowed, unconditionally.
12771295
12781296
.. note::
12791297
@@ -2062,14 +2080,20 @@ class ProxyAllowFrom(Setting):
20622080
name = "proxy_allow_ips"
20632081
section = "Server Mechanics"
20642082
cli = ["--proxy-allow-from"]
2065-
validator = validate_string_to_list
2066-
default = "127.0.0.1"
2083+
validator = validate_string_to_addr_list
2084+
default = "127.0.0.1,::1"
20672085
desc = """\
2068-
Front-end's IPs from which allowed accept proxy requests (comma separate).
2086+
Front-end's IPs from which allowed accept proxy requests (comma separated).
2087+
2088+
Set to ``*`` to disable checking of front-end IPs. This is useful for setups
2089+
where you don't know in advance the IP address of front-end, but
2090+
instead have ensured via other means that only your
2091+
authorized front-ends can access Gunicorn.
2092+
2093+
.. note::
20692094
2070-
Set to ``*`` to disable checking of Front-end IPs (useful for setups
2071-
where you don't know in advance the IP address of Front-end, but
2072-
you still trust the environment)
2095+
This option does not affect UNIX socket connections. Connections not associated with
2096+
an IP address are treated as allowed, unconditionally.
20732097
"""
20742098

20752099

@@ -2368,6 +2392,27 @@ def validate_header_map_behaviour(val):
23682392
raise ValueError("Invalid header map behaviour: %s" % val)
23692393

23702394

2395+
class ForwarderHeaders(Setting):
2396+
name = "forwarder_headers"
2397+
section = "Server Mechanics"
2398+
cli = ["--forwarder-headers"]
2399+
validator = validate_string_to_list
2400+
default = "SCRIPT_NAME,PATH_INFO"
2401+
desc = """\
2402+
2403+
A list containing upper-case header field names that the front-end proxy
2404+
(see :ref:`forwarded-allow-ips`) sets, to be used in WSGI environment.
2405+
2406+
This option has no effect for headers not present in the request.
2407+
2408+
This option can be used to transfer ``SCRIPT_NAME``, ``PATH_INFO``
2409+
and ``REMOTE_USER``.
2410+
2411+
It is important that your front-end proxy configuration ensures that
2412+
the headers defined here can not be passed directly from the client.
2413+
"""
2414+
2415+
23712416
class HeaderMap(Setting):
23722417
name = "header_map"
23732418
section = "Server Mechanics"
@@ -2383,9 +2428,13 @@ class HeaderMap(Setting):
23832428
23842429
The safe default ``drop`` is to silently drop headers that cannot be unambiguously mapped.
23852430
The value ``refuse`` will return an error if a request contains *any* such header.
2386-
The value ``dangerous`` matches the previous, not advisabble, behaviour of mapping different
2431+
The value ``dangerous`` matches the previous, not advisable, behaviour of mapping different
23872432
header field names into the same environ name.
23882433
2434+
If the source is permitted as explained in :ref:`forwarded-allow-ips`, *and* the header name is
2435+
present in :ref:`forwarder-headers`, the header is mapped into environment regardless of
2436+
the state of this setting.
2437+
23892438
Use with care and only if necessary and after considering if your problem could
23902439
instead be solved by specifically renaming or rewriting only the intended headers
23912440
on a proxy in front of Gunicorn.

gunicorn/http/message.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def parse_headers(self, data, from_trailer=False):
7878
# handle scheme headers
7979
scheme_header = False
8080
secure_scheme_headers = {}
81+
forwarder_headers = []
8182
if from_trailer:
8283
# nonsense. either a request is https from the beginning
8384
# .. or we are just behind a proxy who does not remove conflicting trailers
@@ -86,6 +87,7 @@ def parse_headers(self, data, from_trailer=False):
8687
not isinstance(self.peer_addr, tuple)
8788
or self.peer_addr[0] in cfg.forwarded_allow_ips):
8889
secure_scheme_headers = cfg.secure_scheme_headers
90+
forwarder_headers = cfg.forwarder_headers
8991

9092
# Parse headers into key/value pairs paying attention
9193
# to continuation lines.
@@ -147,7 +149,10 @@ def parse_headers(self, data, from_trailer=False):
147149
# HTTP_X_FORWARDED_FOR = 2001:db8::ha:cc:ed,127.0.0.1,::1
148150
# Only modify after fixing *ALL* header transformations; network to wsgi env
149151
if "_" in name:
150-
if self.cfg.header_map == "dangerous":
152+
if name in forwarder_headers or "*" in forwarder_headers:
153+
# This forwarder may override our environment
154+
pass
155+
elif self.cfg.header_map == "dangerous":
151156
# as if we did not know we cannot safely map this
152157
pass
153158
elif self.cfg.header_map == "drop":

0 commit comments

Comments
 (0)