Merge pull request #10548 from SomberNight/202603_lockdown_rpcserver
in GUI mode, only start a limited minimal RPC server
This commit is contained in:
+16
-7
@@ -287,25 +287,31 @@ class AuthenticatedServer(Logger):
|
|||||||
|
|
||||||
class CommandsServer(AuthenticatedServer):
|
class CommandsServer(AuthenticatedServer):
|
||||||
|
|
||||||
def __init__(self, daemon: 'Daemon', fd):
|
def __init__(self, daemon: 'Daemon', fd, *, only_minimal_jsonrpc: bool):
|
||||||
rpc_user, rpc_password = get_rpc_credentials(daemon.config)
|
rpc_user, rpc_password = get_rpc_credentials(daemon.config)
|
||||||
AuthenticatedServer.__init__(self, rpc_user, rpc_password)
|
AuthenticatedServer.__init__(self, rpc_user, rpc_password)
|
||||||
self.daemon = daemon
|
self.daemon = daemon
|
||||||
self.fd = fd
|
self.fd = fd
|
||||||
|
self._only_minimal_jsonrpc = only_minimal_jsonrpc
|
||||||
self.config = daemon.config
|
self.config = daemon.config
|
||||||
sockettype = self.config.RPC_SOCKET_TYPE
|
sockettype = self.config.RPC_SOCKET_TYPE
|
||||||
self.socktype = sockettype if sockettype != 'auto' else get_rpcsock_default_type(self.config)
|
self.socktype = sockettype if sockettype != 'auto' else get_rpcsock_default_type(self.config)
|
||||||
self.sockpath = self.config.RPC_SOCKET_FILEPATH or get_rpcsock_defaultpath(self.config)
|
self.sockpath = self.config.RPC_SOCKET_FILEPATH or get_rpcsock_defaultpath(self.config)
|
||||||
self.host = self.config.RPC_HOST
|
self.host = self.config.RPC_HOST
|
||||||
self.port = self.config.RPC_PORT
|
self.port = self.config.RPC_PORT
|
||||||
|
self.cmd_runner = Commands(config=self.config, network=self.daemon.network, daemon=self.daemon)
|
||||||
self.app = web.Application()
|
self.app = web.Application()
|
||||||
self.app.router.add_post("/", self.handle)
|
self.app.router.add_post("/", self.handle)
|
||||||
|
# First add always-enabled commands that are also available for "minimal" rpc server.
|
||||||
|
# - "ping" RPC is needed for the lockfile fd to work.
|
||||||
self.register_method('ping', self.ping)
|
self.register_method('ping', self.ping)
|
||||||
|
# - "gui" RPC is needed for URI handling. (TODO restrict further: disallow opening arbitrary file paths)
|
||||||
self.register_method('gui', self.gui)
|
self.register_method('gui', self.gui)
|
||||||
self.cmd_runner = Commands(config=self.config, network=self.daemon.network, daemon=self.daemon)
|
# Add other commands:
|
||||||
for cmdname in known_commands:
|
if not only_minimal_jsonrpc:
|
||||||
self.register_method(cmdname, getattr(self.cmd_runner, cmdname))
|
for cmdname in known_commands:
|
||||||
self.register_method('run_cmdline', self.run_cmdline)
|
self.register_method(cmdname, getattr(self.cmd_runner, cmdname))
|
||||||
|
self.register_method('run_cmdline', self.run_cmdline)
|
||||||
|
|
||||||
def _socket_config_str(self) -> str:
|
def _socket_config_str(self) -> str:
|
||||||
if self.socktype == 'unix':
|
if self.socktype == 'unix':
|
||||||
@@ -343,7 +349,9 @@ class CommandsServer(AuthenticatedServer):
|
|||||||
raise Exception(f"impossible socktype ({self.socktype!r})")
|
raise Exception(f"impossible socktype ({self.socktype!r})")
|
||||||
os.write(self.fd, bytes(repr((self.socktype, addr, time.time())), 'utf8'))
|
os.write(self.fd, bytes(repr((self.socktype, addr, time.time())), 'utf8'))
|
||||||
os.close(self.fd)
|
os.close(self.fd)
|
||||||
self.logger.info(f"now running and listening. socktype={self.socktype}, addr={addr}")
|
self.logger.info(
|
||||||
|
f"now running and listening. socktype={self.socktype}, addr={addr}. "
|
||||||
|
f"only_minimal_jsonrpc={self._only_minimal_jsonrpc}")
|
||||||
|
|
||||||
async def ping(self):
|
async def ping(self):
|
||||||
return True
|
return True
|
||||||
@@ -401,6 +409,7 @@ class Daemon(Logger):
|
|||||||
fd=None,
|
fd=None,
|
||||||
*,
|
*,
|
||||||
listen_jsonrpc: bool = True,
|
listen_jsonrpc: bool = True,
|
||||||
|
only_minimal_jsonrpc: bool = True,
|
||||||
start_network: bool = True, # setting to False allows customising network settings before starting it
|
start_network: bool = True, # setting to False allows customising network settings before starting it
|
||||||
):
|
):
|
||||||
Logger.__init__(self)
|
Logger.__init__(self)
|
||||||
@@ -430,7 +439,7 @@ class Daemon(Logger):
|
|||||||
# Setup commands server
|
# Setup commands server
|
||||||
self.commands_server = None
|
self.commands_server = None
|
||||||
if listen_jsonrpc:
|
if listen_jsonrpc:
|
||||||
self.commands_server = CommandsServer(self, fd)
|
self.commands_server = CommandsServer(self, fd, only_minimal_jsonrpc=only_minimal_jsonrpc)
|
||||||
asyncio.run_coroutine_threadsafe(self.taskgroup.spawn(self.commands_server.run()), self.asyncio_loop)
|
asyncio.run_coroutine_threadsafe(self.taskgroup.spawn(self.commands_server.run()), self.asyncio_loop)
|
||||||
|
|
||||||
@log_exceptions
|
@log_exceptions
|
||||||
|
|||||||
@@ -439,6 +439,14 @@ class ElectrumGui(BaseElectrumGui, Logger):
|
|||||||
window.activateWindow()
|
window.activateWindow()
|
||||||
if uri:
|
if uri:
|
||||||
window.show_send_tab()
|
window.show_send_tab()
|
||||||
|
# Handle URI defensively - local attacker with access to RPC server and config file could get here:
|
||||||
|
# - tell user something happened
|
||||||
|
window.notify(_("Updated 'Pay To' field to handle external URI"))
|
||||||
|
# - clear all fields in Send tab:
|
||||||
|
# - perhaps user was just filling out the fields, trying to make another payment.
|
||||||
|
# e.g. if the given URI does not have an amount, we should clear the amount field
|
||||||
|
window.send_tab.do_clear()
|
||||||
|
# - update "Pay To" field (and maybe others)
|
||||||
window.send_tab.set_payment_identifier(uri)
|
window.send_tab.set_payment_identifier(uri)
|
||||||
return window
|
return window
|
||||||
|
|
||||||
|
|||||||
+6
-3
@@ -132,6 +132,7 @@ from electrum.commands import get_parser, get_simple_parser, known_commands, Com
|
|||||||
from electrum import daemon
|
from electrum import daemon
|
||||||
from electrum.util import create_and_start_event_loop, UserFacingException, JsonRPCError
|
from electrum.util import create_and_start_event_loop, UserFacingException, JsonRPCError
|
||||||
from electrum.i18n import set_language
|
from electrum.i18n import set_language
|
||||||
|
from electrum import constants
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import threading
|
import threading
|
||||||
@@ -518,7 +519,9 @@ def handle_cmd(*, cmdname: str, config: 'SimpleConfig', config_options: dict):
|
|||||||
configure_logging(config)
|
configure_logging(config)
|
||||||
fd = daemon.get_file_descriptor(config)
|
fd = daemon.get_file_descriptor(config)
|
||||||
if fd is not None:
|
if fd is not None:
|
||||||
d = daemon.Daemon(config, fd, start_network=False)
|
# When running in GUI mode, only start a limited minimal RPC server, to limit attack surface.
|
||||||
|
only_minimal_jsonrpc = not constants.net.TESTNET
|
||||||
|
d = daemon.Daemon(config, fd, start_network=False, only_minimal_jsonrpc=only_minimal_jsonrpc)
|
||||||
try:
|
try:
|
||||||
d.run_gui()
|
d.run_gui()
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
@@ -544,7 +547,7 @@ def handle_cmd(*, cmdname: str, config: 'SimpleConfig', config_options: dict):
|
|||||||
fd = daemon.get_file_descriptor(config)
|
fd = daemon.get_file_descriptor(config)
|
||||||
if fd is not None:
|
if fd is not None:
|
||||||
# run daemon
|
# run daemon
|
||||||
d = daemon.Daemon(config, fd)
|
d = daemon.Daemon(config, fd, only_minimal_jsonrpc=False)
|
||||||
d.run_daemon()
|
d.run_daemon()
|
||||||
sys_exit(0)
|
sys_exit(0)
|
||||||
else:
|
else:
|
||||||
@@ -586,7 +589,7 @@ def handle_cmd(*, cmdname: str, config: 'SimpleConfig', config_options: dict):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logger.exception("error running command (with daemon)")
|
_logger.exception("error running command (with daemon)")
|
||||||
sys_exit(1)
|
sys_exit(1)
|
||||||
else:
|
else: # --offline
|
||||||
if cmd.requires_network:
|
if cmd.requires_network:
|
||||||
print_msg("This command cannot be run offline")
|
print_msg("This command cannot be run offline")
|
||||||
sys_exit(1)
|
sys_exit(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user