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

Fixes for the key-notary server #5333

Merged
merged 4 commits into from
Jun 4, 2019
Merged
Show file tree
Hide file tree
Changes from all 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/5333.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix various problems which made the signing-key notary server time out for some requests.
81 changes: 58 additions & 23 deletions synapse/crypto/keyring.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
)
from synapse.storage.keys import FetchKeyResult
from synapse.util import logcontext, unwrapFirstError
from synapse.util.async_helpers import yieldable_gather_results
from synapse.util.logcontext import (
LoggingContext,
PreserveLoggingContext,
Expand Down Expand Up @@ -169,7 +170,12 @@ def process(server_name, json_object, validity_time):
)
)

logger.debug("Verifying for %s with key_ids %s", server_name, key_ids)
logger.debug(
"Verifying for %s with key_ids %s, min_validity %i",
server_name,
key_ids,
validity_time,
)

# add the key request to the queue, but don't start it off yet.
verify_request = VerifyKeyRequest(
Expand Down Expand Up @@ -744,34 +750,50 @@ def __init__(self, hs):
self.clock = hs.get_clock()
self.client = hs.get_http_client()

@defer.inlineCallbacks
def get_keys(self, keys_to_fetch):
"""see KeyFetcher.get_keys"""
# TODO make this more resilient
results = yield logcontext.make_deferred_yieldable(
defer.gatherResults(
[
run_in_background(
self.get_server_verify_key_v2_direct,
server_name,
server_keys.keys(),
)
for server_name, server_keys in keys_to_fetch.items()
],
consumeErrors=True,
).addErrback(unwrapFirstError)
)
"""
Args:
keys_to_fetch (dict[str, iterable[str]]):
the keys to be fetched. server_name -> key_ids

merged = {}
for result in results:
merged.update(result)
Returns:
Deferred[dict[str, dict[str, synapse.storage.keys.FetchKeyResult|None]]]:
map from server_name -> key_id -> FetchKeyResult
"""

results = {}

defer.returnValue(
{server_name: keys for server_name, keys in merged.items() if keys}
@defer.inlineCallbacks
def get_key(key_to_fetch_item):
server_name, key_ids = key_to_fetch_item
try:
keys = yield self.get_server_verify_key_v2_direct(server_name, key_ids)
results[server_name] = keys
except KeyLookupError as e:
logger.warning(
"Error looking up keys %s from %s: %s", key_ids, server_name, e
)
except Exception:
logger.exception("Error getting keys %s from %s", key_ids, server_name)
Copy link
Member

Choose a reason for hiding this comment

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

(This could be done via DeferredList but I think this is neater tbh)


return yieldable_gather_results(get_key, keys_to_fetch.items()).addCallback(
lambda _: results
)

@defer.inlineCallbacks
def get_server_verify_key_v2_direct(self, server_name, key_ids):
"""

Args:
server_name (str):
key_ids (iterable[str]):

Returns:
Deferred[dict[str, FetchKeyResult]]: map from key ID to lookup result

Raises:
KeyLookupError if there was a problem making the lookup
"""
keys = {} # type: dict[str, FetchKeyResult]

for requested_key_id in key_ids:
Expand All @@ -786,6 +808,19 @@ def get_server_verify_key_v2_direct(self, server_name, key_ids):
path="/_matrix/key/v2/server/"
+ urllib.parse.quote(requested_key_id),
ignore_backoff=True,

# we only give the remote server 10s to respond. It should be an
# easy request to handle, so if it doesn't reply within 10s, it's
# probably not going to.
#
# Furthermore, when we are acting as a notary server, we cannot
# wait all day for all of the origin servers, as the requesting
# server will otherwise time out before we can respond.
#
# (Note that get_json may make 4 attempts, so this can still take
# almost 45 seconds to fetch the headers, plus up to another 60s to
# read the response).
timeout=10000,
)
except (NotRetryingDestination, RequestSendFailed) as e:
raise_from(KeyLookupError("Failed to connect to remote server"), e)
Expand All @@ -810,7 +845,7 @@ def get_server_verify_key_v2_direct(self, server_name, key_ids):
)
keys.update(response_keys)

defer.returnValue({server_name: keys})
defer.returnValue(keys)


@defer.inlineCallbacks
Expand Down
12 changes: 2 additions & 10 deletions synapse/rest/key/v2/remote_key_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from twisted.web.server import NOT_DONE_YET

from synapse.api.errors import Codes, SynapseError
from synapse.crypto.keyring import KeyLookupError, ServerKeyFetcher
from synapse.crypto.keyring import ServerKeyFetcher
from synapse.http.server import respond_with_json_bytes, wrap_json_request_handler
from synapse.http.servlet import parse_integer, parse_json_object_from_request

Expand Down Expand Up @@ -215,15 +215,7 @@ def query_keys(self, request, query, query_remote_on_cache_miss=False):
json_results.add(bytes(result["key_json"]))

if cache_misses and query_remote_on_cache_miss:
for server_name, key_ids in cache_misses.items():
try:
yield self.fetcher.get_server_verify_key_v2_direct(
server_name, key_ids
)
except KeyLookupError as e:
logger.info("Failed to fetch key: %s", e)
except Exception:
logger.exception("Failed to get key for %r", server_name)
yield self.fetcher.get_keys(cache_misses)
yield self.query_keys(
request, query, query_remote_on_cache_miss=False
)
Expand Down
12 changes: 5 additions & 7 deletions tests/crypto/test_keyring.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@

from synapse.api.errors import SynapseError
from synapse.crypto import keyring
from synapse.crypto.keyring import (
KeyLookupError,
PerspectivesKeyFetcher,
ServerKeyFetcher,
)
from synapse.crypto.keyring import PerspectivesKeyFetcher, ServerKeyFetcher
from synapse.storage.keys import FetchKeyResult
from synapse.util import logcontext
from synapse.util.logcontext import LoggingContext
Expand Down Expand Up @@ -364,9 +360,11 @@ def get_json(destination, path, **kwargs):
bytes(res["key_json"]), canonicaljson.encode_canonical_json(response)
)

# change the server name: it should cause a rejection
# change the server name: the result should be ignored
response["server_name"] = "OTHER_SERVER"
self.get_failure(fetcher.get_keys(keys_to_fetch), KeyLookupError)

keys = self.get_success(fetcher.get_keys(keys_to_fetch))
self.assertEqual(keys, {})


class PerspectivesKeyFetcherTestCase(unittest.HomeserverTestCase):
Expand Down