123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- """
- X-Forwarded-For Proxy Fix
- =========================
- This module provides a middleware that adjusts the WSGI environ based on
- ``X-Forwarded-`` headers that proxies in front of an application may
- set.
- When an application is running behind a proxy server, WSGI may see the
- request as coming from that server rather than the real client. Proxies
- set various headers to track where the request actually came from.
- This middleware should only be applied if the application is actually
- behind such a proxy, and should be configured with the number of proxies
- that are chained in front of it. Not all proxies set all the headers.
- Since incoming headers can be faked, you must set how many proxies are
- setting each header so the middleware knows what to trust.
- .. autoclass:: ProxyFix
- :copyright: 2007 Pallets
- :license: BSD-3-Clause
- """
- import warnings
- class ProxyFix(object):
- """Adjust the WSGI environ based on ``X-Forwarded-`` that proxies in
- front of the application may set.
- - ``X-Forwarded-For`` sets ``REMOTE_ADDR``.
- - ``X-Forwarded-Proto`` sets ``wsgi.url_scheme``.
- - ``X-Forwarded-Host`` sets ``HTTP_HOST``, ``SERVER_NAME``, and
- ``SERVER_PORT``.
- - ``X-Forwarded-Port`` sets ``HTTP_HOST`` and ``SERVER_PORT``.
- - ``X-Forwarded-Prefix`` sets ``SCRIPT_NAME``.
- You must tell the middleware how many proxies set each header so it
- knows what values to trust. It is a security issue to trust values
- that came from the client rather than a proxy.
- The original values of the headers are stored in the WSGI
- environ as ``werkzeug.proxy_fix.orig``, a dict.
- :param app: The WSGI application to wrap.
- :param x_for: Number of values to trust for ``X-Forwarded-For``.
- :param x_proto: Number of values to trust for ``X-Forwarded-Proto``.
- :param x_host: Number of values to trust for ``X-Forwarded-Host``.
- :param x_port: Number of values to trust for ``X-Forwarded-Port``.
- :param x_prefix: Number of values to trust for
- ``X-Forwarded-Prefix``.
- :param num_proxies: Deprecated, use ``x_for`` instead.
- .. code-block:: python
- from werkzeug.middleware.proxy_fix import ProxyFix
- # App is behind one proxy that sets the -For and -Host headers.
- app = ProxyFix(app, x_for=1, x_host=1)
- .. versionchanged:: 0.15
- All headers support multiple values. The ``num_proxies``
- argument is deprecated. Each header is configured with a
- separate number of trusted proxies.
- .. versionchanged:: 0.15
- Original WSGI environ values are stored in the
- ``werkzeug.proxy_fix.orig`` dict. ``orig_remote_addr``,
- ``orig_wsgi_url_scheme``, and ``orig_http_host`` are deprecated
- and will be removed in 1.0.
- .. versionchanged:: 0.15
- Support ``X-Forwarded-Port`` and ``X-Forwarded-Prefix``.
- .. versionchanged:: 0.15
- ``X-Fowarded-Host`` and ``X-Forwarded-Port`` modify
- ``SERVER_NAME`` and ``SERVER_PORT``.
- """
- def __init__(
- self, app, num_proxies=None, x_for=1, x_proto=1, x_host=0, x_port=0, x_prefix=0
- ):
- self.app = app
- self.x_for = x_for
- self.x_proto = x_proto
- self.x_host = x_host
- self.x_port = x_port
- self.x_prefix = x_prefix
- self.num_proxies = num_proxies
- @property
- def num_proxies(self):
- """The number of proxies setting ``X-Forwarded-For`` in front
- of the application.
- .. deprecated:: 0.15
- A separate number of trusted proxies is configured for each
- header. ``num_proxies`` maps to ``x_for``. This method will
- be removed in 1.0.
- :internal:
- """
- warnings.warn(
- "'num_proxies' is deprecated as of version 0.15 and will be"
- " removed in version 1.0. Use 'x_for' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return self.x_for
- @num_proxies.setter
- def num_proxies(self, value):
- if value is not None:
- warnings.warn(
- "'num_proxies' is deprecated as of version 0.15 and"
- " will be removed in version 1.0. Use"
- " 'x_for={value}, x_proto={value}, x_host={value}'"
- " instead.".format(value=value),
- DeprecationWarning,
- stacklevel=2,
- )
- self.x_for = value
- self.x_proto = value
- self.x_host = value
- def get_remote_addr(self, forwarded_for):
- """Get the real ``remote_addr`` by looking backwards ``x_for``
- number of values in the ``X-Forwarded-For`` header.
- :param forwarded_for: List of values parsed from the
- ``X-Forwarded-For`` header.
- :return: The real ``remote_addr``, or ``None`` if there were not
- at least ``x_for`` values.
- .. deprecated:: 0.15
- This is handled internally for each header. This method will
- be removed in 1.0.
- .. versionchanged:: 0.9
- Use ``num_proxies`` instead of always picking the first
- value.
- .. versionadded:: 0.8
- """
- warnings.warn(
- "'get_remote_addr' is deprecated as of version 0.15 and"
- " will be removed in version 1.0. It is now handled"
- " internally for each header.",
- DeprecationWarning,
- )
- return self._get_trusted_comma(self.x_for, ",".join(forwarded_for))
- def _get_trusted_comma(self, trusted, value):
- """Get the real value from a comma-separated header based on the
- configured number of trusted proxies.
- :param trusted: Number of values to trust in the header.
- :param value: Header value to parse.
- :return: The real value, or ``None`` if there are fewer values
- than the number of trusted proxies.
- .. versionadded:: 0.15
- """
- if not (trusted and value):
- return
- values = [x.strip() for x in value.split(",")]
- if len(values) >= trusted:
- return values[-trusted]
- def __call__(self, environ, start_response):
- """Modify the WSGI environ based on the various ``Forwarded``
- headers before calling the wrapped application. Store the
- original environ values in ``werkzeug.proxy_fix.orig_{key}``.
- """
- environ_get = environ.get
- orig_remote_addr = environ_get("REMOTE_ADDR")
- orig_wsgi_url_scheme = environ_get("wsgi.url_scheme")
- orig_http_host = environ_get("HTTP_HOST")
- environ.update(
- {
- "werkzeug.proxy_fix.orig": {
- "REMOTE_ADDR": orig_remote_addr,
- "wsgi.url_scheme": orig_wsgi_url_scheme,
- "HTTP_HOST": orig_http_host,
- "SERVER_NAME": environ_get("SERVER_NAME"),
- "SERVER_PORT": environ_get("SERVER_PORT"),
- "SCRIPT_NAME": environ_get("SCRIPT_NAME"),
- },
- # todo: remove deprecated keys
- "werkzeug.proxy_fix.orig_remote_addr": orig_remote_addr,
- "werkzeug.proxy_fix.orig_wsgi_url_scheme": orig_wsgi_url_scheme,
- "werkzeug.proxy_fix.orig_http_host": orig_http_host,
- }
- )
- x_for = self._get_trusted_comma(self.x_for, environ_get("HTTP_X_FORWARDED_FOR"))
- if x_for:
- environ["REMOTE_ADDR"] = x_for
- x_proto = self._get_trusted_comma(
- self.x_proto, environ_get("HTTP_X_FORWARDED_PROTO")
- )
- if x_proto:
- environ["wsgi.url_scheme"] = x_proto
- x_host = self._get_trusted_comma(
- self.x_host, environ_get("HTTP_X_FORWARDED_HOST")
- )
- if x_host:
- environ["HTTP_HOST"] = x_host
- parts = x_host.split(":", 1)
- environ["SERVER_NAME"] = parts[0]
- if len(parts) == 2:
- environ["SERVER_PORT"] = parts[1]
- x_port = self._get_trusted_comma(
- self.x_port, environ_get("HTTP_X_FORWARDED_PORT")
- )
- if x_port:
- host = environ.get("HTTP_HOST")
- if host:
- parts = host.split(":", 1)
- host = parts[0] if len(parts) == 2 else host
- environ["HTTP_HOST"] = "%s:%s" % (host, x_port)
- environ["SERVER_PORT"] = x_port
- x_prefix = self._get_trusted_comma(
- self.x_prefix, environ_get("HTTP_X_FORWARDED_PREFIX")
- )
- if x_prefix:
- environ["SCRIPT_NAME"] = x_prefix
- return self.app(environ, start_response)
|