From 5cae533daeaff2c678372ff2bdd2aed6e1c59a33 Mon Sep 17 00:00:00 2001 From: themylogin Date: Mon, 9 Dec 2024 13:53:32 +0100 Subject: [PATCH] Log when private methods are called via websocket connection --- src/middlewared/middlewared/api/base/server/app.py | 1 + src/middlewared/middlewared/api/base/server/method.py | 4 ++++ .../middlewared/api/base/server/ws_handler/rpc.py | 4 ++++ src/middlewared/middlewared/api/v25_04_0/core.py | 1 + src/middlewared/middlewared/plugins/failover_/remote.py | 2 +- src/middlewared/middlewared/plugins/jbof/redfish/client.py | 3 ++- src/middlewared/middlewared/plugins/zettarepl.py | 5 ++--- src/middlewared/middlewared/service/core_service.py | 2 ++ .../middlewared/test/integration/utils/client.py | 4 ++-- src/middlewared/middlewared/worker.py | 7 ++++--- 10 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/middlewared/middlewared/api/base/server/app.py b/src/middlewared/middlewared/api/base/server/app.py index 2a48949d696dc..c272a61c8243a 100644 --- a/src/middlewared/middlewared/api/base/server/app.py +++ b/src/middlewared/middlewared/api/base/server/app.py @@ -14,6 +14,7 @@ def __init__(self, origin: ConnectionOrigin): self.authenticated = False self.authentication_context: AuthenticationContext = AuthenticationContext() self.authenticated_credentials: SessionManagerCredentials | None = None + self.private_methods = False self.py_exceptions = False self.websocket = False self.rest = False diff --git a/src/middlewared/middlewared/api/base/server/method.py b/src/middlewared/middlewared/api/base/server/method.py index 53e344a3b29d1..7c621e5d268ae 100644 --- a/src/middlewared/middlewared/api/base/server/method.py +++ b/src/middlewared/middlewared/api/base/server/method.py @@ -22,6 +22,10 @@ def __init__(self, middleware: "Middleware", name: str): self.name = name self.serviceobj, self.methodobj = self.middleware.get_method(self.name) + @property + def private(self): + return getattr(self.methodobj, "_private", False) + async def call(self, app: "RpcWebSocketApp", params: list): """ Calls the method in the context of a given `app`. diff --git a/src/middlewared/middlewared/api/base/server/ws_handler/rpc.py b/src/middlewared/middlewared/api/base/server/ws_handler/rpc.py index d89f1e721faae..d7e4064138afa 100644 --- a/src/middlewared/middlewared/api/base/server/ws_handler/rpc.py +++ b/src/middlewared/middlewared/api/base/server/ws_handler/rpc.py @@ -273,6 +273,10 @@ async def process_message(self, app: RpcWebSocketApp, message: Any): if method is None: app.send_error(id_, JSONRPCError.METHOD_NOT_FOUND.value, "Method does not exist") return + if not app.private_methods and method.private: + # FIXME: Eventually, prohibit this + self.middleware.logger.warning("Private method %r called on a connection without private method call " + "enabled", method.name) asyncio.ensure_future(self.process_method_call(app, id_, method, message.get("params", []))) diff --git a/src/middlewared/middlewared/api/v25_04_0/core.py b/src/middlewared/middlewared/api/v25_04_0/core.py index 4b58ffc83ad2b..2d37b03ff8920 100644 --- a/src/middlewared/middlewared/api/v25_04_0/core.py +++ b/src/middlewared/middlewared/api/v25_04_0/core.py @@ -23,6 +23,7 @@ class CorePingResult(BaseModel): class CoreSetOptionsOptions(BaseModel, metaclass=ForUpdateMetaclass): + private_methods: bool py_exceptions: bool diff --git a/src/middlewared/middlewared/plugins/failover_/remote.py b/src/middlewared/middlewared/plugins/failover_/remote.py index 2a0b35d143c17..82e0f08324b36 100644 --- a/src/middlewared/middlewared/plugins/failover_/remote.py +++ b/src/middlewared/middlewared/plugins/failover_/remote.py @@ -65,7 +65,7 @@ def connect_and_wait(self, *, legacy=False): url = f'ws://{self.remote_ip}:6000/websocket' try: - with Client(url, reserved_ports=True) as c: + with Client(url, reserved_ports=True, private_methods=True) as c: self.client = c with self._subscribe_lock: self.connected.set() diff --git a/src/middlewared/middlewared/plugins/jbof/redfish/client.py b/src/middlewared/middlewared/plugins/jbof/redfish/client.py index 8e814ae954ec3..dc4294e334c9d 100644 --- a/src/middlewared/middlewared/plugins/jbof/redfish/client.py +++ b/src/middlewared/middlewared/plugins/jbof/redfish/client.py @@ -455,7 +455,8 @@ async def cache_get(cls, uuid, jbof_query=None): if jbof_query is not None: jbofs = jbof_query else: - with Client(f'ws+unix://{MIDDLEWARE_RUN_DIR}/middlewared-internal.sock', py_exceptions=True) as c: + with Client(f'ws+unix://{MIDDLEWARE_RUN_DIR}/middlewared-internal.sock', private_methods=True, + py_exceptions=True) as c: jbofs = c.call('jbof.query', filters) for jbof in filter_list(jbofs, filters, options): diff --git a/src/middlewared/middlewared/plugins/zettarepl.py b/src/middlewared/middlewared/plugins/zettarepl.py index 9b75c5621e1e4..a3aaa264ef8a0 100644 --- a/src/middlewared/middlewared/plugins/zettarepl.py +++ b/src/middlewared/middlewared/plugins/zettarepl.py @@ -198,9 +198,8 @@ def _observer(self, message): task_id = int(message.task_id.split("_")[-1]) if isinstance(message, PeriodicSnapshotTaskStart): - with Client() as c: + with Client(private_methods=True) as c: context = None - vm_context = None if begin_context := c.call("vmware.periodic_snapshot_task_begin", task_id): context = c.call("vmware.periodic_snapshot_task_proceed", begin_context, job=True) if vm_context := c.call("vm.periodic_snapshot_task_begin", task_id): @@ -219,7 +218,7 @@ def _observer(self, message): context = self.vmware_contexts.pop(task_id, None) vm_context = self.vm_contexts.pop(task_id, None) if context or vm_context: - with Client() as c: + with Client(private_methods=True) as c: if context: c.call("vmware.periodic_snapshot_task_end", context, job=True) if vm_context: diff --git a/src/middlewared/middlewared/service/core_service.py b/src/middlewared/middlewared/service/core_service.py index 8f3144f99c12c..fcfc0a62ee82f 100644 --- a/src/middlewared/middlewared/service/core_service.py +++ b/src/middlewared/middlewared/service/core_service.py @@ -885,6 +885,8 @@ def _cli_args_descriptions(self, doc, names): @api_method(CoreSetOptionsArgs, CoreSetOptionsResult, rate_limit=False) @pass_app() async def set_options(self, app, options): + if "private_methods" in options: + app.private_methods = options["private_methods"] if "py_exceptions" in options: app.py_exceptions = options["py_exceptions"] diff --git a/src/middlewared/middlewared/test/integration/utils/client.py b/src/middlewared/middlewared/test/integration/utils/client.py index ac98bcc20f7aa..bb79a538695fb 100644 --- a/src/middlewared/middlewared/test/integration/utils/client.py +++ b/src/middlewared/middlewared/test/integration/utils/client.py @@ -116,7 +116,7 @@ def client(self) -> Client: raise RuntimeError('IP is not set') uri = host_websocket_uri(addr) - cl = Client(uri, py_exceptions=True, log_py_exceptions=True) + cl = Client(uri, private_methods=True, py_exceptions=True, log_py_exceptions=True) try: resp = cl.call('auth.login_ex', { 'mechanism': 'PASSWORD_PLAIN', @@ -166,7 +166,7 @@ def client(*, auth=undefined, auth_required=True, py_exceptions=True, log_py_exc uri = host_websocket_uri(host_ip) try: - with Client(uri, py_exceptions=py_exceptions, log_py_exceptions=log_py_exceptions) as c: + with Client(uri, private_methods=True, py_exceptions=py_exceptions, log_py_exceptions=log_py_exceptions) as c: if auth is not None: auth_req = { "mechanism": "PASSWORD_PLAIN", diff --git a/src/middlewared/middlewared/worker.py b/src/middlewared/middlewared/worker.py index 43d14411d6ae2..9e0a954a4b615 100755 --- a/src/middlewared/middlewared/worker.py +++ b/src/middlewared/middlewared/worker.py @@ -31,7 +31,8 @@ def __init__(self): def _call(self, name, serviceobj, methodobj, params=None, app=None, pipes=None, job=None): try: - with Client(f'ws+unix://{MIDDLEWARE_RUN_DIR}/middlewared-internal.sock', py_exceptions=True) as c: + with Client(f'ws+unix://{MIDDLEWARE_RUN_DIR}/middlewared-internal.sock', private_methods=True, + py_exceptions=True) as c: self.client = c job_options = getattr(methodobj, '_job', None) if job and job_options: @@ -82,7 +83,7 @@ def get_events(self): return [] def send_event(self, name, event_type, **kwargs): - with Client(py_exceptions=True) as c: + with Client(private_methods=True, py_exceptions=True) as c: return c.call('core.event_send', name, event_type, kwargs) @@ -121,7 +122,7 @@ def main_worker(*call_args): def receive_events(): - c = Client(f'ws+unix://{MIDDLEWARE_RUN_DIR}/middlewared-internal.sock', py_exceptions=True) + c = Client(f'ws+unix://{MIDDLEWARE_RUN_DIR}/middlewared-internal.sock', private_methods=True, py_exceptions=True) c.subscribe('core.environ', lambda *args, **kwargs: environ_update(kwargs['fields'])) environ_update(c.call('core.environ'))