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:
parent
c976716871
commit
b2e3471512
165
server/src/uds/REST/methods/tunnel_ticket.py
Normal file
165
server/src/uds/REST/methods/tunnel_ticket.py
Normal 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()
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user