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:
parent
fd3154946d
commit
05cc12a32c
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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)
|
@ -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()
|
||||
|
@ -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 (
|
||||
|
58
server/src/uds/core/managers/servers.py
Normal file
58
server/src/uds/core/managers/servers.py
Normal 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
|
@ -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'
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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,
|
@ -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
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user