1
0
mirror of https://github.com/dkmstr/openuds.git synced 2024-12-22 13:34:04 +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 _
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.model import process_uuid
from uds import models
@ -206,6 +206,14 @@ class Tunnels(ModelHandler):
# Ensure host is a valid IP(4 or 6) or hostname
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:
parent = ensure.is_instance(parent, models.ServerGroup)
self.ensure_has_access(parent, uds.core.types.permissions.PermissionType.MANAGEMENT)