1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-01-08 21:18:00 +03:00

Refactor tunnel_ticket.py and tunnels_management.py

- Refactor tunnel_ticket.py to adapt to snake_case and improve code readability
- Add validation to prevent deletion of tunnel server group with attached transports in tunnels_management.py
This commit is contained in:
Adolfo Gómez García 2024-10-15 18:16:34 +02:00
parent c976716871
commit b2e3471512
No known key found for this signature in database
GPG Key ID: DD1ABF20724CDA23
2 changed files with 174 additions and 1 deletions

View File

@ -0,0 +1,165 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2021 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 collections.abc
import logging
import typing
from uds import models
from uds.core import exceptions, types
from uds.core.auths.auth import is_trusted_source
from uds.core.util import log, net
from uds.core.util.model import sql_stamp_seconds
from uds.core.util.stats import events
from uds.REST import Handler
from .servers import ServerRegisterBase
logger = logging.getLogger(__name__)
MAX_SESSION_LENGTH = 60 * 60 * 24 * 7 * 2 # Two weeks is max session length for a tunneled connection
# Enclosed methods under /tunnel path
class TunnelTicket(Handler):
"""
Processes tunnel requests
"""
authenticated = False # Client requests are not authenticated
path = 'tunnel'
name = 'ticket'
def get(self) -> collections.abc.MutableMapping[str, typing.Any]:
"""
Processes get requests
"""
logger.debug(
'Tunnel parameters for GET: %s (%s) from %s',
self._args,
self._params,
self._request.ip,
)
if not is_trusted_source(self._request.ip) or len(self._args) != 3 or len(self._args[0]) != 48:
# Invalid requests
raise exceptions.rest.AccessDenied()
# Take token from url
token = self._args[2][:48]
if not models.Server.validate_token(token, server_type=types.servers.ServerType.TUNNEL):
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,
# Also, same tunnel can be used for multiple applications, so we need to
# discard invalid stop requests. (because the data provided is also for "several" applications)")
return {}
logger.error('Invalid token %s from %s', token, self._request.ip)
raise exceptions.rest.AccessDenied()
# Try to get ticket from DB
try:
user, user_service, host, port, extra, key = models.TicketStore.get_for_tunnel(self._args[0])
host = host or ''
data: dict[str, typing.Any] = {}
if self._args[1][:4] == 'stop':
sent, recv = self._params['sent'], self._params['recv']
# Ensures extra exists...
extra = extra or {}
now = sql_stamp_seconds()
totalTime = now - extra.get('b', now - 1)
msg = f'User {user.name} stopped tunnel {extra.get("t", "")[:8]}... to {host}:{port}: u:{sent}/d:{recv}/t:{totalTime}.'
log.log(user.manager, types.log.LogLevel.INFO, msg)
log.log(user_service, types.log.LogLevel.INFO, msg)
# Try to log Close event
try:
# If pool does not exists, do not log anything
events.add_event(
user_service.deployed_service,
events.types.stats.EventType.TUNNEL_CLOSE,
duration=totalTime,
sent=sent,
received=recv,
tunnel=extra.get('t', 'unknown'),
)
except Exception as e:
logger.warning('Error logging tunnel close event: %s', e)
else:
if net.ip_to_long(self._args[1][:32]).version == 0:
raise Exception('Invalid from IP')
events.add_event(
user_service.deployed_service,
events.types.stats.EventType.TUNNEL_OPEN,
username=user.pretty_name,
srcip=self._args[1],
dstip=host,
tunnel=self._args[0],
)
msg = f'User {user.name} started tunnel {self._args[0][:8]}... to {host}:{port} from {self._args[1]}.'
log.log(user.manager, types.log.LogLevel.INFO, msg)
log.log(user_service, types.log.LogLevel.INFO, msg)
# Generate new, notify only, ticket
notify_ticket = models.TicketStore.create_for_tunnel(
userservice=user_service,
port=port,
host=host,
extra={
't': self._args[0], # ticket
'b': sql_stamp_seconds(), # Begin time stamp
},
validity=MAX_SESSION_LENGTH,
)
data = {'host': host, 'port': port, 'notify': notify_ticket, 'tunnel_key': key}
return data
except Exception as e:
logger.info('Ticket ignored: %s', e)
raise exceptions.rest.AccessDenied() from e
class TunnelRegister(ServerRegisterBase):
needs_admin = True
path = 'tunnel'
name = 'register'
# Just a compatibility method for old tunnel servers
def post(self) -> collections.abc.MutableMapping[str, typing.Any]:
self._params['type'] = types.servers.ServerType.TUNNEL
self._params['os'] = self._params.get(
'os', types.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
self._params['certificate'] = (
'' # No certificate for legacy tunnels, does not respond to API requests from UDS
)
return super().post()

View File

@ -36,7 +36,7 @@ from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import uds.core.types.permissions import uds.core.types.permissions
from uds.core import types, consts from uds.core import exceptions, types, consts
from uds.core.util import permissions, validators, ensure from uds.core.util import permissions, validators, ensure
from uds.core.util.model import process_uuid from uds.core.util.model import process_uuid
from uds import models from uds import models
@ -206,6 +206,14 @@ class Tunnels(ModelHandler):
# Ensure host is a valid IP(4 or 6) or hostname # Ensure host is a valid IP(4 or 6) or hostname
validators.validate_host(fields['host']) validators.validate_host(fields['host'])
def validate_delete(self, item: 'Model') -> None:
item = ensure.is_instance(item, models.ServerGroup)
# Only can delete if no ServicePools attached
if item.transports.count() > 0:
raise exceptions.rest.RequestError(
gettext('Cannot delete a tunnel server group with transports attached')
)
def assign(self, parent: 'Model') -> typing.Any: def assign(self, parent: 'Model') -> typing.Any:
parent = ensure.is_instance(parent, models.ServerGroup) parent = ensure.is_instance(parent, models.ServerGroup)
self.ensure_has_access(parent, uds.core.types.permissions.PermissionType.MANAGEMENT) self.ensure_has_access(parent, uds.core.types.permissions.PermissionType.MANAGEMENT)