From c59cec0c31b8d804eaaeea241f6a136cae07f68b Mon Sep 17 00:00:00 2001 From: Izalia Mae Date: Wed, 2 Dec 2020 22:41:45 -0500 Subject: [PATCH 1/6] cleanup load_config --- relay/__init__.py | 14 +++++++++----- requirements.txt | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/relay/__init__.py b/relay/__init__.py index 87ab5ef..0d9a9e5 100644 --- a/relay/__init__.py +++ b/relay/__init__.py @@ -9,9 +9,13 @@ import yaml def load_config(): with open('relay.yaml') as f: - yaml_file = yaml.load(f) - whitelist = yaml_file['ap'].get('whitelist', []) - blocked = yaml_file['ap'].get('blocked_instances', []) + options = {} + + ## Prevent a warning message for pyyaml 5.1+ + if getattr(yaml, 'FullLoader', None): + options['Loader'] = yaml.FullLoader + + yaml_file = yaml.load(f, **options) config = { 'db': yaml_file.get('db', 'relay.jsonld'), @@ -19,9 +23,9 @@ def load_config(): 'port': int(yaml_file.get('port', 8080)), 'note': yaml_file.get('note', 'Make a note about your instance here.'), 'ap': { - 'blocked_instances': [] if blocked is None else blocked, + 'blocked_instances': yaml_file['ap'].get('blocked_instances', []), 'host': yaml_file['ap'].get('host', 'localhost'), - 'whitelist': [] if whitelist is None else whitelist, + 'whitelist': yaml_file['ap'].get('whitelist', []), 'whitelist_enabled': yaml_file['ap'].get('whitelist_enabled', False) } } diff --git a/requirements.txt b/requirements.txt index 6b42e7f..b8053bd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ idna==2.7 idna-ssl==1.1.0 multidict==4.3.1 pycryptodome==3.9.4 -PyYAML==3.13 +PyYAML>=5.1 simplejson==3.16.0 yarl==1.2.6 cachetools From 1727425bec6ce2cf3b4ecb7d1705cdf203b132eb Mon Sep 17 00:00:00 2001 From: Izalia Mae Date: Wed, 2 Dec 2020 23:13:33 -0500 Subject: [PATCH 2/6] add relay blocking option --- relay.yaml.example | 3 ++- relay/__init__.py | 1 + relay/actor.py | 13 +++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/relay.yaml.example b/relay.yaml.example index 1a0da3d..0f7f949 100644 --- a/relay.yaml.example +++ b/relay.yaml.example @@ -20,4 +20,5 @@ ap: whitelist_enabled: false whitelist: - 'good-instance.example.com' - - 'another.good-instance.example.com' \ No newline at end of file + - 'another.good-instance.example.com' + block_relays: false diff --git a/relay/__init__.py b/relay/__init__.py index 0d9a9e5..cad19ce 100644 --- a/relay/__init__.py +++ b/relay/__init__.py @@ -23,6 +23,7 @@ def load_config(): 'port': int(yaml_file.get('port', 8080)), 'note': yaml_file.get('note', 'Make a note about your instance here.'), 'ap': { + 'block_relays': yaml_file['ap'].get('block_relays', False), 'blocked_instances': yaml_file['ap'].get('blocked_instances', []), 'host': yaml_file['ap'].get('host', 'localhost'), 'whitelist': yaml_file['ap'].get('whitelist', []), diff --git a/relay/actor.py b/relay/actor.py index eb8c51b..b304721 100644 --- a/relay/actor.py +++ b/relay/actor.py @@ -103,6 +103,12 @@ async def push_message_to_actor(actor, message, our_key_id): logging.info('Caught %r while pushing to %r.', e, inbox) +async def fetch_nodeinfo(domain): + nodeinfo_data = await fetch_actor(f'https://{domain}/nodeinfo/2.0.json') + software = nodeinfo_data.get('software') + return software.get('name') if software else None + + async def follow_remote_actor(actor_uri): actor = await fetch_actor(actor_uri) @@ -235,6 +241,7 @@ async def handle_follow(actor, data, request): following = DATABASE.get('relay-list', []) inbox = get_actor_inbox(actor) + if urlsplit(inbox).hostname in AP_CONFIG['blocked_instances']: return @@ -294,6 +301,12 @@ async def inbox(request): data = await request.json() instance = urlsplit(data['actor']).hostname + if AP_CONFIG['block_relays']: + software = await fetch_nodeinfo(instance) + + if software and 'relay' in software.lower(): + raise aiohttp.web.HTTPUnauthorized(body='relays have been blocked', content_type='text/plain') + if 'actor' not in data or not request['validated']: raise aiohttp.web.HTTPUnauthorized(body='access denied', content_type='text/plain') From f0e08f26b3f29235a12949870a3be72389b66fa4 Mon Sep 17 00:00:00 2001 From: Izalia Mae Date: Fri, 4 Dec 2020 01:43:49 -0500 Subject: [PATCH 3/6] fetch well-known url for nodeinfo --- relay/actor.py | 19 ++++++++++++++++++- relay/remote_actor.py | 9 +++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/relay/actor.py b/relay/actor.py index b304721..d0e1f8b 100644 --- a/relay/actor.py +++ b/relay/actor.py @@ -104,8 +104,25 @@ async def push_message_to_actor(actor, message, our_key_id): async def fetch_nodeinfo(domain): - nodeinfo_data = await fetch_actor(f'https://{domain}/nodeinfo/2.0.json') + headers = {'Accept': 'application/activity+json'} + nodeinfo_url = None + + wk_nodeinfo = await fetch_actor(f'https://{domain}/.well-known/nodeinfo', headers=headers) + + if not wk_nodeinfo: + return + + for link in wk_nodeinfo.get('links', ''): + if link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/2.0': + nodeinfo_url = link['href'] + break + + if not nodeinfo_url: + return + + nodeinfo_data = await fetch_actor(nodeinfo_url) software = nodeinfo_data.get('software') + return software.get('name') if software else None diff --git a/relay/remote_actor.py b/relay/remote_actor.py index faa0ced..279ada7 100644 --- a/relay/remote_actor.py +++ b/relay/remote_actor.py @@ -12,13 +12,18 @@ CACHE_TTL = CONFIG.get('cache-ttl', 3600) ACTORS = TTLCache(CACHE_SIZE, CACHE_TTL) -async def fetch_actor(uri, force=False): +async def fetch_actor(uri, headers={}, force=False): if uri in ACTORS and not force: return ACTORS[uri] + new_headers = {'Accept': 'application/activity+json'} + + for k,v in headers.items(): + new_headers[k.capitalize()] = v + try: async with aiohttp.ClientSession(trace_configs=[http_debug()]) as session: - async with session.get(uri, headers={'Accept': 'application/activity+json'}) as resp: + async with session.get(uri, headers=new_headers) as resp: if resp.status != 200: return None ACTORS[uri] = (await resp.json(encoding='utf-8', content_type=None)) From bc0914b5c1cc2fa43006d9fb2f1df2358b46e30d Mon Sep 17 00:00:00 2001 From: Izalia Mae Date: Fri, 4 Dec 2020 02:34:40 -0500 Subject: [PATCH 4/6] make software blocklist configurable --- relay.yaml.example | 8 +++++++- relay/__init__.py | 3 +-- relay/actor.py | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/relay.yaml.example b/relay.yaml.example index 0f7f949..68f0016 100644 --- a/relay.yaml.example +++ b/relay.yaml.example @@ -21,4 +21,10 @@ ap: whitelist: - 'good-instance.example.com' - 'another.good-instance.example.com' - block_relays: false + # uncomment the lines below to prevent certain activitypub software from posting + # to the relay (all known relays by default). this uses the software name in nodeinfo + #blocked_software: + #- 'activityrelay' + #- 'aoderelay' + #- 'social.seattle.wa.us-relay' + #- 'unciarelay' diff --git a/relay/__init__.py b/relay/__init__.py index cad19ce..3785de6 100644 --- a/relay/__init__.py +++ b/relay/__init__.py @@ -23,7 +23,7 @@ def load_config(): 'port': int(yaml_file.get('port', 8080)), 'note': yaml_file.get('note', 'Make a note about your instance here.'), 'ap': { - 'block_relays': yaml_file['ap'].get('block_relays', False), + 'blocked_software': [v.lower() for v in yaml_file['ap'].get('blocked_software', [])], 'blocked_instances': yaml_file['ap'].get('blocked_instances', []), 'host': yaml_file['ap'].get('host', 'localhost'), 'whitelist': yaml_file['ap'].get('whitelist', []), @@ -35,7 +35,6 @@ def load_config(): CONFIG = load_config() - from .http_signatures import http_signatures_middleware diff --git a/relay/actor.py b/relay/actor.py index d0e1f8b..4264cca 100644 --- a/relay/actor.py +++ b/relay/actor.py @@ -318,10 +318,10 @@ async def inbox(request): data = await request.json() instance = urlsplit(data['actor']).hostname - if AP_CONFIG['block_relays']: + if AP_CONFIG['blocked_software']: software = await fetch_nodeinfo(instance) - if software and 'relay' in software.lower(): + if software and software.lower() in AP_CONFIG['blocked_software']: raise aiohttp.web.HTTPUnauthorized(body='relays have been blocked', content_type='text/plain') if 'actor' not in data or not request['validated']: From 63be7f20348e304d2e16e2bb0a18833506eb3450 Mon Sep 17 00:00:00 2001 From: Izalia Mae <2908-izalia@users.noreply.git.pleroma.social> Date: Sat, 5 Dec 2020 05:34:58 +0000 Subject: [PATCH 5/6] Apply 1 suggestion(s) to 1 file(s) --- relay/actor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relay/actor.py b/relay/actor.py index 4264cca..b45ee6f 100644 --- a/relay/actor.py +++ b/relay/actor.py @@ -120,7 +120,7 @@ async def fetch_nodeinfo(domain): if not nodeinfo_url: return - nodeinfo_data = await fetch_actor(nodeinfo_url) + nodeinfo_data = await fetch_actor(nodeinfo_url, headers=headers) software = nodeinfo_data.get('software') return software.get('name') if software else None From 6cef5d8f387a3b303cb3fe0802a08d12b89363ab Mon Sep 17 00:00:00 2001 From: Izalia Mae <2908-izalia@users.noreply.git.pleroma.social> Date: Sat, 5 Dec 2020 05:35:10 +0000 Subject: [PATCH 6/6] Apply 1 suggestion(s) to 1 file(s) --- relay/actor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relay/actor.py b/relay/actor.py index b45ee6f..aba72ea 100644 --- a/relay/actor.py +++ b/relay/actor.py @@ -104,7 +104,7 @@ async def push_message_to_actor(actor, message, our_key_id): async def fetch_nodeinfo(domain): - headers = {'Accept': 'application/activity+json'} + headers = {'Accept': 'application/json'} nodeinfo_url = None wk_nodeinfo = await fetch_actor(f'https://{domain}/.well-known/nodeinfo', headers=headers)