forked from LBRYCommunity/lbry-sdk
Allow 0.0.0.0 for api interface
For a host to be able to access the daemon running inside a docker container the damon needs to be listening to 0.0.0.0 - move the API_INTERFACE setting to the adjustablesettings - check the port matches as well as the interface
This commit is contained in:
parent
9140c95a0c
commit
6a8789050c
4 changed files with 97 additions and 9 deletions
|
@ -170,7 +170,12 @@ ENVIRONMENT = Env(
|
||||||
#
|
#
|
||||||
# TODO: writing json on the cmd line is a pain, come up with a nicer
|
# TODO: writing json on the cmd line is a pain, come up with a nicer
|
||||||
# parser for this data structure. (maybe MAX_KEY_FEE=USD:25
|
# parser for this data structure. (maybe MAX_KEY_FEE=USD:25
|
||||||
max_key_fee=(json.loads, {'USD': {'amount': 25.0, 'address': ''}})
|
max_key_fee=(json.loads, {'USD': {'amount': 25.0, 'address': ''}}),
|
||||||
|
# Changing this value is not-advised as it could potentially
|
||||||
|
# expose the lbrynet daemon to the outside world which would
|
||||||
|
# give an attacker access to your wallet and you could lose
|
||||||
|
# all of your credits.
|
||||||
|
API_INTERFACE=(str, "localhost"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -205,7 +210,6 @@ class ApplicationSettings(Settings):
|
||||||
self.LOG_FILE_NAME = "lbrynet.log"
|
self.LOG_FILE_NAME = "lbrynet.log"
|
||||||
self.LOG_POST_URL = "https://lbry.io/log-upload"
|
self.LOG_POST_URL = "https://lbry.io/log-upload"
|
||||||
self.CRYPTSD_FILE_EXTENSION = ".cryptsd"
|
self.CRYPTSD_FILE_EXTENSION = ".cryptsd"
|
||||||
self.API_INTERFACE = "localhost"
|
|
||||||
self.API_ADDRESS = "lbryapi"
|
self.API_ADDRESS = "lbryapi"
|
||||||
self.ICON_PATH = "icons" if platform is WINDOWS else "app.icns"
|
self.ICON_PATH = "icons" if platform is WINDOWS else "app.icns"
|
||||||
self.APP_NAME = "LBRY"
|
self.APP_NAME = "LBRY"
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
|
import urlparse
|
||||||
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from zope.interface import implements
|
from zope.interface import implements
|
||||||
from twisted.web import server, resource
|
from twisted.web import server, resource
|
||||||
|
@ -219,16 +221,37 @@ class AuthJSONRPCServer(AuthorizedBase):
|
||||||
request.finish()
|
request.finish()
|
||||||
|
|
||||||
def _check_headers(self, request):
|
def _check_headers(self, request):
|
||||||
origin = request.getHeader("Origin")
|
return (
|
||||||
referer = request.getHeader("Referer")
|
self._check_header_source(request, 'Origin') and
|
||||||
if origin not in [None, settings.ORIGIN]:
|
self._check_header_source(request, 'Referer'))
|
||||||
log.warning("Attempted api call from %s", origin)
|
|
||||||
return False
|
def _check_header_source(self, request, header):
|
||||||
if referer is not None and not referer.startswith(settings.REFERER):
|
"""Check if the source of the request is allowed based on the header value."""
|
||||||
log.warning("Attempted api call from %s", referer)
|
source = request.getHeader(header)
|
||||||
|
if not self._check_source_of_request(source):
|
||||||
|
log.warning("Attempted api call from invalid %s: %s", header, source)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _check_source_of_request(self, source):
|
||||||
|
if source is None:
|
||||||
|
return True
|
||||||
|
if settings.API_INTERFACE == '0.0.0.0':
|
||||||
|
return True
|
||||||
|
server, port = self.get_server_port(source)
|
||||||
|
return (
|
||||||
|
server == settings.API_INTERFACE and
|
||||||
|
port == settings.api_port)
|
||||||
|
|
||||||
|
def get_server_port(self, origin):
|
||||||
|
parsed = urlparse.urlparse(origin)
|
||||||
|
server_port = parsed.netloc.split(':')
|
||||||
|
assert len(server_port) <= 2
|
||||||
|
if len(server_port) == 2:
|
||||||
|
return server_port[0], int(server_port[1])
|
||||||
|
else:
|
||||||
|
return server_port[0], 80
|
||||||
|
|
||||||
def _check_function_path(self, function_path):
|
def _check_function_path(self, function_path):
|
||||||
if function_path not in self.callable_methods:
|
if function_path not in self.callable_methods:
|
||||||
log.warning("Unknown method: %s", function_path)
|
log.warning("Unknown method: %s", function_path)
|
||||||
|
|
0
tests/unit/lbrynet_daemon/auth/__init__.py
Normal file
0
tests/unit/lbrynet_daemon/auth/__init__.py
Normal file
61
tests/unit/lbrynet_daemon/auth/test_server.py
Normal file
61
tests/unit/lbrynet_daemon/auth/test_server.py
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import mock
|
||||||
|
import requests
|
||||||
|
from twisted.trial import unittest
|
||||||
|
|
||||||
|
from lbrynet import conf
|
||||||
|
from lbrynet.lbrynet_daemon.auth import server
|
||||||
|
|
||||||
|
|
||||||
|
class AuthJSONRPCServerTest(unittest.TestCase):
|
||||||
|
# TODO: move to using a base class for tests
|
||||||
|
# and add useful general utilities like this
|
||||||
|
# onto it.
|
||||||
|
def setUp(self):
|
||||||
|
self.server = server.AuthJSONRPCServer(False)
|
||||||
|
|
||||||
|
def _set_setting(self, attr, value):
|
||||||
|
original = getattr(conf.settings, attr)
|
||||||
|
setattr(conf.settings, attr, value)
|
||||||
|
self.addCleanup(lambda: setattr(conf.settings, attr, original))
|
||||||
|
|
||||||
|
def test_get_server_port(self):
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
('example.com', 80), self.server.get_server_port('http://example.com'))
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
('example.com', 1234), self.server.get_server_port('http://example.com:1234'))
|
||||||
|
|
||||||
|
def test_foreign_origin_is_rejected(self):
|
||||||
|
request = mock.Mock(['getHeader'])
|
||||||
|
request.getHeader = mock.Mock(return_value='http://example.com')
|
||||||
|
self.assertFalse(self.server._check_header_source(request, 'Origin'))
|
||||||
|
|
||||||
|
def test_wrong_port_is_rejected(self):
|
||||||
|
self._set_setting('api_port', 1234)
|
||||||
|
request = mock.Mock(['getHeader'])
|
||||||
|
request.getHeader = mock.Mock(return_value='http://localhost:9999')
|
||||||
|
self.assertFalse(self.server._check_header_source(request, 'Origin'))
|
||||||
|
|
||||||
|
def test_matching_origin_is_allowed(self):
|
||||||
|
self._set_setting('API_INTERFACE', 'example.com')
|
||||||
|
self._set_setting('api_port', 1234)
|
||||||
|
request = mock.Mock(['getHeader'])
|
||||||
|
request.getHeader = mock.Mock(return_value='http://example.com:1234')
|
||||||
|
self.assertTrue(self.server._check_header_source(request, 'Origin'))
|
||||||
|
|
||||||
|
def test_any_origin_is_allowed(self):
|
||||||
|
self._set_setting('API_INTERFACE', '0.0.0.0')
|
||||||
|
self._set_setting('api_port', 80)
|
||||||
|
request = mock.Mock(['getHeader'])
|
||||||
|
request.getHeader = mock.Mock(return_value='http://example.com')
|
||||||
|
self.assertTrue(self.server._check_header_source(request, 'Origin'))
|
||||||
|
request = mock.Mock(['getHeader'])
|
||||||
|
request.getHeader = mock.Mock(return_value='http://another-example.com')
|
||||||
|
self.assertTrue(self.server._check_header_source(request, 'Origin'))
|
||||||
|
|
||||||
|
def test_matching_referer_is_allowed(self):
|
||||||
|
self._set_setting('API_INTERFACE', 'the_api')
|
||||||
|
self._set_setting('api_port', 1111)
|
||||||
|
request = mock.Mock(['getHeader'])
|
||||||
|
request.getHeader = mock.Mock(return_value='http://the_api:1111?settings')
|
||||||
|
self.assertTrue(self.server._check_header_source(request, 'Referer'))
|
||||||
|
request.getHeader.assert_called_with('Referer')
|
Loading…
Reference in a new issue