forked from MirrorHub/synapse
Add support for X-Forwarded-Proto (#9472)
rewrite XForwardedForRequest to set `isSecure()` based on `X-Forwarded-Proto`. Also implement `getClientAddress()` while we're here.
This commit is contained in:
parent
00bf80cb8e
commit
d8e95e5452
3 changed files with 94 additions and 28 deletions
1
changelog.d/9472.feature
Normal file
1
changelog.d/9472.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Add support for `X-Forwarded-Proto` header when using a reverse proxy. Administrators using a reverse proxy should ensure this header is set to avoid warnings. See [docs/workers.md](docs/workers.md) for example configurations.
|
|
@ -9,23 +9,23 @@ of doing so is that it means that you can expose the default https port
|
||||||
(443) to Matrix clients without needing to run Synapse with root
|
(443) to Matrix clients without needing to run Synapse with root
|
||||||
privileges.
|
privileges.
|
||||||
|
|
||||||
**NOTE**: Your reverse proxy must not `canonicalise` or `normalise`
|
You should configure your reverse proxy to forward requests to `/_matrix` or
|
||||||
the requested URI in any way (for example, by decoding `%xx` escapes).
|
`/_synapse/client` to Synapse, and have it set the `X-Forwarded-For` and
|
||||||
Beware that Apache *will* canonicalise URIs unless you specify
|
`X-Forwarded-Proto` request headers.
|
||||||
`nocanon`.
|
|
||||||
|
|
||||||
When setting up a reverse proxy, remember that Matrix clients and other
|
You should remember that Matrix clients and other Matrix servers do not
|
||||||
Matrix servers do not necessarily need to connect to your server via the
|
necessarily need to connect to your server via the same server name or
|
||||||
same server name or port. Indeed, clients will use port 443 by default,
|
port. Indeed, clients will use port 443 by default, whereas servers default to
|
||||||
whereas servers default to port 8448. Where these are different, we
|
port 8448. Where these are different, we refer to the 'client port' and the
|
||||||
refer to the 'client port' and the 'federation port'. See [the Matrix
|
'federation port'. See [the Matrix
|
||||||
specification](https://matrix.org/docs/spec/server_server/latest#resolving-server-names)
|
specification](https://matrix.org/docs/spec/server_server/latest#resolving-server-names)
|
||||||
for more details of the algorithm used for federation connections, and
|
for more details of the algorithm used for federation connections, and
|
||||||
[delegate.md](<delegate.md>) for instructions on setting up delegation.
|
[delegate.md](<delegate.md>) for instructions on setting up delegation.
|
||||||
|
|
||||||
Endpoints that are part of the standardised Matrix specification are
|
**NOTE**: Your reverse proxy must not `canonicalise` or `normalise`
|
||||||
located under `/_matrix`, whereas endpoints specific to Synapse are
|
the requested URI in any way (for example, by decoding `%xx` escapes).
|
||||||
located under `/_synapse/client`.
|
Beware that Apache *will* canonicalise URIs unless you specify
|
||||||
|
`nocanon`.
|
||||||
|
|
||||||
Let's assume that we expect clients to connect to our server at
|
Let's assume that we expect clients to connect to our server at
|
||||||
`https://matrix.example.com`, and other servers to connect at
|
`https://matrix.example.com`, and other servers to connect at
|
||||||
|
@ -52,6 +52,7 @@ server {
|
||||||
location ~* ^(\/_matrix|\/_synapse\/client) {
|
location ~* ^(\/_matrix|\/_synapse\/client) {
|
||||||
proxy_pass http://localhost:8008;
|
proxy_pass http://localhost:8008;
|
||||||
proxy_set_header X-Forwarded-For $remote_addr;
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
# Nginx by default only allows file uploads up to 1M in size
|
# Nginx by default only allows file uploads up to 1M in size
|
||||||
# Increase client_max_body_size to match max_upload_size defined in homeserver.yaml
|
# Increase client_max_body_size to match max_upload_size defined in homeserver.yaml
|
||||||
client_max_body_size 50M;
|
client_max_body_size 50M;
|
||||||
|
@ -102,6 +103,7 @@ example.com:8448 {
|
||||||
SSLEngine on
|
SSLEngine on
|
||||||
ServerName matrix.example.com;
|
ServerName matrix.example.com;
|
||||||
|
|
||||||
|
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
|
||||||
AllowEncodedSlashes NoDecode
|
AllowEncodedSlashes NoDecode
|
||||||
ProxyPass /_matrix http://127.0.0.1:8008/_matrix nocanon
|
ProxyPass /_matrix http://127.0.0.1:8008/_matrix nocanon
|
||||||
ProxyPassReverse /_matrix http://127.0.0.1:8008/_matrix
|
ProxyPassReverse /_matrix http://127.0.0.1:8008/_matrix
|
||||||
|
@ -113,6 +115,7 @@ example.com:8448 {
|
||||||
SSLEngine on
|
SSLEngine on
|
||||||
ServerName example.com;
|
ServerName example.com;
|
||||||
|
|
||||||
|
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
|
||||||
AllowEncodedSlashes NoDecode
|
AllowEncodedSlashes NoDecode
|
||||||
ProxyPass /_matrix http://127.0.0.1:8008/_matrix nocanon
|
ProxyPass /_matrix http://127.0.0.1:8008/_matrix nocanon
|
||||||
ProxyPassReverse /_matrix http://127.0.0.1:8008/_matrix
|
ProxyPassReverse /_matrix http://127.0.0.1:8008/_matrix
|
||||||
|
@ -134,6 +137,9 @@ example.com:8448 {
|
||||||
```
|
```
|
||||||
frontend https
|
frontend https
|
||||||
bind :::443 v4v6 ssl crt /etc/ssl/haproxy/ strict-sni alpn h2,http/1.1
|
bind :::443 v4v6 ssl crt /etc/ssl/haproxy/ strict-sni alpn h2,http/1.1
|
||||||
|
http-request set-header X-Forwarded-Proto https if { ssl_fc }
|
||||||
|
http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
|
||||||
|
http-request set-header X-Forwarded-For %[src]
|
||||||
|
|
||||||
# Matrix client traffic
|
# Matrix client traffic
|
||||||
acl matrix-host hdr(host) -i matrix.example.com
|
acl matrix-host hdr(host) -i matrix.example.com
|
||||||
|
@ -144,6 +150,10 @@ frontend https
|
||||||
|
|
||||||
frontend matrix-federation
|
frontend matrix-federation
|
||||||
bind :::8448 v4v6 ssl crt /etc/ssl/haproxy/synapse.pem alpn h2,http/1.1
|
bind :::8448 v4v6 ssl crt /etc/ssl/haproxy/synapse.pem alpn h2,http/1.1
|
||||||
|
http-request set-header X-Forwarded-Proto https if { ssl_fc }
|
||||||
|
http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
|
||||||
|
http-request set-header X-Forwarded-For %[src]
|
||||||
|
|
||||||
default_backend matrix
|
default_backend matrix
|
||||||
|
|
||||||
backend matrix
|
backend matrix
|
||||||
|
|
|
@ -16,6 +16,10 @@ import logging
|
||||||
import time
|
import time
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
|
import attr
|
||||||
|
from zope.interface import implementer
|
||||||
|
|
||||||
|
from twisted.internet.interfaces import IAddress
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
from twisted.web.server import Request, Site
|
from twisted.web.server import Request, Site
|
||||||
|
|
||||||
|
@ -333,27 +337,78 @@ class SynapseRequest(Request):
|
||||||
|
|
||||||
|
|
||||||
class XForwardedForRequest(SynapseRequest):
|
class XForwardedForRequest(SynapseRequest):
|
||||||
def __init__(self, *args, **kw):
|
"""Request object which honours proxy headers
|
||||||
SynapseRequest.__init__(self, *args, **kw)
|
|
||||||
|
|
||||||
"""
|
Extends SynapseRequest to replace getClientIP, getClientAddress, and isSecure with
|
||||||
Add a layer on top of another request that only uses the value of an
|
information from request headers.
|
||||||
X-Forwarded-For header as the result of C{getClientIP}.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def getClientIP(self):
|
# the client IP and ssl flag, as extracted from the headers.
|
||||||
"""
|
_forwarded_for = None # type: Optional[_XForwardedForAddress]
|
||||||
@return: The client address (the first address) in the value of the
|
_forwarded_https = False # type: bool
|
||||||
I{X-Forwarded-For header}. If the header is not present, return
|
|
||||||
C{b"-"}.
|
def requestReceived(self, command, path, version):
|
||||||
"""
|
# this method is called by the Channel once the full request has been
|
||||||
return (
|
# received, to dispatch the request to a resource.
|
||||||
self.requestHeaders.getRawHeaders(b"x-forwarded-for", [b"-"])[0]
|
# We can use it to set the IP address and protocol according to the
|
||||||
.split(b",")[0]
|
# headers.
|
||||||
.strip()
|
self._process_forwarded_headers()
|
||||||
.decode("ascii")
|
return super().requestReceived(command, path, version)
|
||||||
|
|
||||||
|
def _process_forwarded_headers(self):
|
||||||
|
headers = self.requestHeaders.getRawHeaders(b"x-forwarded-for")
|
||||||
|
if not headers:
|
||||||
|
return
|
||||||
|
|
||||||
|
# for now, we just use the first x-forwarded-for header. Really, we ought
|
||||||
|
# to start from the client IP address, and check whether it is trusted; if it
|
||||||
|
# is, work backwards through the headers until we find an untrusted address.
|
||||||
|
# see https://github.com/matrix-org/synapse/issues/9471
|
||||||
|
self._forwarded_for = _XForwardedForAddress(
|
||||||
|
headers[0].split(b",")[0].strip().decode("ascii")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# if we got an x-forwarded-for header, also look for an x-forwarded-proto header
|
||||||
|
header = self.getHeader(b"x-forwarded-proto")
|
||||||
|
if header is not None:
|
||||||
|
self._forwarded_https = header.lower() == b"https"
|
||||||
|
else:
|
||||||
|
# this is done largely for backwards-compatibility so that people that
|
||||||
|
# haven't set an x-forwarded-proto header don't get a redirect loop.
|
||||||
|
logger.warning(
|
||||||
|
"forwarded request lacks an x-forwarded-proto header: assuming https"
|
||||||
|
)
|
||||||
|
self._forwarded_https = True
|
||||||
|
|
||||||
|
def isSecure(self):
|
||||||
|
if self._forwarded_https:
|
||||||
|
return True
|
||||||
|
return super().isSecure()
|
||||||
|
|
||||||
|
def getClientIP(self) -> str:
|
||||||
|
"""
|
||||||
|
Return the IP address of the client who submitted this request.
|
||||||
|
|
||||||
|
This method is deprecated. Use getClientAddress() instead.
|
||||||
|
"""
|
||||||
|
if self._forwarded_for is not None:
|
||||||
|
return self._forwarded_for.host
|
||||||
|
return super().getClientIP()
|
||||||
|
|
||||||
|
def getClientAddress(self) -> IAddress:
|
||||||
|
"""
|
||||||
|
Return the address of the client who submitted this request.
|
||||||
|
"""
|
||||||
|
if self._forwarded_for is not None:
|
||||||
|
return self._forwarded_for
|
||||||
|
return super().getClientAddress()
|
||||||
|
|
||||||
|
|
||||||
|
@implementer(IAddress)
|
||||||
|
@attr.s(frozen=True, slots=True)
|
||||||
|
class _XForwardedForAddress:
|
||||||
|
host = attr.ib(type=str)
|
||||||
|
|
||||||
|
|
||||||
class SynapseSite(Site):
|
class SynapseSite(Site):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in a new issue