From c0484d628dc3e39cbb3e28294b576d7ef88b098e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20G=C3=B3mez=20Garc=C3=ADa?= Date: Fri, 21 Jul 2023 18:27:55 +0200 Subject: [PATCH] Included "mac" as a informative field for registered servers --- server/src/uds/REST/handlers.py | 2 +- server/src/uds/REST/methods/actor_v3.py | 43 +++++++++---------- .../{servers.py => registered_servers.py} | 7 ++- server/src/uds/REST/methods/tunnel.py | 4 +- server/src/uds/models/registered_servers.py | 27 +++++++++--- 5 files changed, 49 insertions(+), 34 deletions(-) rename server/src/uds/REST/methods/{servers.py => registered_servers.py} (91%) diff --git a/server/src/uds/REST/handlers.py b/server/src/uds/REST/handlers.py index fe5cad009..8486a537f 100644 --- a/server/src/uds/REST/handlers.py +++ b/server/src/uds/REST/handlers.py @@ -86,7 +86,7 @@ class Handler: _request: 'ExtendedHttpRequestWithUser' # It's a modified HttpRequest _path: str _operation: str - _params: typing.Any # This is a deserliazied object from request. Can be anything as 'a' or {'a': 1} or .... + _params: typing.MutableMapping[str, typing.Any] # This is a deserliazied object from request. Can be anything as 'a' or {'a': 1} or .... _args: typing.Tuple[ str, ... ] # This are the "path" split by /, that is, the REST invocation arguments diff --git a/server/src/uds/REST/methods/actor_v3.py b/server/src/uds/REST/methods/actor_v3.py index 48cb4c415..4866ec70e 100644 --- a/server/src/uds/REST/methods/actor_v3.py +++ b/server/src/uds/REST/methods/actor_v3.py @@ -276,31 +276,28 @@ class Register(ActorV3Action): name = 'register' def post(self) -> typing.MutableMapping[str, typing.Any]: - actorToken: RegisteredServers # If already exists a token for this MAC, return it instead of creating a new one, and update the information... # Look for a token for this mac. mac is "inside" data, so we must filter first by type and then ensure mac is inside data # and mac is the requested one found = False - for actorToken in RegisteredServers.objects.filter(kind=RegisteredServers.ServerType.ACTOR): - if actorToken.data and actorToken.data.get('mac', '') == self._params['mac']: - # Update parameters - actorToken.username = self._user.pretty_name - actorToken.ip_from = self._request.ip - actorToken.ip = self._params['ip'] - actorToken.hostname = self._params['hostname'] - actorToken.data = { # type: ignore - 'mac': self._params['mac'], - 'pre_command': self._params['pre_command'], - 'post_command': self._params['post_command'], - 'run_once_command': self._params['run_once_command'], - 'log_level': self._params['log_level'], - 'custom': self._params.get('custom', ''), - } - actorToken.stamp = getSqlDatetime() - actorToken.save() - logger.info('Registered actor %s', self._params) - found = True - break + actorToken: typing.Optional[RegisteredServers] = RegisteredServers.objects.filter(kind=RegisteredServers.ServerType.ACTOR, mac=self._params['mac']).first() + if actorToken: + # Update parameters + actorToken.username = self._user.pretty_name + actorToken.ip_from = self._request.ip + actorToken.ip = self._params['ip'] + actorToken.hostname = self._params['hostname'] + actorToken.data = { # type: ignore + 'pre_command': self._params['pre_command'], + 'post_command': self._params['post_command'], + 'run_once_command': self._params['run_once_command'], + 'log_level': self._params['log_level'], + 'custom': self._params.get('custom', ''), + } + actorToken.stamp = getSqlDatetime() + actorToken.save() + logger.info('Registered actor %s', self._params) + found = True if not found: kwargs = { @@ -310,16 +307,16 @@ class Register(ActorV3Action): 'ip_version': self._request.ip_version, 'hostname': self._params['hostname'], 'data': { # type: ignore - 'mac': self._params['mac'], 'pre_command': self._params['pre_command'], 'post_command': self._params['post_command'], 'run_once_command': self._params['run_once_command'], 'log_level': self._params['log_level'], 'custom': self._params.get('custom', ''), }, - 'token': secrets.token_urlsafe(36), + 'token': RegisteredServers.create_token(), 'kind': RegisteredServers.ServerType.ACTOR, 'os_type': self._params.get('os', KnownOS.UNKNOWN.os_name()), + 'mac': self._params['mac'], 'stamp': getSqlDatetime(), } diff --git a/server/src/uds/REST/methods/servers.py b/server/src/uds/REST/methods/registered_servers.py similarity index 91% rename from server/src/uds/REST/methods/servers.py rename to server/src/uds/REST/methods/registered_servers.py index bd8f01416..610ab8549 100644 --- a/server/src/uds/REST/methods/servers.py +++ b/server/src/uds/REST/methods/registered_servers.py @@ -55,7 +55,9 @@ class ServerRegister(Handler): serverToken: models.RegisteredServers now = getSqlDatetimeAsUnix() try: - # If already exists a token for this MAC, return it instead of creating a new one, and update the information... + # If already exists a token for this, return it instead of creating a new one, and update the information... + # Note that we use IP and HOSTNAME to identify the server, so if any of them changes, a new token will be created + # MAC is just informative, and data is used to store any other information that may be needed serverToken = models.RegisteredServers.objects.get( ip=self._params['ip'], hostname=self._params['hostname'] ) @@ -72,10 +74,11 @@ class ServerRegister(Handler): ip_from=self._request.ip, ip=self._params['ip'], hostname=self._params['hostname'], - token=secrets.token_urlsafe(36), + token=models.RegisteredServers.create_token(), stamp=getSqlDatetime(), kind=self._params['type'], os_type=typing.cast(str, self._params.get('os', KnownOS.UNKNOWN.os_name())).lower(), + mac=self._params.get('mac', models.RegisteredServers.MAC_UNKNOWN), data=self._params.get('data', None), ) except Exception as e: diff --git a/server/src/uds/REST/methods/tunnel.py b/server/src/uds/REST/methods/tunnel.py index 219cc434a..ebf6c2211 100644 --- a/server/src/uds/REST/methods/tunnel.py +++ b/server/src/uds/REST/methods/tunnel.py @@ -41,7 +41,7 @@ from uds.core.auths.auth import isTrustedSource from uds.core.util import log, net from uds.core.util.stats import events -from .servers import ServerRegister +from .registered_servers import ServerRegister logger = logging.getLogger(__name__) @@ -162,5 +162,5 @@ class TunnelRegister(ServerRegister): # Just a compatibility method for old tunnel servers def post(self) -> typing.MutableMapping[str, typing.Any]: self._params['type'] = models.RegisteredServers.ServerType.TUNNEL - self._params['os'] = KnownOS.LINUX.os_name() # Legacy tunnels are always linux + self._params['os'] = self._params.get('os', KnownOS.LINUX.os_name()) # Legacy tunnels are always linux return super().post() diff --git a/server/src/uds/models/registered_servers.py b/server/src/uds/models/registered_servers.py index 1525d302f..94d25e82a 100644 --- a/server/src/uds/models/registered_servers.py +++ b/server/src/uds/models/registered_servers.py @@ -30,6 +30,8 @@ Author: Adolfo Gómez, dkmaster at dkmon dot com ''' import typing import enum +import secrets + from uds.core.util.os_detector import KnownOS from django.db import models @@ -37,6 +39,7 @@ from uds.core.util.request import ExtendedHttpRequest from .consts import MAX_DNS_NAME_LENGTH, MAX_IPV6_LENGTH + class RegisteredServers(models.Model): """ UDS Registered Servers @@ -54,13 +57,17 @@ class RegisteredServers(models.Model): If server is Other, but not Tunnel, it will be allowed to access API, but will not be able to create tunnels. """ - class ServerType(enum.IntFlag): + + class ServerType(enum.IntEnum): TUNNEL = 1 ACTOR = 2 + APP_SERVER = 3 OTHER = 99 def as_str(self) -> str: return self.name.lower() # type: ignore + + MAC_UNKNOWN = '00:00:00:00:00:00' username = models.CharField(max_length=128) ip_from = models.CharField(max_length=MAX_IPV6_LENGTH) @@ -72,18 +79,26 @@ class RegisteredServers(models.Model): token = models.CharField(max_length=48, db_index=True, unique=True) stamp = models.DateTimeField() # Date creation or validation of this entry - kind = models.IntegerField(default=ServerType.TUNNEL.value) # Defaults to tunnel server, so we can migrate from previous versions - os_type = models.CharField(max_length=32, default=KnownOS.UNKNOWN.os_name()) # os type of server (linux, windows, etc..) + # Type of server. Defaults to tunnel, so we can migrate from previous versions + kind = models.IntegerField(default=ServerType.TUNNEL.value) + # os type of server (linux, windows, etc..) + os_type = models.CharField(max_length=32, default=KnownOS.UNKNOWN.os_name()) + # mac address of registered server, if any. Important for actor servers mainly, informative for others + mac = models.CharField(max_length=32, default=MAC_UNKNOWN, db_index=True) + # Extra data, for custom server type use (i.e. actor keeps command related data here) data = models.JSONField(null=True, blank=True, default=None) class Meta: # pylint: disable=too-few-public-methods app_label = 'uds' @staticmethod - def validateToken( - token: str, request: typing.Optional[ExtendedHttpRequest] = None - ) -> bool: + def create_token() -> str: + return secrets.token_urlsafe(36) + + @staticmethod + def validateToken(token: str, request: typing.Optional[ExtendedHttpRequest] = None) -> bool: + # Ensure tiken is composed of try: tt = RegisteredServers.objects.get(token=token) # We could check the request ip here