Compare commits

...

30 Commits

Author SHA1 Message Date
Andrew Morgan
e0715d035b Merge branch 'develop' into anoa/blacklist_ip_ranges
* develop: (45 commits)
  URL preview blacklisting fixes (#5155)
  Revert 085ae346ac
  Add a DUMMY stage to captcha-only registration flow
  Make Prometheus snippet less confusing on the metrics collection doc (#4288)
  Set syslog identifiers in systemd units (#5023)
  Run Black on the tests again (#5170)
  Add AllowEncodedSlashes to apache (#5068)
  remove instructions for jessie installation (#5164)
  Run `black` on per_destination_queue
  Limit the number of EDUs in transactions to 100 as expected by receiver (#5138)
  Fix bogus imports in tests (#5154)
  add options to require an access_token to GET /profile and /publicRooms on CS API (#5083)
  Do checks on aliases for incoming m.room.aliases events (#5128)
  Remove the requirement to authenticate for /admin/server_version. (#5122)
  Fix spelling in server notices admin API docs (#5142)
  Fix sample config
  0.99.3.2
  include disco in deb build target list
  changelog
  Debian: we now need libpq-dev.
  ...
2019-05-10 10:39:08 -07:00
Andrew Morgan
6b29f7ecf9 regen config 2019-05-10 10:32:09 -07:00
Andrew Morgan
7f15dd71ab lint 2019-05-10 09:57:35 -07:00
Andrew Morgan
358777d814 lower pump value 2019-05-10 09:53:33 -07:00
Andrew Morgan
4ba420f298 always blacklist 0.0.0.0, :: 2019-05-10 09:50:40 -07:00
Andrew Morgan
ede582f91a lint 2019-05-08 16:52:13 -07:00
Andrew Morgan
a30a778e6c Update tests 2019-05-08 15:42:35 -07:00
Andrew Morgan
aee810a548 Fix tests and various small review issues 2019-05-08 11:52:25 -07:00
Andrew Morgan
43ffe47b77 Enable federation blacklisting by default 2019-05-08 02:24:21 -07:00
Andrew Morgan
ec67848235 Remove yield 2019-05-03 14:40:44 -07:00
Andrew Morgan
e2bc9af798 lint 2019-05-03 14:27:30 -07:00
Andrew Morgan
13f430cee4 Same behavior for no result and result blacklisted 2019-05-03 14:26:00 -07:00
Andrew Morgan
131b9c00c6 yield deferred 2019-05-02 18:53:09 -07:00
Andrew Morgan
15d1802668 lint 2019-05-02 17:07:13 -07:00
Andrew Morgan
517794ed5f isort locally didn't have a problem >:( 2019-05-02 17:06:36 -07:00
Andrew Morgan
6592691f9f Import at the top 2019-05-02 16:55:56 -07:00
Andrew Morgan
152d7a8eaf Remove different behaviour for fed vs. nonfed 2019-05-02 16:53:51 -07:00
Richard van der Hoff
e1feb45f2b We can't throw exceptions in an IResolutionReceiver 2019-05-02 22:55:55 +01:00
Andrew Morgan
968ddca569 Testing 2019-05-02 14:08:18 -07:00
Andrew Morgan
3f4f931e49 Merge branch 'develop' into anoa/blacklist_ip_ranges
* develop: (34 commits)
  Add a default .m.rule.tombstone push rule (#4867)
  Fix infinite loop in presence handler
  changelog
  more logging improvements
  remove extraneous exception logging
  Clarify logging when PDU signature checking fails
  Changelog
  Add --no-pep-517 to README instructions
  set PIP_USE_PEP517 = False for tests
  Fix handling of SYNAPSE_NO_TLS in docker image (#5005)
  Config option for verifying federation certificates (MSC 1711) (#4967)
  Remove log error for .well-known/matrix/client (#4972)
  Prevent "producer not unregistered" message (#5009)
  add gpg key fingerprint
  Don't crash on lack of expiry templates
  Update debian install docs for new key and repo (#5074)
  Add management endpoints for account validity
  Send out emails with links to extend an account's validity period
  Make sure we're not registering the same 3pid twice
  Newsfile
  ...
2019-04-30 15:02:30 -07:00
Andrew Morgan
6479cd5f88 Use an empty list as default 2019-04-30 15:01:56 -07:00
Andrew Morgan
0e2f8ca019 Add some notes 2019-04-10 12:44:15 +01:00
Andrew Morgan
1b3989bcc4 lint 2019-04-10 12:23:47 +01:00
Andrew Morgan
9795344905 Don't raise an exception if coming from federation 2019-04-10 12:22:20 +01:00
Andrew Morgan
25c99dc8a5 sample config 2019-04-10 11:59:23 +01:00
Andrew Morgan
6631485bef actually add changelog 2019-04-10 11:56:51 +01:00
Andrew Morgan
9f1f03f800 lint and changelog 2019-04-10 11:55:40 +01:00
Andrew Morgan
0200c864d2 lint 2019-04-10 11:49:48 +01:00
Andrew Morgan
4501489e39 tests pass 2019-04-10 11:46:03 +01:00
Andrew Morgan
1b8532b458 tests fail 2019-04-10 10:35:39 +01:00
6 changed files with 168 additions and 14 deletions

1
changelog.d/5043.feature Normal file
View File

@@ -0,0 +1 @@
Add ability to blacklist IP ranges for the federation client.

View File

@@ -115,6 +115,24 @@ pid_file: DATADIR/homeserver.pid
# - nyc.example.com
# - syd.example.com
# Prevent federation requests from being sent to the following
# blacklist IP address CIDR ranges. If this option is not specified, or
# specified with an empty list, no ip range blacklist will be enforced.
#
# (0.0.0.0 and :: are always blacklisted, whether or not they are explicitly
# listed here, since they correspond to unroutable addresses.)
#
federation_ip_range_blacklist:
- '127.0.0.0/8'
- '10.0.0.0/8'
- '172.16.0.0/12'
- '192.168.0.0/16'
- '100.64.0.0/10'
- '169.254.0.0/16'
- '::1/128'
- 'fe80::/64'
- 'fc00::/7'
# List of ports that Synapse should listen on, their purpose and their
# configuration.
#

View File

@@ -17,6 +17,8 @@
import logging
import os.path
from netaddr import IPSet
from synapse.http.endpoint import parse_and_validate_server_name
from synapse.python_dependencies import DependencyException, check_requirements
@@ -137,6 +139,24 @@ class ServerConfig(Config):
for domain in federation_domain_whitelist:
self.federation_domain_whitelist[domain] = True
self.federation_ip_range_blacklist = config.get(
"federation_ip_range_blacklist", [],
)
# Attempt to create an IPSet from the given ranges
try:
self.federation_ip_range_blacklist = IPSet(
self.federation_ip_range_blacklist
)
# Always blacklist 0.0.0.0, ::
self.federation_ip_range_blacklist.update(["0.0.0.0", "::"])
except Exception as e:
raise ConfigError(
"Invalid range(s) provided in "
"federation_ip_range_blacklist: %s" % e
)
if self.public_baseurl is not None:
if self.public_baseurl[-1] != '/':
self.public_baseurl += '/'
@@ -386,6 +406,24 @@ class ServerConfig(Config):
# - nyc.example.com
# - syd.example.com
# Prevent federation requests from being sent to the following
# blacklist IP address CIDR ranges. If this option is not specified, or
# specified with an empty list, no ip range blacklist will be enforced.
#
# (0.0.0.0 and :: are always blacklisted, whether or not they are explicitly
# listed here, since they correspond to unroutable addresses.)
#
federation_ip_range_blacklist:
- '127.0.0.0/8'
- '10.0.0.0/8'
- '172.16.0.0/12'
- '192.168.0.0/16'
- '100.64.0.0/10'
- '169.254.0.0/16'
- '::1/128'
- 'fe80::/64'
- 'fc00::/7'
# List of ports that Synapse should listen on, their purpose and their
# configuration.
#

View File

@@ -165,7 +165,8 @@ class BlacklistingAgentWrapper(Agent):
ip_address, self._ip_whitelist, self._ip_blacklist
):
logger.info(
"Blocking access to %s because of blacklist" % (ip_address,)
"Blocking access to %s due to blacklist" %
(ip_address,)
)
e = SynapseError(403, "IP address blocked by IP blacklist entry")
return defer.fail(Failure(e))
@@ -263,9 +264,6 @@ class SimpleHttpClient(object):
uri (str): URI to query.
data (bytes): Data to send in the request body, if applicable.
headers (t.w.http_headers.Headers): Request headers.
Raises:
SynapseError: If the IP is blacklisted.
"""
# A small wrapper around self.agent.request() so we can easily attach
# counters to it

View File

@@ -27,9 +27,11 @@ import treq
from canonicaljson import encode_canonical_json
from prometheus_client import Counter
from signedjson.sign import sign_json
from zope.interface import implementer
from twisted.internet import defer, protocol
from twisted.internet.error import DNSLookupError
from twisted.internet.interfaces import IReactorPluggableNameResolver
from twisted.internet.task import _EPSILON, Cooperator
from twisted.web._newclient import ResponseDone
from twisted.web.http_headers import Headers
@@ -44,6 +46,7 @@ from synapse.api.errors import (
SynapseError,
)
from synapse.http import QuieterFileBodyProducer
from synapse.http.client import BlacklistingAgentWrapper, IPBlacklistingResolver
from synapse.http.federation.matrix_federation_agent import MatrixFederationAgent
from synapse.util.async_helpers import timeout_deferred
from synapse.util.logcontext import make_deferred_yieldable
@@ -172,19 +175,44 @@ class MatrixFederationHttpClient(object):
self.hs = hs
self.signing_key = hs.config.signing_key[0]
self.server_name = hs.hostname
reactor = hs.get_reactor()
real_reactor = hs.get_reactor()
# We need to use a DNS resolver which filters out blacklisted IP
# addresses, to prevent DNS rebinding.
nameResolver = IPBlacklistingResolver(
real_reactor, None, hs.config.federation_ip_range_blacklist,
)
@implementer(IReactorPluggableNameResolver)
class Reactor(object):
def __getattr__(_self, attr):
if attr == "nameResolver":
return nameResolver
else:
return getattr(real_reactor, attr)
self.reactor = Reactor()
self.agent = MatrixFederationAgent(
hs.get_reactor(),
self.reactor,
tls_client_options_factory,
)
# Use a BlacklistingAgentWrapper to prevent circumventing the IP
# blacklist via IP literals in server names
self.agent = BlacklistingAgentWrapper(
self.agent, self.reactor,
ip_blacklist=hs.config.federation_ip_range_blacklist,
)
self.clock = hs.get_clock()
self._store = hs.get_datastore()
self.version_string_bytes = hs.version_string.encode('ascii')
self.default_timeout = 60
def schedule(x):
reactor.callLater(_EPSILON, x)
self.reactor.callLater(_EPSILON, x)
self._cooperator = Cooperator(scheduler=schedule)
@@ -370,7 +398,7 @@ class MatrixFederationHttpClient(object):
request_deferred = timeout_deferred(
request_deferred,
timeout=_sec_timeout,
reactor=self.hs.get_reactor(),
reactor=self.reactor,
)
response = yield request_deferred
@@ -397,7 +425,7 @@ class MatrixFederationHttpClient(object):
d = timeout_deferred(
d,
timeout=_sec_timeout,
reactor=self.hs.get_reactor(),
reactor=self.reactor,
)
try:
@@ -586,7 +614,7 @@ class MatrixFederationHttpClient(object):
)
body = yield _handle_json_response(
self.hs.get_reactor(), self.default_timeout, request, response,
self.reactor, self.default_timeout, request, response,
)
defer.returnValue(body)
@@ -645,7 +673,7 @@ class MatrixFederationHttpClient(object):
_sec_timeout = self.default_timeout
body = yield _handle_json_response(
self.hs.get_reactor(), _sec_timeout, request, response,
self.reactor, _sec_timeout, request, response,
)
defer.returnValue(body)
@@ -704,7 +732,7 @@ class MatrixFederationHttpClient(object):
)
body = yield _handle_json_response(
self.hs.get_reactor(), self.default_timeout, request, response,
self.reactor, self.default_timeout, request, response,
)
defer.returnValue(body)
@@ -753,7 +781,7 @@ class MatrixFederationHttpClient(object):
)
body = yield _handle_json_response(
self.hs.get_reactor(), self.default_timeout, request, response,
self.reactor, self.default_timeout, request, response,
)
defer.returnValue(body)
@@ -801,7 +829,7 @@ class MatrixFederationHttpClient(object):
try:
d = _readBodyToFile(response, output_stream, max_size)
d.addTimeout(self.default_timeout, self.hs.get_reactor())
d.addTimeout(self.default_timeout, self.reactor)
length = yield make_deferred_yieldable(d)
except Exception as e:
logger.warn(

View File

@@ -15,6 +15,8 @@
from mock import Mock
from netaddr import IPSet
from twisted.internet import defer
from twisted.internet.defer import TimeoutError
from twisted.internet.error import ConnectingCancelledError, DNSLookupError
@@ -209,6 +211,75 @@ class FederationClientTests(HomeserverTestCase):
self.assertIsInstance(f.value, RequestSendFailed)
self.assertIsInstance(f.value.inner_exception, ResponseNeverReceived)
def test_client_ip_range_blacklist(self):
"""Ensure that Synapse does not try to connect to blacklisted IPs"""
# Set up the ip_range blacklist
self.hs.config.federation_ip_range_blacklist = IPSet([
"127.0.0.0/8",
"fe80::/64",
])
self.reactor.lookups["internal"] = "127.0.0.1"
self.reactor.lookups["internalv6"] = "fe80:0:0:0:0:8a2e:370:7337"
self.reactor.lookups["fine"] = "10.20.30.40"
cl = MatrixFederationHttpClient(self.hs, None)
# Try making a GET request to a blacklisted IPv4 address
# ------------------------------------------------------
# Make the request
d = cl.get_json("internal:8008", "foo/bar", timeout=10000)
# Nothing happened yet
self.assertNoResult(d)
self.pump(1)
# Check that it was unable to resolve the address
clients = self.reactor.tcpClients
self.assertEqual(len(clients), 0)
f = self.failureResultOf(d)
self.assertIsInstance(f.value, RequestSendFailed)
self.assertIsInstance(f.value.inner_exception, DNSLookupError)
# Try making a POST request to a blacklisted IPv6 address
# -------------------------------------------------------
# Make the request
d = cl.post_json("internalv6:8008", "foo/bar", timeout=10000)
# Nothing has happened yet
self.assertNoResult(d)
# Move the reactor forwards
self.pump(1)
# Check that it was unable to resolve the address
clients = self.reactor.tcpClients
self.assertEqual(len(clients), 0)
# Check that it was due to a blacklisted DNS lookup
f = self.failureResultOf(d, RequestSendFailed)
self.assertIsInstance(f.value.inner_exception, DNSLookupError)
# Try making a GET request to a non-blacklisted IPv4 address
# ----------------------------------------------------------
# Make the request
d = cl.post_json("fine:8008", "foo/bar", timeout=10000)
# Nothing has happened yet
self.assertNoResult(d)
# Move the reactor forwards
self.pump(1)
# Check that it was able to resolve the address
clients = self.reactor.tcpClients
self.assertNotEqual(len(clients), 0)
# Connection will still fail as this IP address does not resolve to anything
f = self.failureResultOf(d, RequestSendFailed)
self.assertIsInstance(f.value.inner_exception, ConnectingCancelledError)
def test_client_gets_headers(self):
"""
Once the client gets the headers, _request returns successfully.