mirror of
https://github.com/element-hq/synapse.git
synced 2024-12-15 17:51:10 +00:00
An federation whitelist query endpoint extension (#16848)
This is to allow clients to query the configured federation whitelist. Disabled by default. --------- Co-authored-by: Devon Hudson <devonhudson@librem.one> Co-authored-by: devonh <devon.dmytro@gmail.com> Co-authored-by: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com>
This commit is contained in:
parent
59ac541310
commit
038b9ec59a
8 changed files with 243 additions and 0 deletions
1
changelog.d/16848.feature
Normal file
1
changelog.d/16848.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Add a feature that allows clients to query the configured federation whitelist. Disabled by default.
|
|
@ -1232,6 +1232,31 @@ federation_domain_whitelist:
|
||||||
- syd.example.com
|
- syd.example.com
|
||||||
```
|
```
|
||||||
---
|
---
|
||||||
|
### `federation_whitelist_endpoint_enabled`
|
||||||
|
|
||||||
|
Enables an endpoint for fetching the federation whitelist config.
|
||||||
|
|
||||||
|
The request method and path is `GET /_synapse/client/config/federation_whitelist`, and the
|
||||||
|
response format is:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"whitelist_enabled": true, // Whether the federation whitelist is being enforced
|
||||||
|
"whitelist": [ // Which server names are allowed by the whitelist
|
||||||
|
"example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If `whitelist_enabled` is `false` then the server is permitted to federate with all others.
|
||||||
|
|
||||||
|
The endpoint requires authentication.
|
||||||
|
|
||||||
|
Example configuration:
|
||||||
|
```yaml
|
||||||
|
federation_whitelist_endpoint_enabled: true
|
||||||
|
```
|
||||||
|
---
|
||||||
### `federation_metrics_domains`
|
### `federation_metrics_domains`
|
||||||
|
|
||||||
Report prometheus metrics on the age of PDUs being sent to and received from
|
Report prometheus metrics on the age of PDUs being sent to and received from
|
||||||
|
|
|
@ -42,6 +42,10 @@ class FederationConfig(Config):
|
||||||
for domain in federation_domain_whitelist:
|
for domain in federation_domain_whitelist:
|
||||||
self.federation_domain_whitelist[domain] = True
|
self.federation_domain_whitelist[domain] = True
|
||||||
|
|
||||||
|
self.federation_whitelist_endpoint_enabled = config.get(
|
||||||
|
"federation_whitelist_endpoint_enabled", False
|
||||||
|
)
|
||||||
|
|
||||||
federation_metrics_domains = config.get("federation_metrics_domains") or []
|
federation_metrics_domains = config.get("federation_metrics_domains") or []
|
||||||
validate_config(
|
validate_config(
|
||||||
_METRICS_FOR_DOMAINS_SCHEMA,
|
_METRICS_FOR_DOMAINS_SCHEMA,
|
||||||
|
|
|
@ -23,6 +23,7 @@ from typing import TYPE_CHECKING, Mapping
|
||||||
|
|
||||||
from twisted.web.resource import Resource
|
from twisted.web.resource import Resource
|
||||||
|
|
||||||
|
from synapse.rest.synapse.client.federation_whitelist import FederationWhitelistResource
|
||||||
from synapse.rest.synapse.client.new_user_consent import NewUserConsentResource
|
from synapse.rest.synapse.client.new_user_consent import NewUserConsentResource
|
||||||
from synapse.rest.synapse.client.pick_idp import PickIdpResource
|
from synapse.rest.synapse.client.pick_idp import PickIdpResource
|
||||||
from synapse.rest.synapse.client.pick_username import pick_username_resource
|
from synapse.rest.synapse.client.pick_username import pick_username_resource
|
||||||
|
@ -77,6 +78,9 @@ def build_synapse_client_resource_tree(hs: "HomeServer") -> Mapping[str, Resourc
|
||||||
# To be removed in Synapse v1.32.0.
|
# To be removed in Synapse v1.32.0.
|
||||||
resources["/_matrix/saml2"] = res
|
resources["/_matrix/saml2"] = res
|
||||||
|
|
||||||
|
if hs.config.federation.federation_whitelist_endpoint_enabled:
|
||||||
|
resources[FederationWhitelistResource.PATH] = FederationWhitelistResource(hs)
|
||||||
|
|
||||||
if hs.config.experimental.msc4108_enabled:
|
if hs.config.experimental.msc4108_enabled:
|
||||||
resources["/_synapse/client/rendezvous"] = MSC4108RendezvousSessionResource(hs)
|
resources["/_synapse/client/rendezvous"] = MSC4108RendezvousSessionResource(hs)
|
||||||
|
|
||||||
|
|
66
synapse/rest/synapse/client/federation_whitelist.py
Normal file
66
synapse/rest/synapse/client/federation_whitelist.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#
|
||||||
|
# This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024 New Vector, Ltd
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# See the GNU Affero General Public License for more details:
|
||||||
|
# <https://www.gnu.org/licenses/agpl-3.0.html>.
|
||||||
|
#
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import TYPE_CHECKING, Tuple
|
||||||
|
|
||||||
|
from synapse.http.server import DirectServeJsonResource
|
||||||
|
from synapse.http.site import SynapseRequest
|
||||||
|
from synapse.types import JsonDict
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from synapse.server import HomeServer
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class FederationWhitelistResource(DirectServeJsonResource):
|
||||||
|
"""Custom endpoint (disabled by default) to fetch the federation whitelist
|
||||||
|
config.
|
||||||
|
|
||||||
|
Only enabled if `federation_whitelist_endpoint_enabled` feature is enabled.
|
||||||
|
|
||||||
|
Response format:
|
||||||
|
|
||||||
|
{
|
||||||
|
"whitelist_enabled": true, // Whether the federation whitelist is being enforced
|
||||||
|
"whitelist": [ // Which server names are allowed by the whitelist
|
||||||
|
"example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
PATH = "/_synapse/client/v1/config/federation_whitelist"
|
||||||
|
|
||||||
|
def __init__(self, hs: "HomeServer"):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self._federation_whitelist = hs.config.federation.federation_domain_whitelist
|
||||||
|
|
||||||
|
self._auth = hs.get_auth()
|
||||||
|
|
||||||
|
async def _async_render_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
|
||||||
|
await self._auth.get_user_by_req(request)
|
||||||
|
|
||||||
|
whitelist = []
|
||||||
|
if self._federation_whitelist:
|
||||||
|
# federation_whitelist is actually a dict, not a list
|
||||||
|
whitelist = list(self._federation_whitelist)
|
||||||
|
|
||||||
|
return_dict: JsonDict = {
|
||||||
|
"whitelist_enabled": self._federation_whitelist is not None,
|
||||||
|
"whitelist": whitelist,
|
||||||
|
}
|
||||||
|
|
||||||
|
return 200, return_dict
|
12
tests/rest/synapse/__init__.py
Normal file
12
tests/rest/synapse/__init__.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#
|
||||||
|
# This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024 New Vector, Ltd
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# See the GNU Affero General Public License for more details:
|
||||||
|
# <https://www.gnu.org/licenses/agpl-3.0.html>.
|
12
tests/rest/synapse/client/__init__.py
Normal file
12
tests/rest/synapse/client/__init__.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#
|
||||||
|
# This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024 New Vector, Ltd
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# See the GNU Affero General Public License for more details:
|
||||||
|
# <https://www.gnu.org/licenses/agpl-3.0.html>.
|
119
tests/rest/synapse/client/test_federation_whitelist.py
Normal file
119
tests/rest/synapse/client/test_federation_whitelist.py
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
#
|
||||||
|
# This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024 New Vector, Ltd
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# See the GNU Affero General Public License for more details:
|
||||||
|
# <https://www.gnu.org/licenses/agpl-3.0.html>.
|
||||||
|
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from twisted.web.resource import Resource
|
||||||
|
|
||||||
|
from synapse.rest import admin
|
||||||
|
from synapse.rest.client import login
|
||||||
|
from synapse.rest.synapse.client import build_synapse_client_resource_tree
|
||||||
|
|
||||||
|
from tests import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class FederationWhitelistTests(unittest.HomeserverTestCase):
|
||||||
|
servlets = [
|
||||||
|
admin.register_servlets_for_client_rest_resource,
|
||||||
|
login.register_servlets,
|
||||||
|
]
|
||||||
|
|
||||||
|
def create_resource_dict(self) -> Dict[str, Resource]:
|
||||||
|
base = super().create_resource_dict()
|
||||||
|
base.update(build_synapse_client_resource_tree(self.hs))
|
||||||
|
return base
|
||||||
|
|
||||||
|
def test_default(self) -> None:
|
||||||
|
"If the config option is not enabled, the endpoint should 404"
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET", "/_synapse/client/v1/config/federation_whitelist", shorthand=False
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(channel.code, 404)
|
||||||
|
|
||||||
|
@unittest.override_config({"federation_whitelist_endpoint_enabled": True})
|
||||||
|
def test_no_auth(self) -> None:
|
||||||
|
"Endpoint requires auth when enabled"
|
||||||
|
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET", "/_synapse/client/v1/config/federation_whitelist", shorthand=False
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(channel.code, 401)
|
||||||
|
|
||||||
|
@unittest.override_config({"federation_whitelist_endpoint_enabled": True})
|
||||||
|
def test_no_whitelist(self) -> None:
|
||||||
|
"Test when there is no whitelist configured"
|
||||||
|
|
||||||
|
self.register_user("user", "password")
|
||||||
|
tok = self.login("user", "password")
|
||||||
|
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
"/_synapse/client/v1/config/federation_whitelist",
|
||||||
|
shorthand=False,
|
||||||
|
access_token=tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body, {"whitelist_enabled": False, "whitelist": []}
|
||||||
|
)
|
||||||
|
|
||||||
|
@unittest.override_config(
|
||||||
|
{
|
||||||
|
"federation_whitelist_endpoint_enabled": True,
|
||||||
|
"federation_domain_whitelist": ["example.com"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def test_whitelist(self) -> None:
|
||||||
|
"Test when there is a whitelist configured"
|
||||||
|
|
||||||
|
self.register_user("user", "password")
|
||||||
|
tok = self.login("user", "password")
|
||||||
|
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
"/_synapse/client/v1/config/federation_whitelist",
|
||||||
|
shorthand=False,
|
||||||
|
access_token=tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body, {"whitelist_enabled": True, "whitelist": ["example.com"]}
|
||||||
|
)
|
||||||
|
|
||||||
|
@unittest.override_config(
|
||||||
|
{
|
||||||
|
"federation_whitelist_endpoint_enabled": True,
|
||||||
|
"federation_domain_whitelist": ["example.com", "example.com"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def test_whitelist_no_duplicates(self) -> None:
|
||||||
|
"Test when there is a whitelist configured with duplicates, no duplicates are returned"
|
||||||
|
|
||||||
|
self.register_user("user", "password")
|
||||||
|
tok = self.login("user", "password")
|
||||||
|
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
"/_synapse/client/v1/config/federation_whitelist",
|
||||||
|
shorthand=False,
|
||||||
|
access_token=tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body, {"whitelist_enabled": True, "whitelist": ["example.com"]}
|
||||||
|
)
|
Loading…
Reference in a new issue