mirror of
				https://github.com/element-hq/synapse.git
				synced 2025-11-04 00:01:22 -05:00 
			
		
		
		
	Support for serving server well-known files (#11211)
Fixes https://github.com/matrix-org/synapse/issues/8308
This commit is contained in:
		
							parent
							
								
									2014098d01
								
							
						
					
					
						commit
						71f9966f27
					
				
							
								
								
									
										1
									
								
								changelog.d/11211.feature
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								changelog.d/11211.feature
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
Add support for serving `/.well-known/matrix/server` files, to redirect federation traffic to port 443.
 | 
			
		||||
@ -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
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
To use this method, you need to be able to alter the
 | 
			
		||||
`server_name` 's https server to serve the `/.well-known/matrix/server`
 | 
			
		||||
URL. Having an active server (with a valid TLS certificate) serving your
 | 
			
		||||
`server_name` domain is out of the scope of this documentation.
 | 
			
		||||
To use this method, you need to be able to configure the server at
 | 
			
		||||
`https://<server_name>` to serve a file at
 | 
			
		||||
`https://<server_name>/.well-known/matrix/server`.  There are two ways to do this, shown below.
 | 
			
		||||
 | 
			
		||||
The URL `https://<server_name>/.well-known/matrix/server` should
 | 
			
		||||
return a JSON structure containing the key `m.server` like so:
 | 
			
		||||
Note that the `.well-known` file is hosted on the default port for `https` (port 443).
 | 
			
		||||
 | 
			
		||||
### 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
 | 
			
		||||
{
 | 
			
		||||
@ -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`
 | 
			
		||||
should return:
 | 
			
		||||
In our example (where we want federation traffic to be routed to
 | 
			
		||||
`https://synapse.example.com`, on port 443), this would mean that
 | 
			
		||||
`https://example.com/.well-known/matrix/server` should return:
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
@ -38,16 +51,29 @@ should return:
 | 
			
		||||
Note, specifying a port is optional. If no port is specified, then it defaults
 | 
			
		||||
to 8448.
 | 
			
		||||
 | 
			
		||||
With .well-known delegation, federating servers will check for a valid TLS
 | 
			
		||||
certificate for the delegated hostname (in our example: `synapse.example.com`).
 | 
			
		||||
### Serving a `.well-known/matrix/server` file with Synapse
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
It is also possible to do delegation using a SRV DNS record. However, that is
 | 
			
		||||
considered an advanced topic since it's a bit complex to set up, and `.well-known`
 | 
			
		||||
delegation is already enough in most cases.
 | 
			
		||||
It is also possible to do delegation using a SRV DNS record. However, that is generally
 | 
			
		||||
not recommended, as it can be difficult to configure the TLS certificates correctly in
 | 
			
		||||
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
 | 
			
		||||
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
 | 
			
		||||
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
 | 
			
		||||
find it easier to direct federation traffic to a reverse proxy and manage their
 | 
			
		||||
own TLS certificates, and this is a supported configuration.
 | 
			
		||||
 | 
			
		||||
See [the reverse proxy documentation](reverse_proxy.md) for information on setting up a
 | 
			
		||||
Generally, using a reverse proxy for both the federation and client traffic is a good
 | 
			
		||||
idea, since it saves handling TLS traffic in Synapse. See
 | 
			
		||||
[the reverse proxy documentation](reverse_proxy.md) for information on setting up a
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
@ -93,6 +93,24 @@ pid_file: DATADIR/homeserver.pid
 | 
			
		||||
#
 | 
			
		||||
#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
 | 
			
		||||
# Zero is used to indicate synapse should set the soft limit to the
 | 
			
		||||
# hard limit.
 | 
			
		||||
 | 
			
		||||
@ -100,6 +100,7 @@ from synapse.rest.client.register import (
 | 
			
		||||
from synapse.rest.health import HealthResource
 | 
			
		||||
from synapse.rest.key.v2 import KeyApiV2Resource
 | 
			
		||||
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.storage.databases.main.censor_events import CensorEventsStore
 | 
			
		||||
from synapse.storage.databases.main.client_ips import ClientIpWorkerStore
 | 
			
		||||
@ -318,6 +319,8 @@ class GenericWorkerServer(HomeServer):
 | 
			
		||||
                    resources.update({CLIENT_API_PREFIX: resource})
 | 
			
		||||
 | 
			
		||||
                    resources.update(build_synapse_client_resource_tree(self))
 | 
			
		||||
                    resources.update({"/.well-known": well_known_resource(self)})
 | 
			
		||||
 | 
			
		||||
                elif name == "federation":
 | 
			
		||||
                    resources.update({FEDERATION_PREFIX: TransportLayerServer(self)})
 | 
			
		||||
                elif name == "media":
 | 
			
		||||
 | 
			
		||||
@ -66,7 +66,7 @@ from synapse.rest.admin import AdminRestResource
 | 
			
		||||
from synapse.rest.health import HealthResource
 | 
			
		||||
from synapse.rest.key.v2 import KeyApiV2Resource
 | 
			
		||||
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.storage import DataStore
 | 
			
		||||
from synapse.util.httpresourcetree import create_resource_tree
 | 
			
		||||
@ -189,7 +189,7 @@ class SynapseHomeServer(HomeServer):
 | 
			
		||||
                    "/_matrix/client/unstable": client_resource,
 | 
			
		||||
                    "/_matrix/client/v2_alpha": client_resource,
 | 
			
		||||
                    "/_matrix/client/versions": client_resource,
 | 
			
		||||
                    "/.well-known/matrix/client": WellKnownResource(self),
 | 
			
		||||
                    "/.well-known": well_known_resource(self),
 | 
			
		||||
                    "/_synapse/admin": AdminRestResource(self),
 | 
			
		||||
                    **build_synapse_client_resource_tree(self),
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -262,6 +262,7 @@ class ServerConfig(Config):
 | 
			
		||||
        self.print_pidfile = config.get("print_pidfile")
 | 
			
		||||
        self.user_agent_suffix = config.get("user_agent_suffix")
 | 
			
		||||
        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")
 | 
			
		||||
        if self.public_baseurl is not None:
 | 
			
		||||
@ -774,6 +775,24 @@ class ServerConfig(Config):
 | 
			
		||||
        #
 | 
			
		||||
        #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
 | 
			
		||||
        # Zero is used to indicate synapse should set the soft limit to the
 | 
			
		||||
        # hard limit.
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,7 @@ from twisted.web.server import Request
 | 
			
		||||
from synapse.http.server import set_cors_headers
 | 
			
		||||
from synapse.types import JsonDict
 | 
			
		||||
from synapse.util import json_encoder
 | 
			
		||||
from synapse.util.stringutils import parse_server_name
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    from synapse.server import HomeServer
 | 
			
		||||
@ -47,8 +48,8 @@ class WellKnownBuilder:
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class WellKnownResource(Resource):
 | 
			
		||||
    """A Twisted web resource which renders the .well-known file"""
 | 
			
		||||
class ClientWellKnownResource(Resource):
 | 
			
		||||
    """A Twisted web resource which renders the .well-known/matrix/client file"""
 | 
			
		||||
 | 
			
		||||
    isLeaf = 1
 | 
			
		||||
 | 
			
		||||
@ -67,3 +68,45 @@ class WellKnownResource(Resource):
 | 
			
		||||
        logger.debug("returning: %s", r)
 | 
			
		||||
        request.setHeader(b"Content-Type", b"application/json")
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
@ -11,17 +11,19 @@
 | 
			
		||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
# See the License for the specific language governing permissions and
 | 
			
		||||
# limitations under the License.
 | 
			
		||||
from twisted.web.resource import Resource
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from synapse.rest.well_known import WellKnownResource
 | 
			
		||||
from synapse.rest.well_known import well_known_resource
 | 
			
		||||
 | 
			
		||||
from tests import unittest
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class WellKnownTests(unittest.HomeserverTestCase):
 | 
			
		||||
    def create_test_resource(self):
 | 
			
		||||
        # replace the JsonResource with a WellKnownResource
 | 
			
		||||
        return WellKnownResource(self.hs)
 | 
			
		||||
        # replace the JsonResource with a Resource wrapping the WellKnownResource
 | 
			
		||||
        res = Resource()
 | 
			
		||||
        res.putChild(b".well-known", well_known_resource(self.hs))
 | 
			
		||||
        return res
 | 
			
		||||
 | 
			
		||||
    @unittest.override_config(
 | 
			
		||||
        {
 | 
			
		||||
@ -29,7 +31,7 @@ class WellKnownTests(unittest.HomeserverTestCase):
 | 
			
		||||
            "default_identity_server": "https://testis",
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    def test_well_known(self):
 | 
			
		||||
    def test_client_well_known(self):
 | 
			
		||||
        channel = self.make_request(
 | 
			
		||||
            "GET", "/.well-known/matrix/client", shorthand=False
 | 
			
		||||
        )
 | 
			
		||||
@ -48,9 +50,27 @@ class WellKnownTests(unittest.HomeserverTestCase):
 | 
			
		||||
            "public_baseurl": None,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    def test_well_known_no_public_baseurl(self):
 | 
			
		||||
    def test_client_well_known_no_public_baseurl(self):
 | 
			
		||||
        channel = self.make_request(
 | 
			
		||||
            "GET", "/.well-known/matrix/client", shorthand=False
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user