diff --git a/server/src/uds/REST/methods/tunnel.py b/server/src/uds/REST/methods/tunnel.py index 4d5ef6e1f..aa90cd4e4 100644 --- a/server/src/uds/REST/methods/tunnel.py +++ b/server/src/uds/REST/methods/tunnel.py @@ -38,12 +38,12 @@ from uds.core import managers from uds.REST import Handler from uds.REST import AccessDenied from uds.core.auths.auth import isTrustedSource -from uds.core.util import log, net, request +from uds.core.util import log, net from uds.core.util.stats import events logger = logging.getLogger(__name__) -MAX_SESSION_LENGTH = 60*60*24*7 +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): diff --git a/server/src/uds/core/managers/stats.py b/server/src/uds/core/managers/stats.py index ee8fef113..e6e8c8397 100644 --- a/server/src/uds/core/managers/stats.py +++ b/server/src/uds/core/managers/stats.py @@ -41,6 +41,16 @@ from uds.models import StatsEvents logger = logging.getLogger(__name__) +FLDS_EQUIV: typing.Mapping[str, typing.Iterable[str]] = { + 'fld1': ('username', 'platform'), + 'fld2': ('source', 'srcip', 'browser'), + 'fld3': ('destionation', 'dstip'), + 'fld4': ('uniqueid',), +} + +REVERSE_FLDS_EQUIV: typing.Mapping[str, str] = { + i: fld for fld, aliases in FLDS_EQUIV.items() for i in aliases +} class StatsManager: """ @@ -149,15 +159,7 @@ class StatsManager: ''' Get equivalency between "cool names" and field. Will raise "KeyError" if no equivalency ''' - return { - 'username': 'fld1', - 'platform': 'fld1', - 'srcip': 'fld2', - 'browser': 'fld2', - 'dstip': 'fld3', - 'version': 'fld3', - 'uniqueid': 'fld4' - }[fld] + return REVERSE_FLDS_EQUIV[fld] # Event stats def addEvent(self, owner_type: int, owner_id: int, eventType: int, **kwargs): @@ -184,15 +186,18 @@ class StatsManager: stamp = int(time.mktime(stamp.timetuple())) # pylint: disable=maybe-no-member try: + def getKwarg(fld: str) -> str: + val = None + for i in FLDS_EQUIV[fld]: + val = kwargs.get(i) + if val is not None: + break + return val or '' - # Replaces nulls for '' - def noneToEmpty(value): - return str(value) if value else '' - - fld1 = noneToEmpty(kwargs.get('fld1', kwargs.get('username', kwargs.get('platform', '')))) - fld2 = noneToEmpty(kwargs.get('fld2', kwargs.get('srcip', kwargs.get('browser', '')))) - fld3 = noneToEmpty(kwargs.get('fld3', kwargs.get('dstip', kwargs.get('version', '')))) - fld4 = noneToEmpty(kwargs.get('fld4', kwargs.get('uniqueid', ''))) + fld1 = getKwarg('fld1') + fld2 = getKwarg('fld2') + fld3 = getKwarg('fld3') + fld4 = getKwarg('fld4') StatsEvents.objects.create(owner_type=owner_type, owner_id=owner_id, event_type=eventType, stamp=stamp, fld1=fld1, fld2=fld2, fld3=fld3, fld4=fld4) return True diff --git a/server/src/uds/dispatchers/guacamole/views.py b/server/src/uds/dispatchers/guacamole/views.py index 48c5d38ed..cdf4fafb9 100644 --- a/server/src/uds/dispatchers/guacamole/views.py +++ b/server/src/uds/dispatchers/guacamole/views.py @@ -30,13 +30,16 @@ """ @author: Adolfo Gómez, dkmaster at dkmon dot com """ +import typing import logging -from django.http import HttpResponse, HttpRequest +from django.http import HttpResponse from uds.models import TicketStore, UserService, TunnelToken from uds.core.auths import auth from uds.core.managers import cryptoManager +from uds.core.util import log +from uds.core.util.stats import events from uds.core.util.request import ExtendedHttpRequestWithUser logger = logging.getLogger(__name__) @@ -58,15 +61,32 @@ def guacamole(request: ExtendedHttpRequestWithUser, tunnelId: str) -> HttpRespon try: tunnelId, scrambler = tunnelId.split('.') - val = TicketStore.get(tunnelId, invalidate=False) + # All strings excetp "ticket-info", that is fixed if it exists later + val = typing.cast(typing.MutableMapping[str, str], TicketStore.get(tunnelId, invalidate=False)) # Extra check that the ticket data belongs to original requested user service/user if 'ticket-info' in val: - ti = val['ticket-info'] + ti = typing.cast(typing.Mapping[str, str], val['ticket-info']) del val['ticket-info'] # Do not send this data to guacamole!! :) try: userService = UserService.objects.get(uuid=ti['userService']) + # Log message and event + protocol = 'RDS' if 'remote-app' in val else val['protocol'].upper() + host = val.get('hostname', '0.0.0.0') + msg = f'User {userService.user.name} started HTML5 {protocol} tunnel to {host}.' + log.doLog(userService.user.manager, log.INFO, msg) + log.doLog(userService, log.INFO, msg) + + events.addEvent( + userService.deployed_service, + events.ET_TUNNEL_ACCESS, + username=userService.user.pretty_name, + source='HTML5 ' + protocol, # On HTML5, currently src is not provided by Guacamole + dstip=host, + uniqueid=userService.unique_id, + ) + except Exception: logger.error('The requested guacamole userservice does not exists anymore') raise