forked from shaba/openuds
Adding tunnelers tokens for increased security
This commit is contained in:
parent
25736f61b8
commit
bddb9355c8
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
85
server/src/uds/REST/methods/tunnel_token.py
Normal file
85
server/src/uds/REST/methods/tunnel_token.py
Normal 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
|
29
server/src/uds/migrations/0042_auto_20210628_1533.py
Normal file
29
server/src/uds/migrations/0042_auto_20210628_1533.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
@ -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__)
|
||||||
|
@ -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
|
||||||
|
60
server/src/uds/models/tunnel_token.py
Normal file
60
server/src/uds/models/tunnel_token.py
Normal 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
|
||||||
|
)
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user