Improve error handling/logging for perspectives-key fetching.

In particular, don't give up on the first failure.
This commit is contained in:
Richard van der Hoff 2019-05-23 17:31:26 +01:00
parent 753b1270da
commit b825d1c800
1 changed files with 77 additions and 28 deletions

View File

@ -17,6 +17,7 @@
import logging import logging
from collections import namedtuple from collections import namedtuple
import six
from six import raise_from from six import raise_from
from six.moves import urllib from six.moves import urllib
@ -349,6 +350,7 @@ class KeyFetcher(object):
Args: Args:
server_name_and_key_ids (iterable[Tuple[str, iterable[str]]]): server_name_and_key_ids (iterable[Tuple[str, iterable[str]]]):
list of (server_name, iterable[key_id]) tuples to fetch keys for list of (server_name, iterable[key_id]) tuples to fetch keys for
Note that the iterables may be iterated more than once.
Returns: Returns:
Deferred[dict[str, dict[str, synapse.storage.keys.FetchKeyResult|None]]]: Deferred[dict[str, dict[str, synapse.storage.keys.FetchKeyResult|None]]]:
@ -557,7 +559,16 @@ class PerspectivesKeyFetcher(BaseV2KeyFetcher):
Returns: Returns:
Deferred[dict[str, dict[str, synapse.storage.keys.FetchKeyResult]]]: map Deferred[dict[str, dict[str, synapse.storage.keys.FetchKeyResult]]]: map
from server_name -> key_id -> FetchKeyResult from server_name -> key_id -> FetchKeyResult
Raises:
KeyLookupError if there was an error processing the entire response from
the server
""" """
logger.info(
"Requesting keys %s from notary server %s",
server_names_and_key_ids,
perspective_name,
)
# TODO(mark): Set the minimum_valid_until_ts to that needed by # TODO(mark): Set the minimum_valid_until_ts to that needed by
# the events being validated or the current time if validating # the events being validated or the current time if validating
# an incoming request. # an incoming request.
@ -586,40 +597,31 @@ class PerspectivesKeyFetcher(BaseV2KeyFetcher):
time_now_ms = self.clock.time_msec() time_now_ms = self.clock.time_msec()
for response in query_response["server_keys"]: for response in query_response["server_keys"]:
if ( # do this first, so that we can give useful errors thereafter
u"signatures" not in response server_name = response.get("server_name")
or perspective_name not in response[u"signatures"] if not isinstance(server_name, six.string_types):
):
raise KeyLookupError( raise KeyLookupError(
"Key response not signed by perspective server" "Malformed response from key notary server %s: invalid server_name"
" %r" % (perspective_name,) % (perspective_name,)
) )
verified = False try:
for key_id in response[u"signatures"][perspective_name]: processed_response = yield self._process_perspectives_response(
if key_id in perspective_keys:
verify_signed_json(
response, perspective_name, perspective_keys[key_id]
)
verified = True
if not verified:
logging.info(
"Response from perspective server %r not signed with a"
" known key, signed with: %r, known keys: %r",
perspective_name, perspective_name,
list(response[u"signatures"][perspective_name]), perspective_keys,
list(perspective_keys), response,
time_added_ms=time_now_ms,
) )
raise KeyLookupError( except KeyLookupError as e:
"Response not signed with a known key for perspective" logger.warning(
" server %r" % (perspective_name,) "Error processing response from key notary server %s for origin "
"server %s: %s",
perspective_name,
server_name,
e,
) )
# we continue to process the rest of the response
processed_response = yield self.process_v2_response( continue
perspective_name, response, time_added_ms=time_now_ms
)
server_name = response["server_name"]
added_keys.extend( added_keys.extend(
(server_name, key_id, key) for key_id, key in processed_response.items() (server_name, key_id, key) for key_id, key in processed_response.items()
@ -632,6 +634,53 @@ class PerspectivesKeyFetcher(BaseV2KeyFetcher):
defer.returnValue(keys) defer.returnValue(keys)
def _process_perspectives_response(
self, perspective_name, perspective_keys, response, time_added_ms
):
"""Parse a 'Server Keys' structure from the result of a /key/query request
Checks that the entry is correctly signed by the perspectives server, and then
passes over to process_v2_response
Args:
perspective_name (str): the name of the notary server that produced this
result
perspective_keys (dict[str, VerifyKey]): map of key_id->key for the
notary server
response (dict): the json-decoded Server Keys response object
time_added_ms (int): the timestamp to record in server_keys_json
Returns:
Deferred[dict[str, FetchKeyResult]]: map from key_id to result object
"""
if (
u"signatures" not in response
or perspective_name not in response[u"signatures"]
):
raise KeyLookupError("Response not signed by the notary server")
verified = False
for key_id in response[u"signatures"][perspective_name]:
if key_id in perspective_keys:
verify_signed_json(response, perspective_name, perspective_keys[key_id])
verified = True
if not verified:
raise KeyLookupError(
"Response not signed with a known key: signed with: %r, known keys: %r"
% (
list(response[u"signatures"][perspective_name].keys()),
list(perspective_keys.keys()),
)
)
return self.process_v2_response(
perspective_name, response, time_added_ms=time_added_ms
)
class ServerKeyFetcher(BaseV2KeyFetcher): class ServerKeyFetcher(BaseV2KeyFetcher):
"""KeyFetcher impl which fetches keys from the origin servers""" """KeyFetcher impl which fetches keys from the origin servers"""