Skip to content

Commit

Permalink
Support for serving server well-known files (matrix-org#11211)
Browse files Browse the repository at this point in the history
  • Loading branch information
richvdh authored Nov 1, 2021
1 parent 2014098 commit 71f9966
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 47 deletions.
1 change: 1 addition & 0 deletions changelog.d/11211.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for serving `/.well-known/matrix/server` files, to redirect federation traffic to port 443.
82 changes: 45 additions & 37 deletions docs/delegate.md
Original file line number Diff line number Diff line change
@@ -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`
Expand All @@ -12,22 +16,31 @@ 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.

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
return a JSON structure containing the key `m.server` like so:
The URL `https://<server_name>/.well-known/matrix/server` should be configured
return a JSON structure containing the key `m.server` like this:

```json
{
"m.server": "<synapse.server.name>[:<yourport>]"
}
```

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
{
Expand All @@ -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).

Expand All @@ -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?

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.
### Should I use a reverse proxy for federation traffic?

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.
18 changes: 18 additions & 0 deletions docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions synapse/app/generic_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
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
Expand Down Expand Up @@ -318,6 +319,8 @@ def _listen_http(self, listener_config: ListenerConfig):
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":
Expand Down
4 changes: 2 additions & 2 deletions synapse/app/homeserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
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
Expand Down Expand Up @@ -189,7 +189,7 @@ def _configure_named_resource(self, name, compress=False):
"/_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),
}
Expand Down
19 changes: 19 additions & 0 deletions synapse/config/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ def read_config(self, config, **kwargs):
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:
Expand Down Expand Up @@ -774,6 +775,24 @@ def generate_config_section(
#
#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.
Expand Down
47 changes: 45 additions & 2 deletions synapse/rest/well_known.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
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
Expand All @@ -47,8 +48,8 @@ def get_well_known(self) -> Optional[JsonDict]:
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

Expand All @@ -67,3 +68,45 @@ def render_GET(self, request: Request) -> bytes:
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
32 changes: 26 additions & 6 deletions tests/rest/test_well_known.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,27 @@
# 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(
{
"public_baseurl": "https://tesths",
"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
)
Expand All @@ -48,9 +50,27 @@ def test_well_known(self):
"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)

0 comments on commit 71f9966

Please sign in to comment.