Adding tunnelers tokens for increased security

This commit is contained in:
Adolfo Gómez García 2021-06-28 15:36:52 +02:00
parent 25736f61b8
commit bddb9355c8
10 changed files with 228 additions and 10 deletions

View File

@ -120,7 +120,7 @@ class Dispatcher(View):
processor = processors.available_processors_ext_dict.get(content_type, processors.default_processor)(request) processor = processors.available_processors_ext_dict.get(content_type, processors.default_processor)(request)
# Obtain method to be invoked # 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 # Path here has "remaining" path, that is, method part has been removed
args = tuple(path) args = tuple(path)

View File

@ -76,7 +76,6 @@ class ActorTokens(ModelHandler):
'log_level': ['DEBUG', 'INFO', 'ERROR', 'FATAL'][item.log_level%4] '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: def delete(self) -> str:
""" """
Processes a DELETE request Processes a DELETE request

View File

@ -31,7 +31,6 @@
import secrets import secrets
import logging import logging
import typing import typing
from uds.models import user
from uds.models import ( from uds.models import (
getSqlDatetimeAsUnix, getSqlDatetimeAsUnix,

View File

@ -29,6 +29,7 @@
""" """
@author: Adolfo Gómez, dkmaster at dkmon dot com @author: Adolfo Gómez, dkmaster at dkmon dot com
""" """
import secrets
import logging import logging
import typing import typing
@ -39,18 +40,20 @@ from uds.REST import AccessDenied
from uds.core.auths.auth import isTrustedSource from uds.core.auths.auth import isTrustedSource
from uds.core.util import log, net from uds.core.util import log, net
from uds.core.util.stats import events from uds.core.util.stats import events
from uds.models.util import getSqlDatetime
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
MAX_SESSION_LENGTH = 60*60*24*7 MAX_SESSION_LENGTH = 60*60*24*7
# Enclosed methods under /tunnel path # Enclosed methods under /tunnel path
class Tunnel(Handler): class TunnelTicket(Handler):
""" """
Processes tunnel requests Processes tunnel requests
""" """
authenticated = False # Client requests are not authenticated authenticated = False # Client requests are not authenticated
path = 'tunnel'
name = 'ticket'
def get(self) -> typing.MutableMapping[str, typing.Any]: def get(self) -> typing.MutableMapping[str, typing.Any]:
""" """
@ -62,7 +65,7 @@ class Tunnel(Handler):
if ( if (
not isTrustedSource(self._request.ip) not isTrustedSource(self._request.ip)
or len(self._args) not in (2, 3) or len(self._args) != 3
or len(self._args[0]) != 48 or len(self._args[0]) != 48
): ):
# Invalid requests # Invalid requests
@ -119,3 +122,41 @@ class Tunnel(Handler):
except Exception as e: except Exception as e:
logger.info('Ticket ignored: %s', e) logger.info('Ticket ignored: %s', e)
raise AccessDenied() 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
}

View File

@ -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

View File

@ -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'),
),
]

View File

@ -110,7 +110,8 @@ from .tag import Tag, TaggingMixin
# Utility # Utility
from .dbfile import DBFile from .dbfile import DBFile
# Actor tokens # Tokens
from .actor_token import ActorToken from .actor_token import ActorToken
from .tunnel_token import TunnelToken
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@ -52,6 +52,9 @@ class ActorToken(models.Model):
# "fake" declarations for type checking # "fake" declarations for type checking
objects: 'models.BaseManager[ActorToken]' objects: 'models.BaseManager[ActorToken]'
class Meta:
app_label = 'uds'
def __str__(self): def __str__(self):
return '<ActorToken {} created on {} by {} from {}/{}>'.format( return '<ActorToken {} created on {} by {} from {}/{}>'.format(
self.token, self.stamp, self.username, self.hostname, self.ip_from self.token, self.stamp, self.username, self.hostname, self.ip_from

View File

@ -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 '<TunnelToken {} created on {} by {} from {}/{}>'.format(
self.token, self.stamp, self.username, self.ip, self.hostname
)

View File

@ -37,9 +37,10 @@ ssl_dhparam = /etc/certs/dhparam.pem
# UDS server location. https NEEDS valid certificate if https # 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 # Must point to tunnel ticket dispatcher URL, that is under /uds/rest/tunnel/ on tunnel server
# Valid examples: # Valid examples:
# http://www.example.com/uds/rest/tunnel/ # http://www.example.com/uds/rest/tunnel/ticket
# https://www.example.com:14333/uds/rest/tunnel/ # https://www.example.com:14333/uds/rest/tunnel/ticket
uds_server = http://172.27.0.1:8000/uds/rest/tunnel 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. # Secret to get access to admin commands (Currently only stats commands). No default for this.
# Admin commands and only allowed from "allow" ips # Admin commands and only allowed from "allow" ips