Support for serving server well-known files (#11211)

Fixes https://github.com/matrix-org/synapse/issues/8308
This commit is contained in:
Richard van der Hoff 2021-11-01 15:10:16 +00:00 committed by GitHub
parent 2014098d01
commit 71f9966f27
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 159 additions and 47 deletions

View file

@ -0,0 +1 @@
Add support for serving `/.well-known/matrix/server` files, to redirect federation traffic to port 443.

View file

@ -1,4 +1,8 @@
# Delegation # Delegation of incoming federation traffic
In the following documentation, we use the term `server_name` to refer to that setting
in your homeserver configuration file. It appears at the ends of user ids, and tells
other homeservers where they can find your server.
By default, other homeservers will expect to be able to reach yours via By default, other homeservers will expect to be able to reach yours via
your `server_name`, on port 8448. For example, if you set your `server_name` your `server_name`, on port 8448. For example, if you set your `server_name`
@ -12,13 +16,21 @@ to a different server and/or port (e.g. `synapse.example.com:443`).
## .well-known delegation ## .well-known delegation
To use this method, you need to be able to alter the To use this method, you need to be able to configure the server at
`server_name` 's https server to serve the `/.well-known/matrix/server` `https://<server_name>` to serve a file at
URL. Having an active server (with a valid TLS certificate) serving your `https://<server_name>/.well-known/matrix/server`. There are two ways to do this, shown below.
`server_name` domain is out of the scope of this documentation.
The URL `https://<server_name>/.well-known/matrix/server` should Note that the `.well-known` file is hosted on the default port for `https` (port 443).
return a JSON structure containing the key `m.server` like so:
### External server
For maximum flexibility, you need to configure an external server such as nginx, Apache
or HAProxy to serve the `https://<server_name>/.well-known/matrix/server` file. Setting
up such a server is out of the scope of this documentation, but note that it is often
possible to configure your [reverse proxy](reverse_proxy.md) for this.
The URL `https://<server_name>/.well-known/matrix/server` should be configured
return a JSON structure containing the key `m.server` like this:
```json ```json
{ {
@ -26,8 +38,9 @@ return a JSON structure containing the key `m.server` like so:
} }
``` ```
In our example, this would mean that URL `https://example.com/.well-known/matrix/server` In our example (where we want federation traffic to be routed to
should return: `https://synapse.example.com`, on port 443), this would mean that
`https://example.com/.well-known/matrix/server` should return:
```json ```json
{ {
@ -38,16 +51,29 @@ should return:
Note, specifying a port is optional. If no port is specified, then it defaults Note, specifying a port is optional. If no port is specified, then it defaults
to 8448. to 8448.
With .well-known delegation, federating servers will check for a valid TLS ### Serving a `.well-known/matrix/server` file with Synapse
certificate for the delegated hostname (in our example: `synapse.example.com`).
If you are able to set up your domain so that `https://<server_name>` is routed to
Synapse (i.e., the only change needed is to direct federation traffic to port 443
instead of port 8448), then it is possible to configure Synapse to serve a suitable
`.well-known/matrix/server` file. To do so, add the following to your `homeserver.yaml`
file:
```yaml
serve_server_wellknown: true
```
**Note**: this *only* works if `https://<server_name>` is routed to Synapse, so is
generally not suitable if Synapse is hosted at a subdomain such as
`https://synapse.example.com`.
## SRV DNS record delegation ## SRV DNS record delegation
It is also possible to do delegation using a SRV DNS record. However, that is It is also possible to do delegation using a SRV DNS record. However, that is generally
considered an advanced topic since it's a bit complex to set up, and `.well-known` not recommended, as it can be difficult to configure the TLS certificates correctly in
delegation is already enough in most cases. this case, and it offers little advantage over `.well-known` delegation.
However, if you really need it, you can find some documentation on how such a However, if you really need it, you can find some documentation on what such a
record should look like and how Synapse will use it in [the Matrix record should look like and how Synapse will use it in [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).
@ -68,27 +94,9 @@ wouldn't need any delegation set up.
domain `server_name` points to, you will need to let other servers know how to domain `server_name` points to, you will need to let other servers know how to
find it using delegation. find it using delegation.
### Do you still recommend against using a reverse proxy on the federation port? ### Should I use a reverse proxy for federation traffic?
We no longer actively recommend against using a reverse proxy. Many admins will Generally, using a reverse proxy for both the federation and client traffic is a good
find it easier to direct federation traffic to a reverse proxy and manage their idea, since it saves handling TLS traffic in Synapse. See
own TLS certificates, and this is a supported configuration. [the reverse proxy documentation](reverse_proxy.md) for information on setting up a
See [the reverse proxy documentation](reverse_proxy.md) for information on setting up a
reverse proxy. reverse proxy.
### Do I still need to give my TLS certificates to Synapse if I am using a reverse proxy?
This is no longer necessary. If you are using a reverse proxy for all of your
TLS traffic, then you can set `no_tls: True` in the Synapse config.
In that case, the only reason Synapse needs the certificate is to populate a legacy
`tls_fingerprints` field in the federation API. This is ignored by Synapse 0.99.0
and later, and the only time pre-0.99 Synapses will check it is when attempting to
fetch the server keys - and generally this is delegated via `matrix.org`, which
is running a modern version of Synapse.
### Do I need the same certificate for the client and federation port?
No. There is nothing stopping you from using different certificates,
particularly if you are using a reverse proxy.

View file

@ -93,6 +93,24 @@ pid_file: DATADIR/homeserver.pid
# #
#public_baseurl: https://example.com/ #public_baseurl: https://example.com/
# Uncomment the following to tell other servers to send federation traffic on
# port 443.
#
# By default, other servers will try to reach our server on port 8448, which can
# be inconvenient in some environments.
#
# Provided 'https://<server_name>/' on port 443 is routed to Synapse, this
# option configures Synapse to serve a file at
# 'https://<server_name>/.well-known/matrix/server'. This will tell other
# servers to send traffic to port 443 instead.
#
# See https://matrix-org.github.io/synapse/latest/delegate.html for more
# information.
#
# Defaults to 'false'.
#
#serve_server_wellknown: true
# Set the soft limit on the number of file descriptors synapse can use # Set the soft limit on the number of file descriptors synapse can use
# Zero is used to indicate synapse should set the soft limit to the # Zero is used to indicate synapse should set the soft limit to the
# hard limit. # hard limit.

View file

@ -100,6 +100,7 @@ from synapse.rest.client.register import (
from synapse.rest.health import HealthResource from synapse.rest.health import HealthResource
from synapse.rest.key.v2 import KeyApiV2Resource from synapse.rest.key.v2 import KeyApiV2Resource
from synapse.rest.synapse.client import build_synapse_client_resource_tree from synapse.rest.synapse.client import build_synapse_client_resource_tree
from synapse.rest.well_known import well_known_resource
from synapse.server import HomeServer from synapse.server import HomeServer
from synapse.storage.databases.main.censor_events import CensorEventsStore from synapse.storage.databases.main.censor_events import CensorEventsStore
from synapse.storage.databases.main.client_ips import ClientIpWorkerStore from synapse.storage.databases.main.client_ips import ClientIpWorkerStore
@ -318,6 +319,8 @@ class GenericWorkerServer(HomeServer):
resources.update({CLIENT_API_PREFIX: resource}) resources.update({CLIENT_API_PREFIX: resource})
resources.update(build_synapse_client_resource_tree(self)) resources.update(build_synapse_client_resource_tree(self))
resources.update({"/.well-known": well_known_resource(self)})
elif name == "federation": elif name == "federation":
resources.update({FEDERATION_PREFIX: TransportLayerServer(self)}) resources.update({FEDERATION_PREFIX: TransportLayerServer(self)})
elif name == "media": elif name == "media":

View file

@ -66,7 +66,7 @@ from synapse.rest.admin import AdminRestResource
from synapse.rest.health import HealthResource from synapse.rest.health import HealthResource
from synapse.rest.key.v2 import KeyApiV2Resource from synapse.rest.key.v2 import KeyApiV2Resource
from synapse.rest.synapse.client import build_synapse_client_resource_tree from synapse.rest.synapse.client import build_synapse_client_resource_tree
from synapse.rest.well_known import WellKnownResource from synapse.rest.well_known import well_known_resource
from synapse.server import HomeServer from synapse.server import HomeServer
from synapse.storage import DataStore from synapse.storage import DataStore
from synapse.util.httpresourcetree import create_resource_tree from synapse.util.httpresourcetree import create_resource_tree
@ -189,7 +189,7 @@ class SynapseHomeServer(HomeServer):
"/_matrix/client/unstable": client_resource, "/_matrix/client/unstable": client_resource,
"/_matrix/client/v2_alpha": client_resource, "/_matrix/client/v2_alpha": client_resource,
"/_matrix/client/versions": client_resource, "/_matrix/client/versions": client_resource,
"/.well-known/matrix/client": WellKnownResource(self), "/.well-known": well_known_resource(self),
"/_synapse/admin": AdminRestResource(self), "/_synapse/admin": AdminRestResource(self),
**build_synapse_client_resource_tree(self), **build_synapse_client_resource_tree(self),
} }

View file

@ -262,6 +262,7 @@ class ServerConfig(Config):
self.print_pidfile = config.get("print_pidfile") self.print_pidfile = config.get("print_pidfile")
self.user_agent_suffix = config.get("user_agent_suffix") self.user_agent_suffix = config.get("user_agent_suffix")
self.use_frozen_dicts = config.get("use_frozen_dicts", False) self.use_frozen_dicts = config.get("use_frozen_dicts", False)
self.serve_server_wellknown = config.get("serve_server_wellknown", False)
self.public_baseurl = config.get("public_baseurl") self.public_baseurl = config.get("public_baseurl")
if self.public_baseurl is not None: if self.public_baseurl is not None:
@ -774,6 +775,24 @@ class ServerConfig(Config):
# #
#public_baseurl: https://example.com/ #public_baseurl: https://example.com/
# Uncomment the following to tell other servers to send federation traffic on
# port 443.
#
# By default, other servers will try to reach our server on port 8448, which can
# be inconvenient in some environments.
#
# Provided 'https://<server_name>/' on port 443 is routed to Synapse, this
# option configures Synapse to serve a file at
# 'https://<server_name>/.well-known/matrix/server'. This will tell other
# servers to send traffic to port 443 instead.
#
# See https://matrix-org.github.io/synapse/latest/delegate.html for more
# information.
#
# Defaults to 'false'.
#
#serve_server_wellknown: true
# Set the soft limit on the number of file descriptors synapse can use # Set the soft limit on the number of file descriptors synapse can use
# Zero is used to indicate synapse should set the soft limit to the # Zero is used to indicate synapse should set the soft limit to the
# hard limit. # hard limit.

View file

@ -21,6 +21,7 @@ from twisted.web.server import Request
from synapse.http.server import set_cors_headers from synapse.http.server import set_cors_headers
from synapse.types import JsonDict from synapse.types import JsonDict
from synapse.util import json_encoder from synapse.util import json_encoder
from synapse.util.stringutils import parse_server_name
if TYPE_CHECKING: if TYPE_CHECKING:
from synapse.server import HomeServer from synapse.server import HomeServer
@ -47,8 +48,8 @@ class WellKnownBuilder:
return result return result
class WellKnownResource(Resource): class ClientWellKnownResource(Resource):
"""A Twisted web resource which renders the .well-known file""" """A Twisted web resource which renders the .well-known/matrix/client file"""
isLeaf = 1 isLeaf = 1
@ -67,3 +68,45 @@ class WellKnownResource(Resource):
logger.debug("returning: %s", r) logger.debug("returning: %s", r)
request.setHeader(b"Content-Type", b"application/json") request.setHeader(b"Content-Type", b"application/json")
return json_encoder.encode(r).encode("utf-8") return json_encoder.encode(r).encode("utf-8")
class ServerWellKnownResource(Resource):
"""Resource for .well-known/matrix/server, redirecting to port 443"""
isLeaf = 1
def __init__(self, hs: "HomeServer"):
super().__init__()
self._serve_server_wellknown = hs.config.server.serve_server_wellknown
host, port = parse_server_name(hs.config.server.server_name)
# If we've got this far, then https://<server_name>/ must route to us, so
# we just redirect the traffic to port 443 instead of 8448.
if port is None:
port = 443
self._response = json_encoder.encode({"m.server": f"{host}:{port}"}).encode(
"utf-8"
)
def render_GET(self, request: Request) -> bytes:
if not self._serve_server_wellknown:
request.setResponseCode(404)
request.setHeader(b"Content-Type", b"text/plain")
return b"404. Is anything ever truly *well* known?\n"
request.setHeader(b"Content-Type", b"application/json")
return self._response
def well_known_resource(hs: "HomeServer") -> Resource:
"""Returns a Twisted web resource which handles '.well-known' requests"""
res = Resource()
matrix_resource = Resource()
res.putChild(b"matrix", matrix_resource)
matrix_resource.putChild(b"server", ServerWellKnownResource(hs))
matrix_resource.putChild(b"client", ClientWellKnownResource(hs))
return res

View file

@ -11,17 +11,19 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from twisted.web.resource import Resource
from synapse.rest.well_known import well_known_resource
from synapse.rest.well_known import WellKnownResource
from tests import unittest from tests import unittest
class WellKnownTests(unittest.HomeserverTestCase): class WellKnownTests(unittest.HomeserverTestCase):
def create_test_resource(self): def create_test_resource(self):
# replace the JsonResource with a WellKnownResource # replace the JsonResource with a Resource wrapping the WellKnownResource
return WellKnownResource(self.hs) res = Resource()
res.putChild(b".well-known", well_known_resource(self.hs))
return res
@unittest.override_config( @unittest.override_config(
{ {
@ -29,7 +31,7 @@ class WellKnownTests(unittest.HomeserverTestCase):
"default_identity_server": "https://testis", "default_identity_server": "https://testis",
} }
) )
def test_well_known(self): def test_client_well_known(self):
channel = self.make_request( channel = self.make_request(
"GET", "/.well-known/matrix/client", shorthand=False "GET", "/.well-known/matrix/client", shorthand=False
) )
@ -48,9 +50,27 @@ class WellKnownTests(unittest.HomeserverTestCase):
"public_baseurl": None, "public_baseurl": None,
} }
) )
def test_well_known_no_public_baseurl(self): def test_client_well_known_no_public_baseurl(self):
channel = self.make_request( channel = self.make_request(
"GET", "/.well-known/matrix/client", shorthand=False "GET", "/.well-known/matrix/client", shorthand=False
) )
self.assertEqual(channel.code, 404) self.assertEqual(channel.code, 404)
@unittest.override_config({"serve_server_wellknown": True})
def test_server_well_known(self):
channel = self.make_request(
"GET", "/.well-known/matrix/server", shorthand=False
)
self.assertEqual(channel.code, 200)
self.assertEqual(
channel.json_body,
{"m.server": "test:443"},
)
def test_server_well_known_disabled(self):
channel = self.make_request(
"GET", "/.well-known/matrix/server", shorthand=False
)
self.assertEqual(channel.code, 404)