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

Commit 339c391

Browse files
authored
support federation queries through http connect proxy (#10475)
Signed-off-by: Marcus Hoffmann <[email protected]> Signed-off-by: Dirk Klimpel [email protected]
1 parent 8c654b7 commit 339c391

File tree

9 files changed

+555
-191
lines changed

9 files changed

+555
-191
lines changed

changelog.d/10475.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for sending federation requests through a proxy. Contributed by @Bubu and @dklimpel.

docs/setup/forward_proxy.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,18 @@ The proxy will be **used** for:
4545
- recaptcha validation
4646
- CAS auth validation
4747
- OpenID Connect
48+
- Outbound federation
4849
- Federation (checking public key revocation)
50+
- Fetching public keys of other servers
51+
- Downloading remote media
4952

5053
It will **not be used** for:
5154

5255
- Application Services
5356
- Identity servers
54-
- Outbound federation
5557
- In worker configurations
5658
- connections between workers
5759
- connections from workers to Redis
58-
- Fetching public keys of other servers
59-
- Downloading remote media
6060

6161
## Troubleshooting
6262

docs/upgrade.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,33 @@ process, for example:
8686
```
8787

8888

89+
# Upgrading to v1.xx.0
90+
91+
## Add support for routing outbound HTTP requests via a proxy for federation
92+
93+
Since Synapse 1.6.0 (2019-11-26) you can set a proxy for outbound HTTP requests via
94+
http_proxy/https_proxy environment variables. This proxy was set for:
95+
- push
96+
- url previews
97+
- phone-home stats
98+
- recaptcha validation
99+
- CAS auth validation
100+
- OpenID Connect
101+
- Federation (checking public key revocation)
102+
103+
In this version we have added support for outbound requests for:
104+
- Outbound federation
105+
- Downloading remote media
106+
- Fetching public keys of other servers
107+
108+
These requests use the same proxy configuration. If you have a proxy configuration we
109+
recommend to verify the configuration. It may be necessary to adjust the `no_proxy`
110+
environment variable.
111+
112+
See [using a forward proxy with Synapse documentation](setup/forward_proxy.md) for
113+
details.
114+
115+
89116
# Upgrading to v1.39.0
90117

91118
## Deprecation of the current third-party rules module interface

synapse/http/connectproxyclient.py

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,18 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import base64
1516
import logging
17+
from typing import Optional
1618

19+
import attr
1720
from zope.interface import implementer
1821

1922
from twisted.internet import defer, protocol
2023
from twisted.internet.error import ConnectError
2124
from twisted.internet.interfaces import IReactorCore, IStreamClientEndpoint
2225
from twisted.internet.protocol import ClientFactory, Protocol, connectionDone
2326
from twisted.web import http
24-
from twisted.web.http_headers import Headers
2527

2628
logger = logging.getLogger(__name__)
2729

@@ -30,6 +32,22 @@ class ProxyConnectError(ConnectError):
3032
pass
3133

3234

35+
@attr.s
36+
class ProxyCredentials:
37+
username_password = attr.ib(type=bytes)
38+
39+
def as_proxy_authorization_value(self) -> bytes:
40+
"""
41+
Return the value for a Proxy-Authorization header (i.e. 'Basic abdef==').
42+
43+
Returns:
44+
A transformation of the authentication string the encoded value for
45+
a Proxy-Authorization header.
46+
"""
47+
# Encode as base64 and prepend the authorization type
48+
return b"Basic " + base64.encodebytes(self.username_password)
49+
50+
3351
@implementer(IStreamClientEndpoint)
3452
class HTTPConnectProxyEndpoint:
3553
"""An Endpoint implementation which will send a CONNECT request to an http proxy
@@ -46,7 +64,7 @@ class HTTPConnectProxyEndpoint:
4664
proxy_endpoint: the endpoint to use to connect to the proxy
4765
host: hostname that we want to CONNECT to
4866
port: port that we want to connect to
49-
headers: Extra HTTP headers to include in the CONNECT request
67+
proxy_creds: credentials to authenticate at proxy
5068
"""
5169

5270
def __init__(
@@ -55,20 +73,20 @@ def __init__(
5573
proxy_endpoint: IStreamClientEndpoint,
5674
host: bytes,
5775
port: int,
58-
headers: Headers,
76+
proxy_creds: Optional[ProxyCredentials],
5977
):
6078
self._reactor = reactor
6179
self._proxy_endpoint = proxy_endpoint
6280
self._host = host
6381
self._port = port
64-
self._headers = headers
82+
self._proxy_creds = proxy_creds
6583

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

6987
def connect(self, protocolFactory: ClientFactory):
7088
f = HTTPProxiedClientFactory(
71-
self._host, self._port, protocolFactory, self._headers
89+
self._host, self._port, protocolFactory, self._proxy_creds
7290
)
7391
d = self._proxy_endpoint.connect(f)
7492
# once the tcp socket connects successfully, we need to wait for the
@@ -87,20 +105,20 @@ class HTTPProxiedClientFactory(protocol.ClientFactory):
87105
dst_host: hostname that we want to CONNECT to
88106
dst_port: port that we want to connect to
89107
wrapped_factory: The original Factory
90-
headers: Extra HTTP headers to include in the CONNECT request
108+
proxy_creds: credentials to authenticate at proxy
91109
"""
92110

93111
def __init__(
94112
self,
95113
dst_host: bytes,
96114
dst_port: int,
97115
wrapped_factory: ClientFactory,
98-
headers: Headers,
116+
proxy_creds: Optional[ProxyCredentials],
99117
):
100118
self.dst_host = dst_host
101119
self.dst_port = dst_port
102120
self.wrapped_factory = wrapped_factory
103-
self.headers = headers
121+
self.proxy_creds = proxy_creds
104122
self.on_connection = defer.Deferred()
105123

106124
def startedConnecting(self, connector):
@@ -114,7 +132,7 @@ def buildProtocol(self, addr):
114132
self.dst_port,
115133
wrapped_protocol,
116134
self.on_connection,
117-
self.headers,
135+
self.proxy_creds,
118136
)
119137

120138
def clientConnectionFailed(self, connector, reason):
@@ -145,7 +163,7 @@ class HTTPConnectProtocol(protocol.Protocol):
145163
connected_deferred: a Deferred which will be callbacked with
146164
wrapped_protocol when the CONNECT completes
147165
148-
headers: Extra HTTP headers to include in the CONNECT request
166+
proxy_creds: credentials to authenticate at proxy
149167
"""
150168

151169
def __init__(
@@ -154,16 +172,16 @@ def __init__(
154172
port: int,
155173
wrapped_protocol: Protocol,
156174
connected_deferred: defer.Deferred,
157-
headers: Headers,
175+
proxy_creds: Optional[ProxyCredentials],
158176
):
159177
self.host = host
160178
self.port = port
161179
self.wrapped_protocol = wrapped_protocol
162180
self.connected_deferred = connected_deferred
163-
self.headers = headers
181+
self.proxy_creds = proxy_creds
164182

165183
self.http_setup_client = HTTPConnectSetupClient(
166-
self.host, self.port, self.headers
184+
self.host, self.port, self.proxy_creds
167185
)
168186
self.http_setup_client.on_connected.addCallback(self.proxyConnected)
169187

@@ -205,30 +223,38 @@ class HTTPConnectSetupClient(http.HTTPClient):
205223
Args:
206224
host: The hostname to send in the CONNECT message
207225
port: The port to send in the CONNECT message
208-
headers: Extra headers to send with the CONNECT message
226+
proxy_creds: credentials to authenticate at proxy
209227
"""
210228

211-
def __init__(self, host: bytes, port: int, headers: Headers):
229+
def __init__(
230+
self,
231+
host: bytes,
232+
port: int,
233+
proxy_creds: Optional[ProxyCredentials],
234+
):
212235
self.host = host
213236
self.port = port
214-
self.headers = headers
237+
self.proxy_creds = proxy_creds
215238
self.on_connected = defer.Deferred()
216239

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

221-
# Send any additional specified headers
222-
for name, values in self.headers.getAllRawHeaders():
223-
for value in values:
224-
self.sendHeader(name, value)
244+
# Determine whether we need to set Proxy-Authorization headers
245+
if self.proxy_creds:
246+
# Set a Proxy-Authorization header
247+
self.sendHeader(
248+
b"Proxy-Authorization",
249+
self.proxy_creds.as_proxy_authorization_value(),
250+
)
225251

226252
self.endHeaders()
227253

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

233259
def handleEndHeaders(self):
234260
logger.debug("End Headers")

0 commit comments

Comments
 (0)