Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

support federation queries through http connect proxy #10475

Merged
merged 32 commits into from
Aug 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
85081fa
support federation queries through http connect proxy
Bubu Jan 11, 2021
7115567
HTTPConnectProxyEndpoint now want a headers parameter
Bubu Apr 26, 2021
ef8792a
make mypy happy
Bubu Apr 27, 2021
781da36
also make black happy :)
Bubu Apr 27, 2021
196af98
Merge remote-tracking branch 'synapse/develop' into federation_throug…
dklimpel Jul 24, 2021
86b3623
add type hints and comments to tests
dklimpel Jul 24, 2021
3c7c3b6
change logic of test to the same like in `test_proxyagent`
dklimpel Jul 24, 2021
cee7f65
Add http connections to `_make_connection` for http proxies
dklimpel Jul 24, 2021
e8cb087
add tests for proxy
dklimpel Jul 24, 2021
73c5ff5
add test for bypass proxy
dklimpel Jul 24, 2021
4cd4a48
put creation of `MatrixFederationAgent` in own function
dklimpel Jul 24, 2021
1595da4
comments and type hints to federation_agent
dklimpel Jul 24, 2021
5acd7ce
move proxy handling to `MatrixHostnameEndpoint` similar to `ProxyAgent`
dklimpel Jul 24, 2021
3342bda
newsfile
dklimpel Jul 24, 2021
0bf5ac8
fix imports
dklimpel Jul 24, 2021
b81eaaf
add upgrade notes
dklimpel Jul 26, 2021
08b19a6
Merge remote-tracking branch 'synapse/develop' into
dklimpel Jul 27, 2021
70a8b11
fix merge error and update tests
dklimpel Jul 27, 2021
b68a14d
fix lint
dklimpel Jul 27, 2021
b766a0f
Merge branch 'matrix-org:develop' into federation_through_proxy
dklimpel Aug 5, 2021
eab6c74
update docs
dklimpel Aug 5, 2021
73a7380
Apply suggestions from code review
dklimpel Aug 7, 2021
007ea8a
add to internal attributes underscore prefixes
dklimpel Aug 7, 2021
258fab8
rename param `ssl` to `expect_proxy_ssl`
dklimpel Aug 7, 2021
08823be
remove `_make_agent()` from `setUp` in tests
dklimpel Aug 7, 2021
67522c3
rename in test `auth_credentials` to `expected_auth_credentials`
dklimpel Aug 7, 2021
512166b
remove default TLS policy
dklimpel Aug 7, 2021
ab2067d
bring `ProxyCredentials` to `HTTPConnectProxyEndpoint`
dklimpel Aug 7, 2021
e8fd305
move build `BlacklistingReactorWrapper` into `MatrixFederationAgent`
dklimpel Aug 7, 2021
d0933e5
lint
dklimpel Aug 7, 2021
c953242
remove not needed reactor
dklimpel Aug 9, 2021
2720c88
Merge branch 'matrix-org:develop' into federation_through_proxy
dklimpel Aug 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/10475.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for sending federation requests through a proxy. Contributed by @Bubu and @dklimpel.
6 changes: 3 additions & 3 deletions docs/setup/forward_proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,18 @@ The proxy will be **used** for:
- recaptcha validation
- CAS auth validation
- OpenID Connect
- Outbound federation
- Federation (checking public key revocation)
- Fetching public keys of other servers
- Downloading remote media

It will **not be used** for:

- Application Services
- Identity servers
- Outbound federation
- In worker configurations
- connections between workers
- connections from workers to Redis
- Fetching public keys of other servers
- Downloading remote media

## Troubleshooting

