1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-03-20 06:50:23 +03:00

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)
# 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)

View File

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

View File

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

View File

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

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
from .dbfile import DBFile
# Actor tokens
# Tokens
from .actor_token import ActorToken
from .tunnel_token import TunnelToken
logger = logging.getLogger(__name__)

View File

@ -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 '<ActorToken {} created on {} by {} from {}/{}>'.format(
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
# 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