1
0
mirror of synced 2025-04-19 11:02:15 +03:00

Add a config option to specify which homeservers can access Sydent (#566)

This commit is contained in:
Shay 2023-06-12 10:02:55 -07:00 committed by GitHub
parent c9980a9fb8
commit 3017c3111d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 115 additions and 39 deletions

1
changelog.d/566.feature Normal file
View File

@ -0,0 +1 @@
Add a config option `homeserver_allow_list` to specify which homeservers can access Sydent.

View File

@ -51,7 +51,8 @@ CONFIG_DEFAULTS = {
# 'prometheus_addr': '', # The address to bind to. Empty string means bind to all.
# The following can be added to your local config file to enable sentry support.
# 'sentry_dsn': 'https://...' # The DSN has configured in the sentry instance project.
# Whether clients and homeservers can register an association using v1 endpoints.
# Whether clients and homeservers can register an association using v1 endpoints. This
# option is now deprecated and will be superceded by the option `enable_v1_access`
"enable_v1_associations": "true",
"delete_tokens_on_bind": "true",
# Prevent outgoing requests from being sent to the following blacklisted
@ -72,6 +73,12 @@ CONFIG_DEFAULTS = {
# This whitelist overrides `ip.blacklist` and defaults to an empty
# list.
"ip.whitelist": "",
# A list of homeservers that are allowed to register with this identity server. Defaults to
# allowing all homeservers. If a list is specified, the config option `enable_v1_access` must be
# set to 'false'.
"homeserver_allow_list": "",
# If set to 'false', entirely disable access via the V1 api.
"enable_v1_access": "true",
},
"db": {
"db.file": os.environ.get("SYDENT_DB_PATH", "sydent.db"),

View File

@ -82,10 +82,6 @@ class GeneralConfig(BaseConfig):
self.sentry_enabled = cfg.has_option("general", "sentry_dsn")
self.sentry_dsn = cfg.get("general", "sentry_dsn", fallback=None)
self.enable_v1_associations = parse_cfg_bool(
cfg.get("general", "enable_v1_associations")
)
self.delete_tokens_on_bind = parse_cfg_bool(
cfg.get("general", "delete_tokens_on_bind")
)
@ -99,6 +95,26 @@ class GeneralConfig(BaseConfig):
self.ip_blacklist = generate_ip_set(ip_blacklist)
self.ip_whitelist = generate_ip_set(ip_whitelist)
self.enable_v1_access = parse_cfg_bool(cfg.get("general", "enable_v1_access"))
homeserver_allow_list = list_from_comma_sep_string(
cfg.get("general", "homeserver_allow_list")
)
if homeserver_allow_list and self.enable_v1_access:
raise RuntimeError(
"""The V1 api must be disabled for the `homeserver_allow_list` to function, if you have
specified a `homeserver_allow_list` in the config file please ensure that the config
option `enable_v1_access` is set to 'false'."""
)
self.homeserver_allow_list = homeserver_allow_list
if not self.enable_v1_access:
self.enable_v1_associations = False
else:
self.enable_v1_associations = parse_cfg_bool(
cfg.get("general", "enable_v1_associations")
)
return False

View File

@ -99,41 +99,54 @@ class ClientApiHttpServer:
identity.putChild(b"api", api)
identity.putChild(b"v2", v2)
identity.putChild(b"versions", VersionsServlet())
api.putChild(b"v1", v1)
validate.putChild(b"email", email)
validate.putChild(b"msisdn", msisdn)
validate_v2.putChild(b"email", email_v2)
validate_v2.putChild(b"msisdn", msisdn_v2)
v1.putChild(b"validate", validate)
v1.putChild(b"lookup", LookupServlet(sydent))
v1.putChild(b"bulk_lookup", BulkLookupServlet(sydent))
v1.putChild(b"pubkey", pubkey)
pubkey.putChild(b"isvalid", PubkeyIsValidServlet(sydent))
pubkey.putChild(b"ed25519:0", Ed25519Servlet(sydent))
pubkey.putChild(b"ephemeral", ephemeralPubkey)
ephemeralPubkey.putChild(b"isvalid", EphemeralPubkeyIsValidServlet(sydent))
# v1
if self.sydent.config.general.enable_v1_access:
api.putChild(b"v1", v1)
validate.putChild(b"email", email)
validate.putChild(b"msisdn", msisdn)
v1.putChild(b"validate", validate)
v1.putChild(b"lookup", LookupServlet(sydent))
v1.putChild(b"bulk_lookup", BulkLookupServlet(sydent))
v1.putChild(b"pubkey", pubkey)
threepid_v1.putChild(b"getValidated3pid", GetValidated3pidServlet(sydent))
threepid_v1.putChild(b"unbind", unbind)
v1.putChild(b"3pid", threepid_v1)
email.putChild(b"requestToken", EmailRequestCodeServlet(sydent))
email.putChild(b"submitToken", EmailValidateCodeServlet(sydent))
msisdn.putChild(b"requestToken", MsisdnRequestCodeServlet(sydent))
msisdn.putChild(b"submitToken", MsisdnValidateCodeServlet(sydent))
v1.putChild(b"store-invite", StoreInviteServlet(sydent))
v1.putChild(b"sign-ed25519", BlindlySignStuffServlet(sydent))
if self.sydent.config.general.enable_v1_associations:
threepid_v1.putChild(b"bind", ThreePidBindServlet(sydent))
# v2
# note v2 loses the /api so goes on 'identity' not 'api'
identity.putChild(b"v2", v2)
validate_v2.putChild(b"email", email_v2)
validate_v2.putChild(b"msisdn", msisdn_v2)
threepid_v2.putChild(
b"getValidated3pid", GetValidated3pidServlet(sydent, require_auth=True)
)
threepid_v2.putChild(b"bind", ThreePidBindServlet(sydent, require_auth=True))
threepid_v2.putChild(b"unbind", unbind)
threepid_v1.putChild(b"getValidated3pid", GetValidated3pidServlet(sydent))
threepid_v1.putChild(b"unbind", unbind)
if self.sydent.config.general.enable_v1_associations:
threepid_v1.putChild(b"bind", ThreePidBindServlet(sydent))
v1.putChild(b"3pid", threepid_v1)
email.putChild(b"requestToken", EmailRequestCodeServlet(sydent))
email.putChild(b"submitToken", EmailValidateCodeServlet(sydent))
email_v2.putChild(
b"requestToken", EmailRequestCodeServlet(sydent, require_auth=True)
)
@ -141,9 +154,6 @@ class ClientApiHttpServer:
b"submitToken", EmailValidateCodeServlet(sydent, require_auth=True)
)
msisdn.putChild(b"requestToken", MsisdnRequestCodeServlet(sydent))
msisdn.putChild(b"submitToken", MsisdnValidateCodeServlet(sydent))
msisdn_v2.putChild(
b"requestToken", MsisdnRequestCodeServlet(sydent, require_auth=True)
)
@ -151,14 +161,6 @@ class ClientApiHttpServer:
b"submitToken", MsisdnValidateCodeServlet(sydent, require_auth=True)
)
v1.putChild(b"store-invite", StoreInviteServlet(sydent))
v1.putChild(b"sign-ed25519", BlindlySignStuffServlet(sydent))
# v2
# note v2 loses the /api so goes on 'identity' not 'api'
identity.putChild(b"v2", v2)
# v2 exclusive APIs
v2.putChild(b"terms", TermsServlet(sydent))
account = AccountServlet(sydent)

View File

@ -53,6 +53,14 @@ class RegisterServlet(SydentResource):
matrix_server = args["matrix_server_name"].lower()
if self.sydent.config.general.homeserver_allow_list:
if matrix_server not in self.sydent.config.general.homeserver_allow_list:
request.setResponseCode(403)
return {
"errcode": "M_UNAUTHORIZED",
"error": "This homeserver is not authorized to access this server.",
}
if not is_valid_matrix_server_name(matrix_server):
request.setResponseCode(400)
return {

View File

@ -98,3 +98,30 @@ class RegisterTestCase(unittest.TestCase):
# Check that we haven't just returned the generic error message in asyncjsonwrap
self.assertNotEqual(channel.json_body["error"], "Internal Server Error")
self.assertIn("JSON", channel.json_body["error"])
class RegisterAllowListTestCase(unittest.TestCase):
"""
Test registering works with the `homeserver_allow_list` config option specified
"""
def test_registering_not_allowed_if_homeserver_not_in_allow_list(self) -> None:
config = {
"general": {
"homeserver_allow_list": "friendly.com, example.com",
"enable_v1_access": "false",
}
}
# Create a new sydent with a homeserver_allow_list specified
self.sydent = make_sydent(test_config=config)
self.sydent.run()
request, channel = make_request(
self.sydent.reactor,
self.sydent.clientApiHttpServer.factory,
"POST",
"/_matrix/identity/v2/account/register",
content={"matrix_server_name": "not.example.com", "access_token": "foo"},
)
self.assertEqual(channel.code, 403)
self.assertEqual(channel.json_body["errcode"], "M_UNAUTHORIZED")

View File

@ -9,3 +9,18 @@ class StartupTestCase(unittest.TestCase):
def test_start(self):
sydent = make_sydent()
sydent.run()
def test_homeserver_allow_list_refuses_to_start_if_v1_not_disabled(self):
"""
Test that Sydent throws a runtime error if `homeserver_allow_list` is specified
but the v1 API has not been disabled
"""
config = {
"general": {
"homeserver_allow_list": "friendly.com, example.com",
"enable_v1_access": "true",
}
}
with self.assertRaises(RuntimeError):
make_sydent(test_config=config)