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

Support for serving server well-known files #11211

Merged
merged 5 commits into from
Nov 1, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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/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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up to you, but I might have been tempted to put this section first since it's the 'easy' case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to put it second, because it's only useful in one very specific case. I thought it was clearer to put the more general solution first.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's reasonable! I think the way you introduced it will give enough hint that there are more options too, so no problem there


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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Ah, this all reminds me of Synapse from a long time ago. Nostalgic.)

Copy link
Member Author

@richvdh richvdh Nov 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's still there in https://matrix-org.github.io/synapse/latest/MSC1711_certificates_FAQ.html if you want the nostalgia hit :)

17 changes: 17 additions & 0 deletions docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,23 @@ pid_file: DATADIR/homeserver.pid
#
#public_baseurl: https://example.com/

# Uncomment the following to tell other servers to redirect traffic to port 443.
richvdh marked this conversation as resolved.
Show resolved Hide resolved
#
# 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
18 changes: 18 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,23 @@ def generate_config_section(
#
#public_baseurl: https://example.com/

# Uncomment the following to tell other servers to redirect traffic to 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": "tesths:443"},
)

def test_server_well_known_disabled(self):
channel = self.make_request(
"GET", "/.well-known/matrix/server", shorthand=False
)
self.assertEqual(channel.code, 404)