Add a config option to specify which homeservers can access Sydent (#566)
This commit is contained in:
parent
c9980a9fb8
commit
3017c3111d
1
changelog.d/566.feature
Normal file
1
changelog.d/566.feature
Normal file
@ -0,0 +1 @@
|
||||
Add a config option `homeserver_allow_list` to specify which homeservers can access Sydent.
|
@ -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"),
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user