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 a4540c35d425e..f7c18d82dceca 100644 --- a/src/middlewared/middlewared/api/base/server/ws_handler/rpc.py +++ b/src/middlewared/middlewared/api/base/server/ws_handler/rpc.py @@ -273,13 +273,24 @@ 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: + if not app.private_methods and method.private and not self._can_call_private_methods(app): # FIXME: Eventually, prohibit this - self.middleware.logger.warning("Private method %r called on a connection without private_methods " + self.middleware.logger.warning("Private method %r called on a connection without private_methods " "enabled", method.name) asyncio.ensure_future(self.process_method_call(app, id_, method, message.get("params", []))) + def _can_call_private_methods(self, app: RpcWebSocketApp): + if app.origin.uid == 33: + # Proxied HexOS calls + return False + + if app.origin.loginuid() is None: + # System-initiated calls to `midclt` + return True + + return False + async def process_method_call(self, app: RpcWebSocketApp, id_: Any, method: Method, params: list): try: async with app.softhardsemaphore: diff --git a/src/middlewared/middlewared/utils/origin.py b/src/middlewared/middlewared/utils/origin.py index dbe039318d0a2..8ccaeb7eb91ef 100644 --- a/src/middlewared/middlewared/utils/origin.py +++ b/src/middlewared/middlewared/utils/origin.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from functools import cached_property from socket import AF_INET, AF_INET6, AF_UNIX, SO_PEERCRED, SOL_SOCKET from struct import calcsize, unpack @@ -97,6 +98,21 @@ def is_ha_connection(self) -> bool: self.rem_addr and self.rem_addr in HA_HEARTBEAT_IPS ) + def loginuid(self) -> int | None: + if self.pid is None: + return None + + try: + with open(f"/proc/{self.pid}/loginuid") as f: + loginuid = int(f.read()) + except (FileNotFoundError, ValueError): + return None + + if loginuid == 4294967295: + return None + + return loginuid + def get_tcp_ip_info(sock, request) -> tuple: # All API connections are terminated by nginx reverse