diff --git a/server/src/uds/REST/__init__.py b/server/src/uds/REST/__init__.py index 5cb2e006..2c6ac0f3 100644 --- a/server/src/uds/REST/__init__.py +++ b/server/src/uds/REST/__init__.py @@ -120,7 +120,7 @@ class Dispatcher(View): processor = processors.available_processors_ext_dict.get(content_type, processors.default_processor)(request) # Obtain method to be invoked - http_method: str = request.method.lower() + http_method: str = request.method.lower() if request.method else '' # Path here has "remaining" path, that is, method part has been removed args = tuple(path) diff --git a/server/src/uds/REST/methods/actor_token.py b/server/src/uds/REST/methods/actor_token.py index 83b3b0a4..0c43e6a2 100644 --- a/server/src/uds/REST/methods/actor_token.py +++ b/server/src/uds/REST/methods/actor_token.py @@ -76,7 +76,6 @@ class ActorTokens(ModelHandler): 'log_level': ['DEBUG', 'INFO', 'ERROR', 'FATAL'][item.log_level%4] } - # Rewrite, this is an special case where no "uuid" is no table def delete(self) -> str: """ Processes a DELETE request diff --git a/server/src/uds/REST/methods/actor_v3.py b/server/src/uds/REST/methods/actor_v3.py index 1d818bfa..026784cb 100644 --- a/server/src/uds/REST/methods/actor_v3.py +++ b/server/src/uds/REST/methods/actor_v3.py @@ -31,7 +31,6 @@ import secrets import logging import typing -from uds.models import user from uds.models import ( getSqlDatetimeAsUnix, diff --git a/server/src/uds/REST/methods/tunnel.py b/server/src/uds/REST/methods/tunnel.py index a69a3585..fb294617 100644 --- a/server/src/uds/REST/methods/tunnel.py +++ b/server/src/uds/REST/methods/tunnel.py @@ -29,6 +29,7 @@ """ @author: Adolfo Gómez, dkmaster at dkmon dot com """ +import secrets import logging import typing @@ -39,18 +40,20 @@ from uds.REST import AccessDenied from uds.core.auths.auth import isTrustedSource from uds.core.util import log, net from uds.core.util.stats import events +from uds.models.util import getSqlDatetime logger = logging.getLogger(__name__) MAX_SESSION_LENGTH = 60*60*24*7 # Enclosed methods under /tunnel path -class Tunnel(Handler): +class TunnelTicket(Handler): """ Processes tunnel requests """ - authenticated = False # Client requests are not authenticated + path = 'tunnel' + name = 'ticket' def get(self) -> typing.MutableMapping[str, typing.Any]: """ @@ -62,7 +65,7 @@ class Tunnel(Handler): if ( not isTrustedSource(self._request.ip) - or len(self._args) not in (2, 3) + or len(self._args) != 3 or len(self._args[0]) != 48 ): # Invalid requests @@ -119,3 +122,41 @@ class Tunnel(Handler): except Exception as e: logger.info('Ticket ignored: %s', e) raise AccessDenied() + + +class TunnelRegister(Handler): + needs_admin = True + path = 'tunnel' + name = 'register' + + def post(self) -> typing.MutableMapping[str, typing.Any]: + tunnelToken: models.TunnelToken + now = models.getSqlDatetimeAsUnix() + try: + # If already exists a token for this MAC, return it instead of creating a new one, and update the information... + tunnelToken = models.TunnelToken.objects.get(ip=self._params['ip'], hostname= self._params['hostname']) + # Update parameters + tunnelToken.username = self._user.pretty_name + tunnelToken.ip_from = self._request.ip + tunnelToken.stamp = models.getSqlDatetime() + tunnelToken.save() + except Exception: + try: + tunnelToken = models.TunnelToken.objects.create( + username=self._user.pretty_name, + ip_from=self._request.ip, + ip=self._params['ip'], + hostname=self._params['hostname'], + token=secrets.token_urlsafe(36), + stamp=models.getSqlDatetime() + ) + except Exception as e: + return { + 'result': '', + 'stamp': now, + 'error': str(e) + } + return { + 'result': tunnelToken.token, + 'stamp': now + } diff --git a/server/src/uds/REST/methods/tunnel_token.py b/server/src/uds/REST/methods/tunnel_token.py new file mode 100644 index 00000000..828b1e11 --- /dev/null +++ b/server/src/uds/REST/methods/tunnel_token.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2020 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. 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 ugettext_lazy as _ + +from uds.models import TunnelToken +from uds.REST.handlers import RequestError, NotFound +from uds.REST.model import ModelHandler, OK +from uds.core.util import permissions + +logger = logging.getLogger(__name__) + +# Enclosed methods under /osm path + + +class TunnelTokens(ModelHandler): + model = TunnelToken + + table_title = _('Actor tokens') + table_fields = [ + {'token': {'title': _('Token')}}, + {'stamp': {'title': _('Date'), 'type': 'datetime'}}, + {'username': {'title': _('Issued by')}}, + {'hostname': {'title': _('Origin')}}, + {'ip': {'title': _('IP')}}, + ] + + def item_as_dict(self, item: TunnelToken) -> typing.Dict[str, typing.Any]: + return { + 'id': item.token, + 'name': _('Token isued by {} from {}').format(item.username, item.ip), + 'stamp': item.stamp, + 'username': item.username, + 'ip': item.ip, + 'hostname': item.hostname, + 'token': item.token + } + + def delete(self) -> str: + """ + Processes a DELETE request + """ + if len(self._args) != 1: + raise RequestError('Delete need one and only one argument') + + self.ensureAccess(self.model(), permissions.PERMISSION_ALL, root=True) # Must have write permissions to delete + + try: + self.model.objects.get(token=self._args[0]).delete() + except self.model.DoesNotExist: + raise NotFound('Element do not exists') + + return OK diff --git a/server/src/uds/migrations/0042_auto_20210628_1533.py b/server/src/uds/migrations/0042_auto_20210628_1533.py new file mode 100644 index 00000000..68fb4220 --- /dev/null +++ b/server/src/uds/migrations/0042_auto_20210628_1533.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.4 on 2021-06-28 15:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('uds', '0041_auto_20210624_1233'), + ] + + operations = [ + migrations.CreateModel( + name='TunnelToken', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('username', models.CharField(max_length=128)), + ('ip_from', models.CharField(max_length=16)), + ('ip', models.CharField(max_length=16)), + ('hostname', models.CharField(max_length=128)), + ('token', models.CharField(db_index=True, max_length=48, unique=True)), + ('stamp', models.DateTimeField()), + ], + ), + migrations.AddConstraint( + model_name='tunneltoken', + constraint=models.UniqueConstraint(fields=('ip', 'hostname'), name='tt_ip_hostname'), + ), + ] diff --git a/server/src/uds/models/__init__.py b/server/src/uds/models/__init__.py index 7e845589..f8f3251d 100644 --- a/server/src/uds/models/__init__.py +++ b/server/src/uds/models/__init__.py @@ -110,7 +110,8 @@ from .tag import Tag, TaggingMixin # Utility from .dbfile import DBFile -# Actor tokens +# Tokens from .actor_token import ActorToken +from .tunnel_token import TunnelToken logger = logging.getLogger(__name__) diff --git a/server/src/uds/models/actor_token.py b/server/src/uds/models/actor_token.py index ad516f84..33510b42 100644 --- a/server/src/uds/models/actor_token.py +++ b/server/src/uds/models/actor_token.py @@ -52,6 +52,9 @@ class ActorToken(models.Model): # "fake" declarations for type checking objects: 'models.BaseManager[ActorToken]' + class Meta: + app_label = 'uds' + def __str__(self): return ''.format( self.token, self.stamp, self.username, self.hostname, self.ip_from diff --git a/server/src/uds/models/tunnel_token.py b/server/src/uds/models/tunnel_token.py new file mode 100644 index 00000000..58d6c53d --- /dev/null +++ b/server/src/uds/models/tunnel_token.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2012-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. 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. +''' +.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com +''' +from django.db import models + + +class TunnelToken(models.Model): + """ + UDS Tunnel tokens on DB + """ + + username = models.CharField(max_length=128) + ip_from = models.CharField(max_length=16) + ip = models.CharField(max_length=16) + hostname = models.CharField(max_length=128) + + token = models.CharField(max_length=48, db_index=True, unique=True) + stamp = models.DateTimeField() # Date creation or validation of this entry + + # "fake" declarations for type checking + objects: 'models.BaseManager[TunnelToken]' + + class Meta: + app_label = 'uds' + constraints = [ + models.UniqueConstraint(fields=['ip', 'hostname'], name='tt_ip_hostname') + ] + + + def __str__(self): + return ''.format( + self.token, self.stamp, self.username, self.ip, self.hostname + ) diff --git a/tunnel-server/src/udstunnel.conf b/tunnel-server/src/udstunnel.conf index 7b951fed..42423ebb 100644 --- a/tunnel-server/src/udstunnel.conf +++ b/tunnel-server/src/udstunnel.conf @@ -37,9 +37,10 @@ ssl_dhparam = /etc/certs/dhparam.pem # UDS server location. https NEEDS valid certificate if https # Must point to tunnel ticket dispatcher URL, that is under /uds/rest/tunnel/ on tunnel server # Valid examples: -# http://www.example.com/uds/rest/tunnel/ -# https://www.example.com:14333/uds/rest/tunnel/ -uds_server = http://172.27.0.1:8000/uds/rest/tunnel +# http://www.example.com/uds/rest/tunnel/ticket +# https://www.example.com:14333/uds/rest/tunnel/ticket +uds_server = http://172.27.0.1:8000/uds/rest/tunnel/ticket +uds_auth = 123456789012345678901234567890123456789012345678 # Secret to get access to admin commands (Currently only stats commands). No default for this. # Admin commands and only allowed from "allow" ips