1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-02-03 13:47:14 +03:00

adding server logic in UDS to acommodate existing Actors and Tunnels and alow new servers

This commit is contained in:
Adolfo Gómez García 2023-07-31 20:58:56 +02:00
parent fd3154946d
commit 05cc12a32c
No known key found for this signature in database
GPG Key ID: DD1ABF20724CDA23
15 changed files with 258 additions and 103 deletions

View File

@ -71,4 +71,4 @@ class ActorRegisterTest(rest.test.RESTActorTestCase):
token = response.json()['result']
# Ensure database contains the registered token
self.assertEqual(models.RegisteredServers.objects.filter(token=token).count(), 1)
self.assertEqual(models.RegisteredServer.objects.filter(token=token).count(), 1)

View File

@ -35,7 +35,7 @@ import typing
from django.utils.translation import gettext_lazy as _
from uds.models import RegisteredServers
from uds.models import RegisteredServer
from uds.REST.exceptions import RequestError, NotFound
from uds.REST.model import ModelHandler, OK
from uds.core.util import permissions
@ -47,8 +47,8 @@ logger = logging.getLogger(__name__)
class ActorTokens(ModelHandler):
model = RegisteredServers
model_filter = {'kind': RegisteredServers.ServerType.ACTOR_SERVICE}
model = RegisteredServer
model_filter = {'kind': RegisteredServer.ServerType.ACTOR_SERVICE}
table_title = _('Actor tokens')
table_fields = [
@ -63,7 +63,7 @@ class ActorTokens(ModelHandler):
{'log_level': {'title': _('Log level')}},
]
def item_as_dict(self, item: RegisteredServers) -> typing.Dict[str, typing.Any]:
def item_as_dict(self, item: RegisteredServer) -> 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

View File