Expand Down
27 changes: 27 additions & 0 deletions docs/upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,33 @@ process, for example:
```


# Upgrading to v1.xx.0

## Add support for routing outbound HTTP requests via a proxy for federation

Since Synapse 1.6.0 (2019-11-26) you can set a proxy for outbound HTTP requests via
http_proxy/https_proxy environment variables. This proxy was set for:
- push
- url previews
- phone-home stats
- recaptcha validation
- CAS auth validation
- OpenID Connect
- Federation (checking public key revocation)

In this version we have added support for outbound requests for:
- Outbound federation
- Downloading remote media
- Fetching public keys of other servers

These requests use the same proxy configuration. If you have a proxy configuration we
recommend to verify the configuration. It may be necessary to adjust the `no_proxy`
environment variable.

See [using a forward proxy with Synapse documentation](setup/forward_proxy.md) for
details.


# Upgrading to v1.39.0

## Deprecation of the current third-party rules module interface
Expand Down
68 changes: 47 additions & 21 deletions synapse/http/connectproxyclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import base64
import logging
from typing import Optional

import attr
from zope.interface import implementer

from twisted.internet import defer, protocol
from twisted.internet.error import ConnectError
from twisted.internet.interfaces import IReactorCore, IStreamClientEndpoint
from twisted.internet.protocol import ClientFactory, Protocol, connectionDone
from twisted.web import http
from twisted.web.http_headers import Headers

logger = logging.getLogger(__name__)

Expand All @@ -30,6 +32,22 @@ class ProxyConnectError(ConnectError):
pass


@attr.s
class ProxyCredentials:
username_password = attr.ib(type=bytes)

def as_proxy_authorization_value(self) -> bytes:
"""
Return the value for a Proxy-Authorization header (i.e. 'Basic abdef==').

Returns:
A transformation of the authentication string the encoded value for
a Proxy-Authorization header.
"""
# Encode as base64 and prepend the authorization type
return b"Basic " + base64.encodebytes(self.username_password)


@implementer(IStreamClientEndpoint)
class HTTPConnectProxyEndpoint:
"""An Endpoint implementation which will send a CONNECT request to an http proxy
Expand All @@ -46,7 +64,7 @@ class HTTPConnectProxyEndpoint:
proxy_endpoint: the endpoint to use to connect to the proxy
host: hostname that we want to CONNECT to
port: port that we want to connect to
headers: Extra HTTP headers to include in the CONNECT request
proxy_creds: credentials to authenticate at proxy
"""

def __init__(
Expand All @@ -55,20 +73,20 @@ def __init__(
proxy_endpoint: IStreamClientEndpoint,
host: bytes,
port: int,
headers: Headers,
proxy_creds: Optional[ProxyCredentials],
):
self._reactor = reactor
self._proxy_endpoint = proxy_endpoint
self._host = host
self._port = port
self._headers = headers
self._proxy_creds = proxy_creds

def __repr__(self):
return "<HTTPConnectProxyEndpoint %s>" % (self._proxy_endpoint,)

def connect(self, protocolFactory: ClientFactory):
f = HTTPProxiedClientFactory(
self._host, self._port, protocolFactory, self._headers
self._host, self._port, protocolFactory, self._proxy_creds
)
d = self._proxy_endpoint.connect(f)
# once the tcp socket connects successfully, we need to wait for the
Expand All @@ -87,20 +105,20 @@ class HTTPProxiedClientFactory(protocol.ClientFactory):
dst_host: hostname that we want to CONNECT to
dst_port: port that we want to connect to
wrapped_factory: The original Factory
headers: Extra HTTP headers to include in the CONNECT request
proxy_creds: credentials to authenticate at proxy
"""

def __init__(
self,
dst_host: bytes,
dst_port: int,
wrapped_factory: ClientFactory,
headers: Headers,
proxy_creds: Optional[ProxyCredentials],
):
self.dst_host = dst_host
self.dst_port = dst_port
self.wrapped_factory = wrapped_factory
self.headers = headers
self.proxy_creds = proxy_creds
self.on_connection = defer.Deferred()

def startedConnecting(self, connector):
Expand All @@ -114,7 +132,7 @@ def buildProtocol(self, addr):
self.dst_port,
wrapped_protocol,
self.on_connection,
self.headers,
self.proxy_creds,
)

def clientConnectionFailed(self, connector, reason):
Expand Down Expand Up @@ -145,7 +163,7 @@ class HTTPConnectProtocol(protocol.Protocol):
connected_deferred: a Deferred which will be callbacked with
wrapped_protocol when the CONNECT completes

headers: Extra HTTP headers to include in the CONNECT request
proxy_creds: credentials to authenticate at proxy
"""

def __init__(
Expand All @@ -154,16 +172,16 @@ def __init__(
port: int,
wrapped_protocol: Protocol,
connected_deferred: defer.Deferred,
headers: Headers,
proxy_creds: Optional[ProxyCredentials],
):
self.host = host
self.port = port
self.wrapped_protocol = wrapped_protocol
self.connected_deferred = connected_deferred
self.headers = headers
self.proxy_creds = proxy_creds

self.http_setup_client = HTTPConnectSetupClient(
self.host, self.port, self.headers
self.host, self.port, self.proxy_creds
)
self.http_setup_client.on_connected.addCallback(self.proxyConnected)

Expand Down Expand Up @@ -205,30 +223,38 @@ class HTTPConnectSetupClient(http.HTTPClient):
Args:
host: The hostname to send in the CONNECT message
port: The port to send in the CONNECT message
headers: Extra headers to send with the CONNECT message
proxy_creds: credentials to authenticate at proxy
"""

def __init__(self, host: bytes, port: int, headers: Headers):
def __init__(
self,
host: bytes,
port: int,
proxy_creds: Optional[ProxyCredentials],
):
self.host = host
self.port = port
self.headers = headers
self.proxy_creds = proxy_creds
self.on_connected = defer.Deferred()

def connectionMade(self):
logger.debug("Connected to proxy, sending CONNECT")
self.sendCommand(b"CONNECT", b"%s:%d" % (self.host, self.port))

# Send any additional specified headers
for name, values in self.headers.getAllRawHeaders():
for value in values:
self.sendHeader(name, value)
# Determine whether we need to set Proxy-Authorization headers
if self.proxy_creds:
# Set a Proxy-Authorization header
self.sendHeader(
b"Proxy-Authorization",
self.proxy_creds.as_proxy_authorization_value(),
)

self.endHeaders()

def handleStatus(self, version: bytes, status: bytes, message: bytes):
logger.debug("Got Status: %s %s %s", status, message, version)
if status != b"200":
raise ProxyConnectError("Unexpected status on CONNECT: %s" % status)
raise ProxyConnectError(f"Unexpected status on CONNECT: {status!s}")

def handleEndHeaders(self):
logger.debug("End Headers")
Expand Down
Loading