From 32c88e15431015ed44f582a2ad4cd9a01de5f987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20G=C3=B3mez=20Garc=C3=ADa?= Date: Wed, 26 Jul 2023 16:47:25 +0200 Subject: [PATCH] Advancing a bit more on server registration, fixing up log level mess --- actor | 2 +- server/src/uds/REST/methods/actor_token.py | 11 ++++--- server/src/uds/REST/methods/actor_v3.py | 32 +++++++++++-------- .../uds/REST/methods/registered_servers.py | 10 ++++-- server/src/uds/REST/methods/tunnel.py | 4 +-- server/src/uds/dispatchers/guacamole/views.py | 2 +- .../0046_registered_servers_migration.py | 5 +++ server/src/uds/models/registered_servers.py | 13 +++++--- server/src/uds/models/user_service.py | 6 ++++ 9 files changed, 55 insertions(+), 30 deletions(-) diff --git a/actor b/actor index a0af4f718..6364ac80a 160000 --- a/actor +++ b/actor @@ -1 +1 @@ -Subproject commit a0af4f7189ca40f9b42062273d735393b53eb881 +Subproject commit 6364ac80a36b3606f41eb25c02bede8eaf629f8e diff --git a/server/src/uds/REST/methods/actor_token.py b/server/src/uds/REST/methods/actor_token.py index 2cd28ee82..460ad7aa3 100644 --- a/server/src/uds/REST/methods/actor_token.py +++ b/server/src/uds/REST/methods/actor_token.py @@ -48,7 +48,7 @@ logger = logging.getLogger(__name__) class ActorTokens(ModelHandler): model = RegisteredServers - model_filter = {'kind': RegisteredServers.ServerType.ACTOR} + model_filter = {'kind': RegisteredServers.ServerType.ACTOR_SERVICE} table_title = _('Actor tokens') table_fields = [ @@ -65,6 +65,11 @@ class ActorTokens(ModelHandler): def item_as_dict(self, item: RegisteredServers) -> typing.Dict[str, typing.Any]: data = item.data or {} + log_level_int = data.get('log_level', 2) + if log_level_int < 10000: # Old log level + log_level = LogLevel.fromActorLevel(log_level_int).name + else: + log_level = LogLevel(log_level_int).name return { 'id': item.token, 'name': str(_('Token isued by {} from {}')).format(item.username, item.hostname or item.ip), @@ -76,9 +81,7 @@ class ActorTokens(ModelHandler): 'pre_command': data.get('pre_command', ''), 'post_command': data.get('post_command', ''), 'runonce_command': data.get('runonce_command', ''), - 'log_level': LogLevel.fromActorLevel( - data.get('log_level', 2) - ).name, # ['DEBUG', 'INFO', 'ERROR', 'FATAL'][item.log_level % 4], + 'log_level': log_level, } def delete(self) -> str: diff --git a/server/src/uds/REST/methods/actor_v3.py b/server/src/uds/REST/methods/actor_v3.py index 4866ec70e..9ead32f06 100644 --- a/server/src/uds/REST/methods/actor_v3.py +++ b/server/src/uds/REST/methods/actor_v3.py @@ -242,7 +242,7 @@ class Test(ActorV3Action): Service.objects.get(token=self._params['token']) else: RegisteredServers.objects.get( - token=self._params['token'], kind=RegisteredServers.ServerType.ACTOR + token=self._params['token'], kind=RegisteredServers.ServerType.ACTOR_SERVICE ) # Not assigned, because only needs check clearFailedIp(self._request) except Exception: @@ -280,18 +280,18 @@ class Register(ActorV3Action): # 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 - actorToken: typing.Optional[RegisteredServers] = RegisteredServers.objects.filter(kind=RegisteredServers.ServerType.ACTOR, mac=self._params['mac']).first() + actorToken: typing.Optional[RegisteredServers] = RegisteredServers.objects.filter(kind=RegisteredServers.ServerType.ACTOR_SERVICE, 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.log_level = self._params['log_level'] 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() @@ -306,15 +306,15 @@ class Register(ActorV3Action): 'ip': self._params['ip'], 'ip_version': self._request.ip_version, 'hostname': self._params['hostname'], + 'log_level': self._params['log_level'], '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', ''), }, 'token': RegisteredServers.create_token(), - 'kind': RegisteredServers.ServerType.ACTOR, + 'kind': RegisteredServers.ServerType.ACTOR_SERVICE, 'os_type': self._params.get('os', KnownOS.UNKNOWN.os_name()), 'mac': self._params['mac'], 'stamp': getSqlDatetime(), @@ -408,9 +408,8 @@ class Initialize(ActorV3Action): dbFilter = UserService.objects.filter(deployed_service__service=service) else: # If not service provided token, use actor tokens - RegisteredServers.objects.get( - token=token, kind=RegisteredServers.ServerType.ACTOR - ) # Not assigned, because only needs check + if not RegisteredServers.validateToken(token, RegisteredServers.ServerType.ACTOR_SERVICE): + raise BlockAccess() # Build the possible ids and make initial filter to match ANY userservice with provided MAC idsList = [i['mac'] for i in self._params['id'][:5]] dbFilter = UserService.objects.all() @@ -432,7 +431,7 @@ class Initialize(ActorV3Action): # Managed by UDS, get initialization data from osmanager and return it # Set last seen actor version - userService.setProperty('actor_version', self._params['version']) + userService.setActorVersion(self._params['version']) osData: typing.MutableMapping[str, typing.Any] = {} osManager = userService.getOsManagerInstance() if osManager: @@ -444,7 +443,7 @@ class Initialize(ActorV3Action): service.aliases.create(alias=alias_token) return initialization_result(userService.uuid, userService.unique_id, osData, alias_token) - except (RegisteredServers.DoesNotExist, Service.DoesNotExist): + except Service.DoesNotExist: raise BlockAccess() from None @@ -561,7 +560,7 @@ class Version(ActorV3Action): def action(self) -> typing.MutableMapping[str, typing.Any]: logger.debug('Version Args: %s, Params: %s', self._args, self._params) userService = self.getUserService() - userService.setProperty('actor_version', self._params['version']) + userService.setActorVersion(self._params['version']) userService.logIP(self._params['ip']) return ActorV3Action.actorResult() @@ -697,13 +696,18 @@ class Log(ActorV3Action): def action(self) -> typing.MutableMapping[str, typing.Any]: logger.debug('Args: %s, Params: %s', self._args, self._params) userService = self.getUserService() - # Adjust loglevel to own, we start on 10000 for OTHER, and received is 0 for OTHER + if userService.getActorVersion() < '4.0.0': + # Adjust loglevel to own, we start on 10000 for OTHER, and received is 0 for OTHER + level = log.LogLevel.fromInt(int(self._params['level']) + 10000) + else: + level = log.LogLevel.fromInt(int(self._params['level'])) log.doLog( userService, - log.LogLevel.fromInt(int(self._params['level']) + 10000), + level, self._params['message'], log.LogSource.ACTOR, ) + return ActorV3Action.actorResult('ok') @@ -720,7 +724,7 @@ class Ticket(ActorV3Action): try: # Simple check that token exists - RegisteredServers.objects.get(token=self._params['token'], kind=RegisteredServers.ServerType.ACTOR) # Not assigned, because only needs check + RegisteredServers.objects.get(token=self._params['token'], kind=RegisteredServers.ServerType.ACTOR_SERVICE) # Not assigned, because only needs check except RegisteredServers.DoesNotExist: raise BlockAccess() from None # If too many blocks... diff --git a/server/src/uds/REST/methods/registered_servers.py b/server/src/uds/REST/methods/registered_servers.py index 389f67151..1f3f6b7fd 100644 --- a/server/src/uds/REST/methods/registered_servers.py +++ b/server/src/uds/REST/methods/registered_servers.py @@ -38,6 +38,7 @@ from django.utils.translation import gettext_lazy as _ from uds import models from uds.core.util.model import getSqlDatetimeAsUnix, getSqlDatetime from uds.core.util.os_detector import KnownOS +from uds.core.util.log import LogLevel from uds.REST import Handler from uds.REST.exceptions import RequestError, NotFound from uds.REST.model import ModelHandler, OK @@ -66,11 +67,12 @@ class ServerRegister(Handler): # Note that we use IP and HOSTNAME (with type) 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=ip, hostname=self._params['hostname'], kind=self._params['type'] + ip=ip, ip_version=ip_version, hostname=self._params['hostname'], kind=self._params['type'] ) # Update parameters serverToken.username = self._user.pretty_name - serverToken.ip_from = self._request.ip.split('%')[0] # Ensure we do not store zone if IPv6 and present + # Ensure we do not store zone if IPv6 and present + serverToken.ip_from = self._request.ip.split('%')[0] serverToken.stamp = getSqlDatetime() serverToken.kind = self._params['type'] serverToken.save() @@ -80,8 +82,10 @@ class ServerRegister(Handler): username=self._user.pretty_name, ip_from=self._request.ip.split('%')[0], # Ensure we do not store zone if IPv6 and present ip=ip, + ip_version=ip_version, hostname=self._params['hostname'], token=models.RegisteredServers.create_token(), + log_level=self._params.get('log_level', LogLevel.INFO.value), stamp=getSqlDatetime(), kind=self._params['type'], os_type=typing.cast(str, self._params.get('os', KnownOS.UNKNOWN.os_name())).lower(), @@ -96,7 +100,7 @@ class ServerRegister(Handler): class ServersTokens(ModelHandler): model = models.RegisteredServers model_filter = { - 'kind__in': [models.RegisteredServers.ServerType.TUNNEL, models.RegisteredServers.ServerType.OTHER] + 'kind__in': [models.RegisteredServers.ServerType.TUNNEL_SERVER, models.RegisteredServers.ServerType.OTHER] } path = 'servers' name = 'tokens' diff --git a/server/src/uds/REST/methods/tunnel.py b/server/src/uds/REST/methods/tunnel.py index 9b18e8a5b..d5c043615 100644 --- a/server/src/uds/REST/methods/tunnel.py +++ b/server/src/uds/REST/methods/tunnel.py @@ -80,7 +80,7 @@ class TunnelTicket(Handler): # Take token from url token = self._args[2][:48] - if not models.RegisteredServers.validateToken(token, serverType=models.RegisteredServers.ServerType.TUNNEL): + if not models.RegisteredServers.validateToken(token, serverType=models.RegisteredServers.ServerType.TUNNEL_SERVER): if self._args[1][:4] == 'stop': # "Discard" invalid stop requests, because Applications does not like them. # RDS connections keep alive for a while after the application is finished, @@ -161,6 +161,6 @@ 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['type'] = models.RegisteredServers.ServerType.TUNNEL_SERVER 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/dispatchers/guacamole/views.py b/server/src/uds/dispatchers/guacamole/views.py index 3d6be95e5..31a205d66 100644 --- a/server/src/uds/dispatchers/guacamole/views.py +++ b/server/src/uds/dispatchers/guacamole/views.py @@ -58,7 +58,7 @@ def dict2resp(dct: typing.Mapping[typing.Any, typing.Any]) -> str: @auth.trustedSourceRequired def guacamole(request: ExtendedHttpRequestWithUser, token: str, tunnelId: str) -> HttpResponse: - if not RegisteredServers.validateToken(token, serverType=RegisteredServers.ServerType.TUNNEL): + if not RegisteredServers.validateToken(token, serverType=RegisteredServers.ServerType.TUNNEL_SERVER): logger.error('Invalid token %s from %s', token, request.ip) return HttpResponse(ERROR, content_type=CONTENT_TYPE) logger.debug('Received credentials request for tunnel id %s', tunnelId) diff --git a/server/src/uds/migrations/0046_registered_servers_migration.py b/server/src/uds/migrations/0046_registered_servers_migration.py index 7353dfe8a..04c53bdee 100644 --- a/server/src/uds/migrations/0046_registered_servers_migration.py +++ b/server/src/uds/migrations/0046_registered_servers_migration.py @@ -107,6 +107,11 @@ class Migration(migrations.Migration): name="listen_port", field=models.IntegerField(default=43910), ), + migrations.AddField( + model_name="registeredservers", + name="log_level", + field=models.IntegerField(default=50000), + ), migrations.RunPython( migrate_old_data, revert_old_data, diff --git a/server/src/uds/models/registered_servers.py b/server/src/uds/models/registered_servers.py index deafae9ca..5362a9a63 100644 --- a/server/src/uds/models/registered_servers.py +++ b/server/src/uds/models/registered_servers.py @@ -36,6 +36,7 @@ from uds.core.util.os_detector import KnownOS from django.db import models from uds.core.util.request import ExtendedHttpRequest +from uds.core.util.log import LogLevel from .consts import MAX_DNS_NAME_LENGTH, MAX_IPV6_LENGTH @@ -61,8 +62,8 @@ class RegisteredServers(models.Model): """ class ServerType(enum.IntEnum): - TUNNEL = 1 - ACTOR = 2 + TUNNEL_SERVER = 1 + ACTOR_SERVICE = 2 APP_SERVER = 3 OTHER = 99 @@ -71,8 +72,8 @@ class RegisteredServers(models.Model): def path(self) -> str: return { - RegisteredServers.ServerType.TUNNEL: 'tunnel', - RegisteredServers.ServerType.ACTOR: 'actor', + RegisteredServers.ServerType.TUNNEL_SERVER: 'tunnel', + RegisteredServers.ServerType.ACTOR_SERVICE: 'actor', RegisteredServers.ServerType.APP_SERVER: 'app', RegisteredServers.ServerType.OTHER: 'other', }[self] @@ -96,11 +97,13 @@ class RegisteredServers(models.Model): # Note that a server can register itself several times, so we can have several entries # for the same server, but with different types. # (So, for example, an APP_SERVER can be also a TUNNEL_SERVER, because will use both APP API and TUNNEL API) - kind = models.IntegerField(default=ServerType.TUNNEL.value) + kind = models.IntegerField(default=ServerType.TUNNEL_SERVER.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 VDI actor servers mainly, informative for others mac = models.CharField(max_length=32, default=MAC_UNKNOWN, db_index=True) + # Log level, so we can filter messages for this server + log_level = models.IntegerField(default=LogLevel.ERROR.value) # Extra data, for custom server type use (i.e. actor keeps command related data here) data = models.JSONField(null=True, blank=True, default=None) diff --git a/server/src/uds/models/user_service.py b/server/src/uds/models/user_service.py index 69a2e6b38..3f2736a12 100644 --- a/server/src/uds/models/user_service.py +++ b/server/src/uds/models/user_service.py @@ -627,6 +627,12 @@ class UserService(UUIDModel): def getLoggedIP(self) -> str: return self.getProperty('ip') or '0.0.0.0' # nosec: no binding address + + def setActorVersion(self, version: typing.Optional[str] = None) -> None: + self.setProperty('actor_version', version) + + def getActorVersion(self) -> str: + return self.getProperty('actor_version') or '0.0.0' def isValidPublication(self) -> bool: """