@ -39,7 +39,7 @@ from uds.models import (
UserService,
Service,
TicketStore,
RegisteredServers,
RegisteredServer,
)
from uds.core.util.model import getSqlDatetimeAsUnix, getSqlDatetime
@ -239,8 +239,8 @@ class Test(ActorV3Action):
if self._params.get('type') == UNMANAGED:
Service.objects.get(token=self._params['token'])
else:
RegisteredServers.objects.get(
token=self._params['token'], kind=RegisteredServers.ServerType.ACTOR_SERVICE
RegisteredServer.objects.get(
token=self._params['token'], kind=RegisteredServer.ServerType.ACTOR_SERVICE
) # Not assigned, because only needs check
clearFailedIp(self._request)
except Exception:
@ -278,8 +278,8 @@ 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_SERVICE, mac=self._params['mac']
actorToken: typing.Optional[RegisteredServer] = RegisteredServer.objects.filter(
kind=RegisteredServer.ServerType.ACTOR_SERVICE, mac=self._params['mac']
).first()
if actorToken:
# Update parameters
@ -288,6 +288,7 @@ class Register(ActorV3Action):
actorToken.ip = self._params['ip']
actorToken.hostname = self._params['hostname']
actorToken.log_level = self._params['log_level']
actorToken.version = self._params.get('version', '')
actorToken.data = { # type: ignore
'pre_command': self._params['pre_command'],
'post_command': self._params['post_command'],
@ -313,14 +314,16 @@ class Register(ActorV3Action):
'run_once_command': self._params['run_once_command'],
'custom': self._params.get('custom', ''),
},
'token': RegisteredServers.create_token(),
'kind': RegisteredServers.ServerType.ACTOR_SERVICE,
'token': RegisteredServer.create_token(),
'kind': RegisteredServer.ServerType.ACTOR_SERVICE,
'sub_kind': 'v3',
'version': self._params.get('version', ''),
'os_type': self._params.get('os', KnownOS.UNKNOWN.os_name()),
'mac': self._params['mac'],
'stamp': getSqlDatetime(),
}
actorToken = RegisteredServers.objects.create(**kwargs)
actorToken = RegisteredServer.objects.create(**kwargs)
return ActorV3Action.actorResult(actorToken.token) # type: ignore # actorToken is always assigned
@ -408,7 +411,7 @@ class Initialize(ActorV3Action):
dbFilter = UserService.objects.filter(deployed_service__service=service)
else:
# If not service provided token, use actor tokens
if not RegisteredServers.validateToken(token, RegisteredServers.ServerType.ACTOR_SERVICE):
if not RegisteredServer.validateToken(token, RegisteredServer.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]]
@ -721,10 +724,10 @@ class Ticket(ActorV3Action):
try:
# Simple check that token exists
RegisteredServers.objects.get(
token=self._params['token'], kind=RegisteredServers.ServerType.ACTOR_SERVICE
RegisteredServer.objects.get(
token=self._params['token'], kind=RegisteredServer.ServerType.ACTOR_SERVICE
) # Not assigned, because only needs check
except RegisteredServers.DoesNotExist:
except RegisteredServer.DoesNotExist:
raise BlockAccess() from None # If too many blocks...
try:

View File

@ -30,13 +30,12 @@
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import logging
import enum
import typing
from django.utils.translation import gettext_lazy as _
from uds import models
from uds.core import exceptions
from uds.core import exceptions, types
from uds.core.util.model import getSqlDatetimeAsUnix, getSqlDatetime
from uds.core.util.os_detector import KnownOS
from uds.core.util.log import LogLevel
@ -55,7 +54,7 @@ class ServerRegister(Handler):
name = 'register'
def post(self) -> typing.MutableMapping[str, typing.Any]:
serverToken: models.RegisteredServers
serverToken: models.RegisteredServer
now = getSqlDatetimeAsUnix()
ip_version = 4
ip = self._params.get('ip', self.request.ip)
@ -68,7 +67,7 @@ class ServerRegister(Handler):
# 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 (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(
serverToken = models.RegisteredServer.objects.get(
ip=ip, ip_version=ip_version, hostname=self._params['hostname'], kind=self._params['type']
)
# Update parameters
@ -80,18 +79,19 @@ class ServerRegister(Handler):
serverToken.save()
except Exception:
try:
serverToken = models.RegisteredServers.objects.create(
serverToken = models.RegisteredServer.objects.create(
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(),
token=models.RegisteredServer.create_token(),
log_level=self._params.get('log_level', LogLevel.INFO.value),
stamp=getSqlDatetime(),
kind=self._params['type'],
sub_kind=self._params.get('sub_kind', ''), # Optional
os_type=typing.cast(str, self._params.get('os', KnownOS.UNKNOWN.os_name())).lower(),
mac=self._params.get('mac', models.RegisteredServers.MAC_UNKNOWN),
mac=self._params.get('mac', models.RegisteredServer.MAC_UNKNOWN),
data=self._params.get('data', None),
)
except Exception as e:
@ -100,12 +100,10 @@ class ServerRegister(Handler):
class ServersTokens(ModelHandler):
model = models.RegisteredServers
model_filter = {
model = models.RegisteredServer
model_exclude = {
'kind__in': [
models.RegisteredServers.ServerType.TUNNEL_SERVER,
models.RegisteredServers.ServerType.APP_SERVER,
models.RegisteredServers.ServerType.OTHER,
models.RegisteredServer.ServerType.ACTOR_SERVICE,
]
}
path = 'servers'
@ -122,7 +120,7 @@ class ServersTokens(ModelHandler):
{'ip': {'title': _('IP')}},
]
def item_as_dict(self, item: models.RegisteredServers) -> typing.Dict[str, typing.Any]:
def item_as_dict(self, item: models.RegisteredServer) -> typing.Dict[str, typing.Any]:
return {
'id': item.token,
'name': str(_('Token isued by {} from {}')).format(item.username, item.ip),
@ -131,7 +129,7 @@ class ServersTokens(ModelHandler):
'ip': item.ip,
'hostname': item.hostname,
'token': item.token,
'type': models.RegisteredServers.ServerType(
'type': models.RegisteredServer.ServerType(
item.kind
).as_str(), # type is a reserved word, so we use "kind" instead on model
'os': item.os_type,
@ -165,7 +163,7 @@ class ServerTest(Handler):
def post(self) -> typing.MutableMapping[str, typing.Any]:
# Test if a token is valid
try:
models.RegisteredServers.objects.get(token=self._params['token'])
models.RegisteredServer.objects.get(token=self._params['token'])
return rest_result(True)
except Exception as e:
return rest_result('error', error=str(e))
@ -175,23 +173,29 @@ class ServerAction(Handler):
authenticated = False # Actor requests are not authenticated normally
path = 'servers/action'
def action(self, server: models.RegisteredServers) -> typing.MutableMapping[str, typing.Any]:
def action(self, server: models.RegisteredServer) -> typing.MutableMapping[str, typing.Any]:
return rest_result('error', error='Base action invoked')
@blocker.blocker()
def post(self) -> typing.MutableMapping[str, typing.Any]:
try:
server = models.RegisteredServers.objects.get(token=self._params['token'])
except models.RegisteredServers.DoesNotExist:
server = models.RegisteredServer.objects.get(token=self._params['token'])
except models.RegisteredServer.DoesNotExist:
raise exceptions.BlockAccess() from None # Block access if token is not valid
return self.action(server)
class NotifyActions(enum.StrEnum):
LOGIN = 'login'
LOGOUT = 'logout'
class ServerEvent(ServerAction):
"""
Manages a event notification from a server to UDS Broker
class ServerNotify(ServerAction):
The idea behind this is manage events like login and logout from a single point
Currently possible notify actions are:
* login
* logout
* log
"""
name = 'notify'
def getUserService(self) -> models.UserService:
@ -204,16 +208,20 @@ class ServerNotify(ServerAction):
logger.error('User service not found (params: %s)', self._params)
raise
def action(self, server: models.RegisteredServers) -> typing.MutableMapping[str, typing.Any]:
def action(self, server: models.RegisteredServer) -> typing.MutableMapping[str, typing.Any]:
# Notify a server that a new service has been assigned to it
# Get action from parameters
# Parameters:
# * action
# * event
# * uuid (user service uuid)
# * data: data related to the received event
# * Login: { 'username': 'username'}
# * Logout: { 'username': 'username'}
# * Log: { 'level': 'level', 'message': 'message'}
try:
action = NotifyActions(self._params.get('action', None))
event = types.NotifiableEvents(self._params.get('event', None))
except ValueError:
return rest_result('error', error='No valid action specified')
return rest_result('error', error='No valid event specified')
# Extract user service
try:
@ -221,11 +229,14 @@ class ServerNotify(ServerAction):
except Exception:
return rest_result('error', error='User service not found')
if action == NotifyActions.LOGIN:
if event == types.NotifiableEvents.LOGIN:
# TODO: notify
pass
elif action == NotifyActions.LOGOUT:
elif event == types.NotifiableEvents.LOGOUT:
# TODO: notify
pass
elif event == types.NotifiableEvents.LOG:
# TODO: log
pass
return rest_result(True)

View File

@ -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_SERVER):
if not models.RegisteredServer.validateToken(token, serverType=models.RegisteredServer.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,7 @@ 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_SERVER
self._params['type'] = models.RegisteredServer.ServerType.TUNNEL_SERVER
self._params['os'] = self._params.get('os', KnownOS.LINUX.os_name()) # Legacy tunnels are always linux
self._params['version'] = '' # No version for legacy tunnels, does not respond to API requests from UDS
return super().post()

View File

@ -711,6 +711,8 @@ class ModelHandler(BaseModelHandler):
model: 'typing.ClassVar[typing.Type[models.Model]]'
# If the model is filtered (for overviews)
model_filter: 'typing.ClassVar[typing.Optional[typing.Mapping[str, typing.Any]]]' = None
# Same, but for exclude
model_exclude: 'typing.ClassVar[typing.Optional[typing.Mapping[str, typing.Any]]]' = None
# By default, filter is empty
fltr: typing.Optional[str] = None
@ -940,6 +942,9 @@ class ModelHandler(BaseModelHandler):
if self.model_filter is not None:
query = query.filter(**self.model_filter)
if self.model_exclude is not None:
query = query.exclude(**self.model_exclude)
for item in query:
try:
if (

View File

@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2023 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import logging
import typing
from django.utils.translation import gettext as _
from uds.core import types
from uds.core.util import singleton
if typing.TYPE_CHECKING:
from uds import models
logger = logging.getLogger(__name__)
traceLogger = logging.getLogger('traceLog')
operationsLogger = logging.getLogger('operationsLog')
class ServerManager(metaclass=singleton.Singleton):
def __init__(self):
pass
@staticmethod
def manager() -> 'ServerManager':
return ServerManager() # Singleton pattern will return always the same instance
# TODO: Implement this
def notifyPreconnect(self, server: 'models.RegisteredServer') -> None:
pass

View File

@ -71,18 +71,16 @@ def _requestActor(
# Maybe service knows how to do it
# logger.warning('No notification is made because agent does not supports notifications: %s', userService.friendly_name)
raise NoActorComms(
f'No notification urls for {userService.friendly_name}'
)
raise NoActorComms(f'No notification urls for {userService.friendly_name}')
minVersion = minVersion or '3.5.0'
version = userService.getProperty('actor_version') or '0.0.0'
if '-' in version or version < minVersion:
logger.warning(
'Pool %s has old actors (%s)', userService.deployed_service.name, version
)
logger.warning('Pool %s has old actors (%s)', userService.deployed_service.name, version)
raise OldActorVersion(
f'Old actor version {version} for {userService.friendly_name}'.format(version, userService.friendly_name)
f'Old actor version {version} for {userService.friendly_name}'.format(
version, userService.friendly_name
)
)
url += '/' + method
@ -141,13 +139,14 @@ def notifyPreconnect(userService: 'UserService', info: types.ConnectionInfoType)
_requestActor(
userService,
'preConnect',
{
'user': info.username,
'protocol': info.protocol,
'ip': src.ip,
'hostname': src.hostname,
'udsuser': userService.user.name + '@' + userService.user.manager.name if userService.user else '',
},
types.PreconnectInfoType(
user=info.username,
protocol=info.protocol,
ip=src.ip,
hostname=src.hostname,
udsuser=userService.user.name + '@' + userService.user.manager.name if userService.user else '',
userservice=userService.uuid,
).asDict(),
)
except NoActorComms:
pass # If no preconnect, warning will appear on UDS log
@ -159,9 +158,7 @@ def checkUuid(userService: 'UserService') -> bool:
"""
try:
uuid = _requestActor(userService, 'uuid')
if (
uuid and uuid != userService.uuid
): # Empty UUID means "no check this, fixed pool machine"
if uuid and uuid != userService.uuid: # Empty UUID means "no check this, fixed pool machine"
logger.info(
'Machine %s do not have expected uuid %s, instead has %s',
userService.friendly_name,
@ -179,7 +176,9 @@ def requestScreenshot(userService: 'UserService') -> bytes:
"""
Returns an screenshot in PNG format (bytes) or empty png if not supported
"""
emptyPng = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='
emptyPng = (
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='
)
try:
png = _requestActor(
userService, 'screenshot', minVersion='3.0.0'

View File

@ -30,20 +30,60 @@
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import typing
import enum
# Types used in UDS
# ************
# Connections
# ************
class ConnectionInfoType(typing.NamedTuple):
# protocol: protocol to use, (there are a few standard defined in 'protocols.py', if yours does not fit those, use your own name
# username: username (transformed if needed to) used to login to service
# password: password (transformed if needed to) used to login to service
# domain: domain (extracted from username or wherever) that will be used. (Not necesarily an AD domain)
protocol: str
username: str
password: str = ''
domain: str = '' # For some transports (RDP, ...)
host: str = '' # Used only for some transports that needs the host to connect to
"""
Connection info type is provided by transports, and contains all the "transformable" information needed to connect to a service
"""
protocol: str # protocol to use, (there are a few standard defined in 'protocols.py', if yours does not fit those, use your own name
username: str # username (transformed if needed to) used to login to service
password: str = '' # password (transformed if needed to) used to login to service
domain: str = '' # domain (extracted from username or wherever) that will be used. (Not necesarily an AD domain)
host: str = '' # Used only for some transports that needs the host to connect to (like SPICE, RDS, etc..)
# sso: bool = False # For future sso implementation
def asDict(self) -> typing.Dict[str, str]:
return self._asdict()
class ConnectionSourceType(typing.NamedTuple):
"""
Connection source from where the connection is being done
"""
ip: str # IP of the client
hostname: str # Hostname of the client
def asDict(self) -> typing.Dict[str, str]:
return self._asdict()
# **************
# Notifications
# **************
# From UDS to Service or UserService types
class PreconnectInfoType(typing.NamedTuple):
user: str
protocol: str
ip: str
hostname: str
udsuser: str
userservice: str
def asDict(self) -> typing.Dict[str, str]:
return self._asdict()
# From UserService to Service types
class NotifiableEvents(enum.StrEnum):
"""
Possible notification actions
"""
LOGIN = 'login'
LOGOUT = 'logout'
LOG = 'log'

View File

@ -61,7 +61,7 @@ class ObjectType(enum.Enum):
# PROXY_TYPE = (15, models.Proxy) has been removed
METAPOOL = (16, models.MetaPool)
ACCOUNT = (17, models.Account)
REGISTERED_SERVER = (19, models.RegisteredServers)
REGISTERED_SERVER = (19, models.RegisteredServer)
ACCOUNT_USAGE = (20, models.AccountUsage)
IMAGE = (21, models.Image)
LOG = (22, models.Log)

View File

@ -35,7 +35,7 @@ import logging
from django.http import HttpResponse
from uds.models import TicketStore, UserService, RegisteredServers
from uds.models import TicketStore, UserService, RegisteredServer
from uds.core.auths import auth
from uds.core.managers.crypto import CryptoManager
from uds.core.util import log
@ -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_SERVER):
if not RegisteredServer.validateToken(token, serverType=RegisteredServer.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)

View File

@ -359,7 +359,7 @@ class Command(BaseCommand):
# Rest of registerd servers
registeredServers: typing.Dict[str, typing.Any] = {}
for i, registeredServer in enumerate(models.RegisteredServers.objects.all()):
for i, registeredServer in enumerate(models.RegisteredServer.objects.all()):
registeredServers[f'{i}'] = getSerializedFromModel(
registeredServer
)

View File

@ -1,21 +1,22 @@
import typing
from django.db import migrations, models
import django.db.models.deletion
from uds.core.util.os_detector import KnownOS
ACTOR_TYPE = 2 # Hardcoded value from uds/models/registered_servers.py
def migrate_old_data(apps, schema_editor):
try:
RegisteredServers = apps.get_model('uds', 'RegisteredServers')
RegisteredServer = apps.get_model('uds', 'RegisteredServer')
ActorToken = apps.get_model('uds', 'ActorToken')
# Current Registered servers are tunnel servers, and all tunnel servers are linux os, so update ip
RegisteredServers.objects.all().update(os_type=KnownOS.LINUX.os_name())
RegisteredServer.objects.all().update(os_type=KnownOS.LINUX.os_name())
# Now append actors to registered servers, with "unknown" os type (legacy)
for token in ActorToken.objects.all():
RegisteredServers.objects.create(
RegisteredServer.objects.create(
username=token.username,
ip_from=token.ip_from,
ip=token.ip,
@ -40,9 +41,9 @@ def migrate_old_data(apps, schema_editor):
raise e
def revert_old_data(apps, schema_editor):
RegisteredServers = apps.get_model('uds', 'RegisteredServers')
RegisteredServer = apps.get_model('uds', 'RegisteredServer')
ActorToken = apps.get_model('uds', 'ActorToken')
for server in RegisteredServers.objects.filter(kind=ACTOR_TYPE):
for server in RegisteredServer.objects.filter(kind=ACTOR_TYPE):
ActorToken.objects.create(
username=server.username,
ip_from=server.ip_from,
@ -69,49 +70,71 @@ class Migration(migrations.Migration):
operations = [
migrations.RenameModel(
'TunnelToken',
'RegisteredServers',
'RegisteredServer',
),
migrations.RemoveConstraint(
model_name="registeredservers",
model_name="registeredserver",
name="tt_ip_hostname",
),
migrations.AddField(
model_name="registeredservers",
model_name="registeredserver",
name="kind",
field=models.IntegerField(default=1),
),
migrations.AddField(
model_name="registeredservers",
model_name="registeredserver",
name="sub_kind",
field=models.CharField(default="", max_length=32),
),
migrations.AddField(
model_name="registeredserver",
name="version",
field=models.CharField(default="4.0.0", max_length=32),
),
migrations.AddField(
model_name="registeredserver",
name="ip_version",
field=models.IntegerField(default=4),
),
migrations.AddField(
model_name="registeredservers",
model_name="registeredserver",
name="data",
field=models.JSONField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name="registeredservers",
model_name="registeredserver",
name="os_type",
field=models.CharField(default="unknown", max_length=32),
),
migrations.AddField(
model_name="registeredservers",
model_name="registeredserver",
name="mac",
field=models.CharField(
db_index=True, default="00:00:00:00:00:00", max_length=32
),
),
migrations.AddField(
model_name="registeredservers",
model_name="registeredserver",
name="listen_port",
field=models.IntegerField(default=43910),
),
migrations.AddField(
model_name="registeredservers",
model_name="registeredserver",
name="log_level",
field=models.IntegerField(default=50000),
),
migrations.AddField(
model_name="registeredserver",
name="attached_to",
field=models.ForeignKey(
blank=True,
default=None,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="attached_servers",
to="uds.provider",
),
),
migrations.RunPython(
migrate_old_data,
revert_old_data,

View File

@ -103,7 +103,7 @@ from .account_usage import AccountUsage
from .tag import Tag, TaggingMixin
# Tokens
from .registered_servers import RegisteredServers
from .registered_servers import RegisteredServer
# Notifications & Alerts
from .notifications import Notification, Notifier, LogLevel

View File

@ -43,7 +43,7 @@ from .consts import MAX_DNS_NAME_LENGTH, MAX_IPV6_LENGTH
DEFAULT_LISTEN_PORT: typing.Final[int] = 43910
class RegisteredServers(models.Model):
class RegisteredServer(models.Model):
"""
UDS Registered Servers
@ -72,10 +72,10 @@ class RegisteredServers(models.Model):
def path(self) -> str:
return {
RegisteredServers.ServerType.TUNNEL_SERVER: 'tunnel',
RegisteredServers.ServerType.ACTOR_SERVICE: 'actor',
RegisteredServers.ServerType.APP_SERVER: 'app',
RegisteredServers.ServerType.OTHER: 'other',
RegisteredServer.ServerType.TUNNEL_SERVER: 'tunnel',
RegisteredServer.ServerType.ACTOR_SERVICE: 'actor',
RegisteredServer.ServerType.APP_SERVER: 'app',
RegisteredServer.ServerType.OTHER: 'other',
}[self]
MAC_UNKNOWN = '00:00:00:00:00:00'
@ -98,6 +98,9 @@ class RegisteredServers(models.Model):
# 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_SERVER.value)
sub_kind = models.CharField(max_length=32, default='') # Subkind of server, if any (I.E. LinuxDocker, RDS, etc..)
version = models.CharField(max_length=32, default='4.0.0') # Version of the UDS API of the server. Starst at 4.0.0
# 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
@ -108,6 +111,18 @@ class RegisteredServers(models.Model):
# Extra data, for custom server type use (i.e. actor keeps command related data here)
data = models.JSONField(null=True, blank=True, default=None)
# The Registered Server can be an "attachable" sever, that is, one that is attached to a "master" server (provider)
# For Actors and Tunnels, this is always "NONE", but for App servers, this can be a provider (or None, it is not)
# attached to a provider
attached_to = models.ForeignKey(
'uds.Provider',
on_delete=models.SET_NULL,
null=True,
blank=True,
default=None,
related_name='attached_servers',
)
class Meta: # pylint: disable=too-few-public-methods
app_label = 'uds'
@ -123,21 +138,21 @@ class RegisteredServers(models.Model):
) -> bool:
# Ensure tiken is composed of
try:
if isinstance(serverType, RegisteredServers.ServerType):
tt = RegisteredServers.objects.get(token=token, kind=serverType.value)
if isinstance(serverType, RegisteredServer.ServerType):
tt = RegisteredServer.objects.get(token=token, kind=serverType.value)
else:
tt = RegisteredServers.objects.get(token=token, kind__in=[st.value for st in serverType])
tt = RegisteredServer.objects.get(token=token, kind__in=[st.value for st in serverType])
# We could check the request ip here
if request and request.ip != tt.ip:
raise Exception('Invalid ip')
return True
except RegisteredServers.DoesNotExist:
except RegisteredServer.DoesNotExist:
pass
return False
@property
def server_type(self) -> ServerType:
return RegisteredServers.ServerType(self.kind)
return RegisteredServer.ServerType(self.kind)
@server_type.setter
def server_type(self, value: ServerType) -> None: