summary refs log tree commit diff
diff options
context:
space:
mode:
authorRichard van der Hoff <richard@matrix.org>2019-01-11 10:21:18 +0000
committerRichard van der Hoff <richard@matrix.org>2019-01-11 10:21:18 +0000
commit892f6c98ec58933a7f45fed895f40f5bc6a78b9c (patch)
tree6fe0cb6f181d0cc1c5e432039b42dd6a36359333
parentMerge branch 'develop' of github.com:matrix-org/synapse into matrix-org-hotfixes (diff)
parentchangelog (diff)
downloadsynapse-892f6c98ec58933a7f45fed895f40f5bc6a78b9c.tar.xz
Merge tag 'v0.34.1.1' into matrix-org-hotfixes
Synapse 0.34.1.1 (2019-01-11)
=============================

This release fixes CVE-2019-5885 and is recommended for all users of Synapse 0.34.1.

This release is compatible with Python 2.7 and 3.5+. Python 3.7 is fully supported.

Bugfixes
--------

- Fix spontaneous logout on upgrade
  ([\#4374](https://github.com/matrix-org/synapse/issues/4374))
-rw-r--r--CHANGES.md23
-rw-r--r--changelog.d/4361.misc1
-rw-r--r--changelog.d/4362.misc1
-rw-r--r--debian/changelog13
-rw-r--r--debian/control1
-rw-r--r--docker/Dockerfile4
-rw-r--r--synapse/__init__.py2
-rw-r--r--synapse/api/auth.py65
-rw-r--r--synapse/config/key.py4
-rw-r--r--tests/api/test_auth.py155
10 files changed, 66 insertions, 203 deletions
diff --git a/CHANGES.md b/CHANGES.md

index abbc65ab8e..6fdd31daab 100644 --- a/CHANGES.md +++ b/CHANGES.md
@@ -1,3 +1,26 @@ +Synapse 0.34.1.1 (2019-01-11) +============================= + +This release fixes CVE-2019-5885 and is recommended for all users of Synapse 0.34.1. + +This release is compatible with Python 2.7 and 3.5+. Python 3.7 is fully supported. + +Bugfixes +-------- + +- Fix spontaneous logout on upgrade + ([\#4374](https://github.com/matrix-org/synapse/issues/4374)) + + +Synapse 0.34.1 (2019-01-09) +=========================== + +Internal Changes +---------------- + +- Add better logging for unexpected errors while sending transactions ([\#4361](https://github.com/matrix-org/synapse/issues/4361), [\#4362](https://github.com/matrix-org/synapse/issues/4362)) + + Synapse 0.34.1rc1 (2019-01-08) ============================== diff --git a/changelog.d/4361.misc b/changelog.d/4361.misc deleted file mode 100644
index 020dacb547..0000000000 --- a/changelog.d/4361.misc +++ /dev/null
@@ -1 +0,0 @@ -Add better logging for unexpected errors while sending transactions diff --git a/changelog.d/4362.misc b/changelog.d/4362.misc deleted file mode 100644
index 020dacb547..0000000000 --- a/changelog.d/4362.misc +++ /dev/null
@@ -1 +0,0 @@ -Add better logging for unexpected errors while sending transactions diff --git a/debian/changelog b/debian/changelog
index fc8218bee7..921f7021d8 100644 --- a/debian/changelog +++ b/debian/changelog
@@ -1,9 +1,18 @@ -matrix-synapse-py3 (0.34.0.1) UNRELEASED; urgency=medium +matrix-synapse-py3 (0.34.1+1) stable; urgency=medium + * Remove 'Breaks: matrix-synapse-ldap3'. (matrix-synapse-py3 includes + the matrix-synapse-ldap3 python files, which makes the + matrix-synapse-ldap3 debian package redundant but not broken. + + -- Synapse Packaging team <packages@matrix.org> Wed, 09 Jan 2019 15:30:00 +0000 + +matrix-synapse-py3 (0.34.1) stable; urgency=medium + + * New synapse release 0.34.1. * Update Conflicts specifications to allow installation alongside our matrix-synapse transitional package. - -- Synapse Packaging team <packages@matrix.org> Wed, 02 Jan 2019 17:48:40 +0000 + -- Synapse Packaging team <packages@matrix.org> Wed, 09 Jan 2019 14:52:24 +0000 matrix-synapse-py3 (0.34.0) stable; urgency=medium diff --git a/debian/control b/debian/control
index 500348bc9a..634f04284a 100644 --- a/debian/control +++ b/debian/control
@@ -20,7 +20,6 @@ Package: matrix-synapse-py3 Architecture: amd64 Provides: matrix-synapse Breaks: - matrix-synapse-ldap3, matrix-synapse (<< 0.34.0-0matrix2), matrix-synapse (>= 0.34.0-1), Pre-Depends: dpkg (>= 1.16.1) diff --git a/docker/Dockerfile b/docker/Dockerfile
index db44c02a92..4b739e7d02 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile
@@ -33,9 +33,7 @@ RUN pip install --prefix="/install" --no-warn-script-location \ COPY . /synapse RUN pip install --prefix="/install" --no-warn-script-location \ - lxml \ - psycopg2 \ - /synapse + /synapse[all] ### ### Stage 1: runtime diff --git a/synapse/__init__.py b/synapse/__init__.py
index 7819cfbcbb..d6a191ccc6 100644 --- a/synapse/__init__.py +++ b/synapse/__init__.py
@@ -27,4 +27,4 @@ try: except ImportError: pass -__version__ = "0.34.1rc1" +__version__ = "0.34.1.1" diff --git a/synapse/api/auth.py b/synapse/api/auth.py
index b8a9af7158..ba1019b9b2 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py
@@ -300,20 +300,28 @@ class Auth(object): Raises: AuthError if no user by that token exists or the token is invalid. """ - try: - user_id, guest = self._parse_and_validate_macaroon(token, rights) - except _InvalidMacaroonException: - # doesn't look like a macaroon: treat it as an opaque token which - # must be in the database. - # TODO: it would be nice to get rid of this, but apparently some - # people use access tokens which aren't macaroons + + if rights == "access": + # first look in the database r = yield self._look_up_user_by_access_token(token) - defer.returnValue(r) + if r: + defer.returnValue(r) + # otherwise it needs to be a valid macaroon try: + user_id, guest = self._parse_and_validate_macaroon(token, rights) user = UserID.from_string(user_id) - if guest: + if rights == "access": + if not guest: + # non-guest access tokens must be in the database + logger.warning("Unrecognised access token - not in store.") + raise AuthError( + self.TOKEN_NOT_FOUND_HTTP_STATUS, + "Unrecognised access token.", + errcode=Codes.UNKNOWN_TOKEN, + ) + # Guest access tokens are not stored in the database (there can # only be one access token per guest, anyway). # @@ -354,31 +362,15 @@ class Auth(object): "device_id": None, } else: - # This codepath exists for several reasons: - # * so that we can actually return a token ID, which is used - # in some parts of the schema (where we probably ought to - # use device IDs instead) - # * the only way we currently have to invalidate an - # access_token is by removing it from the database, so we - # have to check here that it is still in the db - # * some attributes (notably device_id) aren't stored in the - # macaroon. They probably should be. - # TODO: build the dictionary from the macaroon once the - # above are fixed - ret = yield self._look_up_user_by_access_token(token) - if ret["user"] != user: - logger.error( - "Macaroon user (%s) != DB user (%s)", - user, - ret["user"] - ) - raise AuthError( - self.TOKEN_NOT_FOUND_HTTP_STATUS, - "User mismatch in macaroon", - errcode=Codes.UNKNOWN_TOKEN - ) + raise RuntimeError("Unknown rights setting %s", rights) defer.returnValue(ret) - except (pymacaroons.exceptions.MacaroonException, TypeError, ValueError): + except ( + _InvalidMacaroonException, + pymacaroons.exceptions.MacaroonException, + TypeError, + ValueError, + ) as e: + logger.warning("Invalid macaroon in auth: %s %s", type(e), e) raise AuthError( self.TOKEN_NOT_FOUND_HTTP_STATUS, "Invalid macaroon passed.", errcode=Codes.UNKNOWN_TOKEN @@ -508,11 +500,8 @@ class Auth(object): def _look_up_user_by_access_token(self, token): ret = yield self.store.get_user_by_access_token(token) if not ret: - logger.warn("Unrecognised access token - not in store.") - raise AuthError( - self.TOKEN_NOT_FOUND_HTTP_STATUS, "Unrecognised access token.", - errcode=Codes.UNKNOWN_TOKEN - ) + defer.returnValue(None) + # we use ret.get() below because *lots* of unit tests stub out # get_user_by_access_token in a way where it only returns a couple of # the fields. diff --git a/synapse/config/key.py b/synapse/config/key.py
index 53f48fe2dd..3b11f0cfa9 100644 --- a/synapse/config/key.py +++ b/synapse/config/key.py
@@ -57,8 +57,8 @@ class KeyConfig(Config): # Unfortunately, there are people out there that don't have this # set. Lets just be "nice" and derive one from their secret key. logger.warn("Config is missing missing macaroon_secret_key") - seed = self.signing_key[0].seed - self.macaroon_secret_key = hashlib.sha256(seed) + seed = bytes(self.signing_key[0]) + self.macaroon_secret_key = hashlib.sha256(seed).digest() self.expire_access_token = config.get("expire_access_token", False) diff --git a/tests/api/test_auth.py b/tests/api/test_auth.py
index 69dc40428b..d77f20e876 100644 --- a/tests/api/test_auth.py +++ b/tests/api/test_auth.py
@@ -194,8 +194,6 @@ class AuthTestCase(unittest.TestCase): @defer.inlineCallbacks def test_get_user_from_macaroon(self): - # TODO(danielwh): Remove this mock when we remove the - # get_user_by_access_token fallback. self.store.get_user_by_access_token = Mock( return_value={"name": "@baldrick:matrix.org", "device_id": "device"} ) @@ -220,6 +218,7 @@ class AuthTestCase(unittest.TestCase): @defer.inlineCallbacks def test_get_guest_user_from_macaroon(self): self.store.get_user_by_id = Mock(return_value={"is_guest": True}) + self.store.get_user_by_access_token = Mock(return_value=None) user_id = "@baldrick:matrix.org" macaroon = pymacaroons.Macaroon( @@ -241,158 +240,6 @@ class AuthTestCase(unittest.TestCase): self.store.get_user_by_id.assert_called_with(user_id) @defer.inlineCallbacks - def test_get_user_from_macaroon_user_db_mismatch(self): - self.store.get_user_by_access_token = Mock( - return_value={"name": "@percy:matrix.org"} - ) - - user = "@baldrick:matrix.org" - macaroon = pymacaroons.Macaroon( - location=self.hs.config.server_name, - identifier="key", - key=self.hs.config.macaroon_secret_key, - ) - macaroon.add_first_party_caveat("gen = 1") - macaroon.add_first_party_caveat("type = access") - macaroon.add_first_party_caveat("user_id = %s" % (user,)) - with self.assertRaises(AuthError) as cm: - yield self.auth.get_user_by_access_token(macaroon.serialize()) - self.assertEqual(401, cm.exception.code) - self.assertIn("User mismatch", cm.exception.msg) - - @defer.inlineCallbacks - def test_get_user_from_macaroon_missing_caveat(self): - # TODO(danielwh): Remove this mock when we remove the - # get_user_by_access_token fallback. - self.store.get_user_by_access_token = Mock( - return_value={"name": "@baldrick:matrix.org"} - ) - - macaroon = pymacaroons.Macaroon( - location=self.hs.config.server_name, - identifier="key", - key=self.hs.config.macaroon_secret_key, - ) - macaroon.add_first_party_caveat("gen = 1") - macaroon.add_first_party_caveat("type = access") - - with self.assertRaises(AuthError) as cm: - yield self.auth.get_user_by_access_token(macaroon.serialize()) - self.assertEqual(401, cm.exception.code) - self.assertIn("No user caveat", cm.exception.msg) - - @defer.inlineCallbacks - def test_get_user_from_macaroon_wrong_key(self): - # TODO(danielwh): Remove this mock when we remove the - # get_user_by_access_token fallback. - self.store.get_user_by_access_token = Mock( - return_value={"name": "@baldrick:matrix.org"} - ) - - user = "@baldrick:matrix.org" - macaroon = pymacaroons.Macaroon( - location=self.hs.config.server_name, - identifier="key", - key=self.hs.config.macaroon_secret_key + "wrong", - ) - macaroon.add_first_party_caveat("gen = 1") - macaroon.add_first_party_caveat("type = access") - macaroon.add_first_party_caveat("user_id = %s" % (user,)) - - with self.assertRaises(AuthError) as cm: - yield self.auth.get_user_by_access_token(macaroon.serialize()) - self.assertEqual(401, cm.exception.code) - self.assertIn("Invalid macaroon", cm.exception.msg) - - @defer.inlineCallbacks - def test_get_user_from_macaroon_unknown_caveat(self): - # TODO(danielwh): Remove this mock when we remove the - # get_user_by_access_token fallback. - self.store.get_user_by_access_token = Mock( - return_value={"name": "@baldrick:matrix.org"} - ) - - user = "@baldrick:matrix.org" - macaroon = pymacaroons.Macaroon( - location=self.hs.config.server_name, - identifier="key", - key=self.hs.config.macaroon_secret_key, - ) - macaroon.add_first_party_caveat("gen = 1") - macaroon.add_first_party_caveat("type = access") - macaroon.add_first_party_caveat("user_id = %s" % (user,)) - macaroon.add_first_party_caveat("cunning > fox") - - with self.assertRaises(AuthError) as cm: - yield self.auth.get_user_by_access_token(macaroon.serialize()) - self.assertEqual(401, cm.exception.code) - self.assertIn("Invalid macaroon", cm.exception.msg) - - @defer.inlineCallbacks - def test_get_user_from_macaroon_expired(self): - # TODO(danielwh): Remove this mock when we remove the - # get_user_by_access_token fallback. - self.store.get_user_by_access_token = Mock( - return_value={"name": "@baldrick:matrix.org"} - ) - - self.store.get_user_by_access_token = Mock( - return_value={"name": "@baldrick:matrix.org"} - ) - - user = "@baldrick:matrix.org" - macaroon = pymacaroons.Macaroon( - location=self.hs.config.server_name, - identifier="key", - key=self.hs.config.macaroon_secret_key, - ) - macaroon.add_first_party_caveat("gen = 1") - macaroon.add_first_party_caveat("type = access") - macaroon.add_first_party_caveat("user_id = %s" % (user,)) - macaroon.add_first_party_caveat("time < -2000") # ms - - self.hs.clock.now = 5000 # seconds - self.hs.config.expire_access_token = True - # yield self.auth.get_user_by_access_token(macaroon.serialize()) - # TODO(daniel): Turn on the check that we validate expiration, when we - # validate expiration (and remove the above line, which will start - # throwing). - with self.assertRaises(AuthError) as cm: - yield self.auth.get_user_by_access_token(macaroon.serialize()) - self.assertEqual(401, cm.exception.code) - self.assertIn("Invalid macaroon", cm.exception.msg) - - @defer.inlineCallbacks - def test_get_user_from_macaroon_with_valid_duration(self): - # TODO(danielwh): Remove this mock when we remove the - # get_user_by_access_token fallback. - self.store.get_user_by_access_token = Mock( - return_value={"name": "@baldrick:matrix.org"} - ) - - self.store.get_user_by_access_token = Mock( - return_value={"name": "@baldrick:matrix.org"} - ) - - user_id = "@baldrick:matrix.org" - macaroon = pymacaroons.Macaroon( - location=self.hs.config.server_name, - identifier="key", - key=self.hs.config.macaroon_secret_key, - ) - macaroon.add_first_party_caveat("gen = 1") - macaroon.add_first_party_caveat("type = access") - macaroon.add_first_party_caveat("user_id = %s" % (user_id,)) - macaroon.add_first_party_caveat("time < 900000000") # ms - - self.hs.clock.now = 5000 # seconds - self.hs.config.expire_access_token = True - - user_info = yield self.auth.get_user_by_access_token(macaroon.serialize()) - user = user_info["user"] - self.assertEqual(UserID.from_string(user_id), user) - - @defer.inlineCallbacks def test_cannot_use_regular_token_as_guest(self): USER_ID = "@percy:matrix.org" self.store.add_access_token_to_user = Mock()