mirror of
https://github.com/dkmstr/openuds.git
synced 2025-01-08 21:18:00 +03:00
* Added new module "net" under uds.core.util
* Moved out some method from models to net * Started a generic network validation/retrieval function * Added security to html5 guacamole proxy to UDS, allowing to restrict requests for credentials from an IP * Including "from __future__ import unicode_literals" (step forward python 3)
This commit is contained in:
parent
0bb7cecf8f
commit
3140dd0bca
@ -88,6 +88,7 @@ encoding//src/uds/core/util/UniqueNameGenerator.py=utf-8
|
||||
encoding//src/uds/core/util/connection.py=utf-8
|
||||
encoding//src/uds/core/util/log.py=utf-8
|
||||
encoding//src/uds/core/util/modfinder.py=utf-8
|
||||
encoding//src/uds/core/util/net.py=utf-8
|
||||
encoding//src/uds/core/util/stats/__init__.py=utf-8
|
||||
encoding//src/uds/core/util/stats/charts.py=utf-8
|
||||
encoding//src/uds/core/util/stats/counters.py=utf-8
|
||||
|
@ -35,7 +35,7 @@
|
||||
from django.utils.translation import ugettext_noop as _
|
||||
from uds.core.auths import Authenticator
|
||||
from uds.core.auths.GroupsManager import GroupsManager
|
||||
from uds.models import Network
|
||||
from uds.core.util import net
|
||||
from uds.core.util.Config import Config
|
||||
import logging, random, string
|
||||
|
||||
@ -74,11 +74,11 @@ class IPAuth(Authenticator):
|
||||
def getGroups(self, ip, groupsManager):
|
||||
# these groups are a bit special. They are in fact ip-ranges, and we must check that the ip is in betwen
|
||||
# The ranges are stored in group names
|
||||
ip = Network.ipToLong(ip)
|
||||
ip = net.ipToLong(ip)
|
||||
for g in groupsManager.getGroupsNames():
|
||||
rangeStart, rangeEnd = g.split('-')
|
||||
rangeStart = Network.ipToLong(rangeStart)
|
||||
rangeEnd = Network.ipToLong(rangeEnd)
|
||||
rangeStart = net.ipToLong(rangeStart)
|
||||
rangeEnd = net.ipToLong(rangeEnd)
|
||||
if ip >= rangeStart and ip <= rangeEnd:
|
||||
groupsManager.validate(g)
|
||||
|
||||
|
@ -30,6 +30,7 @@
|
||||
'''
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from uds.core.ui.UserInterface import UserInterface
|
||||
|
@ -30,6 +30,7 @@
|
||||
'''
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
TEMP_ENV = 'temporary'
|
||||
GLOBAL_ENV = 'global'
|
||||
|
@ -30,6 +30,8 @@
|
||||
'''
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
class Serializable(object):
|
||||
'''
|
||||
|
@ -33,6 +33,8 @@ Provides useful functions for authenticating, used by web interface.
|
||||
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from functools import wraps
|
||||
from django.http import HttpResponseRedirect
|
||||
from uds.core.util.Config import GlobalConfig
|
||||
|
142
server/src/uds/core/util/net.py
Normal file
142
server/src/uds/core/util/net.py
Normal file
@ -0,0 +1,142 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# 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
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
import re
|
||||
|
||||
from exceptions import ValueError
|
||||
|
||||
# Test patters for networks
|
||||
reCIDR = re.compile('^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/([0-9]{1,2})$')
|
||||
reMask = re.compile('^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})netmask([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$')
|
||||
re1Asterisk = re.compile('^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.\*$')
|
||||
re2Asterisk = re.compile('^([0-9]{1,3})\.([0-9]{1,3})\.\*(\.\*)?$')
|
||||
re3Asterisk = re.compile('^([0-9]{1,3})\.\*(\.\*)?(\.\*)?$')
|
||||
reRange = re.compile('^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})-([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$')
|
||||
reHost = re.compile('^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$')
|
||||
|
||||
def ipToLong(ip):
|
||||
'''
|
||||
convert decimal dotted quad string to long integer
|
||||
'''
|
||||
try:
|
||||
hexn = ''.join(["%02X" % long(i) for i in ip.split('.')])
|
||||
return long(hexn, 16)
|
||||
except:
|
||||
return 0 # Invalid values will map to "0.0.0.0" --> 0
|
||||
|
||||
def longToIp(n):
|
||||
'''
|
||||
convert long int to dotted quad string
|
||||
'''
|
||||
try:
|
||||
d = 256 * 256 * 256
|
||||
q = []
|
||||
while d > 0:
|
||||
m,n = divmod(n,d)
|
||||
q.append(str(m))
|
||||
d = d/256
|
||||
|
||||
return '.'.join(q)
|
||||
except:
|
||||
return '0.0.0.0' # Invalid values will map to "0.0.0.0"
|
||||
|
||||
|
||||
def networksFromString(strNets, allowMultipleNetworks = True):
|
||||
'''
|
||||
Parses the network from strings in this forms:
|
||||
- A.* (or A.*.* or A.*.*.*)
|
||||
- A.B.* (or A.B.*.* )
|
||||
- A.B.C.* (i.e. 192.168.0.*)
|
||||
- A.B.C.D/N (i.e. 192.168.0.0/24)
|
||||
- A.B.C.D netmask X.X.X.X (i.e. 192.168.0.0 netmask 255.255.255.0)
|
||||
- A.B.C.D - E.F.G.D (i.e. 192-168.0.0-192.168.0.255)
|
||||
- A.B.C.D
|
||||
If allowMultipleNetworks is True, it allows ',' and ';' separators (and, ofc, more than 1 network)
|
||||
Returns a list of networks tuples in the form [(start1, end1), (start2, end2) ...]
|
||||
'''
|
||||
def check(*args):
|
||||
for n in args:
|
||||
if int(n) < 0 or int(n) > 255:
|
||||
raise Exception()
|
||||
|
||||
def toNum(*args):
|
||||
start = 256*256*256
|
||||
val = 0
|
||||
for n in args:
|
||||
val += start*int(n)
|
||||
start /= 256
|
||||
return val
|
||||
|
||||
def maskFromBits(nBits):
|
||||
v = 0
|
||||
for n in xrange(nBits):
|
||||
v |= 1<<(31-n)
|
||||
return v
|
||||
|
||||
nets = strNets.replace(' ', '')
|
||||
if allowMultipleNetworks is True:
|
||||
res = []
|
||||
for strNet in re.split('[;,]',nets):
|
||||
if strNet != '':
|
||||
res.append(networksFromString(strNet, False))
|
||||
return res
|
||||
|
||||
try:
|
||||
# Test patterns
|
||||
m = reCIDR.match(strNets)
|
||||
if m is not None:
|
||||
check(*m.groups())
|
||||
bits = int(m.group(5))
|
||||
if bits < 0 | bits > 32:
|
||||
raise Exception()
|
||||
val = toNum(*m.groups())
|
||||
bits = maskFromBits(bits)
|
||||
noBits = ~bits & 0xffffffff
|
||||
return (val&bits, val|noBits)
|
||||
|
||||
m = reMask.match(strNets)
|
||||
if m is not None:
|
||||
check(*m.groups())
|
||||
val = toNum(*(m.group(i+1) for i in xrange(4)))
|
||||
bits = toNum(*(m.group(i+5) for i in xrange(4)))
|
||||
noBits = ~bits & 0xffffffff
|
||||
return (val&bits, val|noBits)
|
||||
|
||||
# No pattern recognized, invalid network
|
||||
raise Exception()
|
||||
except:
|
||||
raise ValueError(nets)
|
||||
|
||||
|
||||
|
||||
|
@ -30,6 +30,7 @@
|
||||
'''
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
def __init__():
|
||||
'''
|
||||
|
@ -31,8 +31,11 @@
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.http import HttpResponse
|
||||
from uds.core.util.Cache import Cache
|
||||
from uds.core.util import net
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -48,18 +51,30 @@ def dict2resp(dct):
|
||||
def guacamole(request, tunnelId):
|
||||
logger.debug('Received credentials request for tunnel id {0}'.format(tunnelId))
|
||||
|
||||
cache = Cache('guacamole')
|
||||
try:
|
||||
cache = Cache('guacamole')
|
||||
|
||||
val = cache.get(tunnelId, None)
|
||||
val = cache.get(tunnelId, None)
|
||||
|
||||
if val is None:
|
||||
# Ensure request for credentials are allowed
|
||||
allowFrom = val['allow-from'].replace(' ', '')
|
||||
# and remove allow-from from parameters
|
||||
del val['allow-from']
|
||||
|
||||
allowFrom = net.networksFromString(allowFrom)
|
||||
|
||||
|
||||
# Remove key from cache, just 1 use
|
||||
# Cache has a limit lifetime, so we will allow to "reload" the page
|
||||
# cache.remove(tunnelId)
|
||||
|
||||
#response = 'protocol\trdp\rhostname\tw7adolfo\rusername\tadmin\rpassword\ttemporal'
|
||||
response = dict2resp(val)
|
||||
|
||||
except:
|
||||
return HttpResponse(ERROR, content_type=CONTENT_TYPE)
|
||||
|
||||
# Remove key from cache, just 1 use
|
||||
# Cache has a limit lifetime, so we will allow to "reload" the page
|
||||
# cache.remove(tunnelId)
|
||||
|
||||
#response = 'protocol\trdp\rhostname\tw7adolfo\rusername\tadmin\rpassword\ttemporal'
|
||||
response = dict2resp(val)
|
||||
|
||||
|
||||
return HttpResponse(response, content_type=CONTENT_TYPE)
|
||||
|
@ -40,6 +40,7 @@ from uds.core.Environment import Environment
|
||||
from uds.core.db.LockingManager import LockingManager
|
||||
from uds.core.util.State import State
|
||||
from uds.core.util import log
|
||||
from uds.core.util import net
|
||||
from uds.core.services.Exceptions import InvalidServiceException
|
||||
from datetime import datetime, timedelta
|
||||
from time import mktime
|
||||
@ -463,7 +464,7 @@ class Transport(models.Model):
|
||||
'''
|
||||
if self.networks.count() == 0:
|
||||
return True
|
||||
ip = Network.ipToLong(ip)
|
||||
ip = net.ipToLong(ip)
|
||||
if self.nets_positive:
|
||||
return self.networks.filter(net_start__lte=ip, net_end__gte=ip).count() > 0
|
||||
else:
|
||||
@ -1971,40 +1972,12 @@ class Network(models.Model):
|
||||
net_end = models.BigIntegerField(db_index = True)
|
||||
transports = models.ManyToManyField(Transport, related_name='networks', db_table='uds_net_trans')
|
||||
|
||||
@staticmethod
|
||||
def ipToLong(ip):
|
||||
'''
|
||||
convert decimal dotted quad string to long integer
|
||||
'''
|
||||
try:
|
||||
hexn = ''.join(["%02X" % long(i) for i in ip.split('.')])
|
||||
return long(hexn, 16)
|
||||
except:
|
||||
return 0 # Invalid values will map to "0.0.0.0" --> 0
|
||||
|
||||
@staticmethod
|
||||
def longToIp(n):
|
||||
'''
|
||||
convert long int to dotted quad string
|
||||
'''
|
||||
try:
|
||||
d = 256 * 256 * 256
|
||||
q = []
|
||||
while d > 0:
|
||||
m,n = divmod(n,d)
|
||||
q.append(str(m))
|
||||
d = d/256
|
||||
|
||||
return '.'.join(q)
|
||||
except:
|
||||
return '0.0.0.0' # Invalid values will map to "0.0.0.0"
|
||||
|
||||
@staticmethod
|
||||
def networksFor(ip):
|
||||
'''
|
||||
Returns the networks that are valid for specified ip in dotted quad (xxx.xxx.xxx.xxx)
|
||||
'''
|
||||
ip = Network.ipToLong(ip)
|
||||
ip = net.ipToLong(ip)
|
||||
return Network.objects.filter(net_start__lte=ip, net_end__gte=ip)
|
||||
|
||||
@staticmethod
|
||||
@ -2017,7 +1990,7 @@ class Network(models.Model):
|
||||
|
||||
netEnd: Network end
|
||||
'''
|
||||
return Network.objects.create(name=name, net_start = Network.ipToLong(netStart), net_end = Network.ipToLong(netEnd))
|
||||
return Network.objects.create(name=name, net_start = net.ipToLong(netStart), net_end = Network.ipToLong(netEnd))
|
||||
|
||||
@property
|
||||
def netStart(self):
|
||||
@ -2027,7 +2000,7 @@ class Network(models.Model):
|
||||
Returns:
|
||||
string representing the dotted quad of this network start
|
||||
'''
|
||||
return Network.longToIp(self.net_start)
|
||||
return net.longToIp(self.net_start)
|
||||
|
||||
@property
|
||||
def netEnd(self):
|
||||
@ -2037,7 +2010,7 @@ class Network(models.Model):
|
||||
Returns:
|
||||
string representing the dotted quad of this network end
|
||||
'''
|
||||
return Network.longToIp(self.net_end)
|
||||
return net.longToIp(self.net_end)
|
||||
|
||||
def update(self, name, netStart, netEnd):
|
||||
'''
|
||||
@ -2051,10 +2024,10 @@ class Network(models.Model):
|
||||
netEnd: new Network end (quad dotted)
|
||||
'''
|
||||
self.name = name
|
||||
self.net_start = Network.ipToLong(netStart)
|
||||
self.net_end = Network.ipToLong(netEnd)
|
||||
self.net_start = net.ipToLong(netStart)
|
||||
self.net_end = net.ipToLong(netEnd)
|
||||
self.save()
|
||||
|
||||
def __unicode__(self):
|
||||
return u'Network {0} from {1} to {2}'.format(self.name, Network.longToIp(self.net_start), Network.longToIp(self.net_end))
|
||||
return u'Network {0} from {1} to {2}'.format(self.name, net.longToIp(self.net_start), net.longToIp(self.net_end))
|
||||
|
||||
|
@ -31,9 +31,12 @@
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_noop as _
|
||||
from uds.core.ui.UserInterface import gui
|
||||
from uds.core.util.Cache import Cache
|
||||
from uds.core.util import net
|
||||
from uds.core.transports.BaseTransport import Transport
|
||||
from uds.core.util import connection
|
||||
|
||||
@ -54,7 +57,9 @@ class HTML5RDPTransport(Transport):
|
||||
iconFile = 'rdp.png'
|
||||
needsJava = False # If this transport needs java for rendering
|
||||
|
||||
guacamoleServer = gui.TextField(label=_('Tunnel Server'), order = 1, tooltip = _('Host of the tunnel server (use http/http & port if needed)'), defvalue = 'https://')
|
||||
guacamoleServer = gui.TextField(label=_('Tunnel Server'), order = 1, tooltip = _('Host of the tunnel server (use http/http & port if needed)'), defvalue = 'https://', length = 64)
|
||||
allowRequestsFrom = gui.TextField(label=_('Allowed hosts'), order = 1, tooltip = _('Hosts allowed to ask for credentials for users (use * for all host, but not recommended). Comma separated list'),
|
||||
defvalue = '*', length = 256)
|
||||
useEmptyCreds = gui.CheckBoxField(label = _('Empty creds'), order = 2, tooltip = _('If checked, the credentials used to connect will be emtpy'))
|
||||
fixedName = gui.TextField(label=_('Username'), order = 3, tooltip = _('If not empty, this username will be always used as credential'))
|
||||
fixedPassword = gui.PasswordField(label=_('Password'), order = 4, tooltip = _('If not empty, this password will be always used as credential'))
|
||||
@ -66,6 +71,10 @@ class HTML5RDPTransport(Transport):
|
||||
a = ''
|
||||
if self.guacamoleServer.value[0:4] != 'http':
|
||||
raise Transport.ValidationException(_('The server must be http or https'))
|
||||
try:
|
||||
net.networksFromString(self.allowRequestsFrom.value)
|
||||
except Exception as e:
|
||||
raise Transport.ValidationException(_('Invalid network: {0}').format(str(e)))
|
||||
|
||||
# Same check as normal RDP transport
|
||||
def isAvailableFor(self, ip):
|
||||
@ -115,7 +124,7 @@ class HTML5RDPTransport(Transport):
|
||||
username = username + '@' + username
|
||||
|
||||
# Build params dict
|
||||
params = { 'protocol':'rdp', 'hostname':ip, 'username': username, 'password': password }
|
||||
params = { 'protocol':'rdp', 'hostname':ip, 'username': username, 'password': password, 'allow-from': self.allowRequestsFrom.value }
|
||||
|
||||
logger.debug('RDP Params: {0}'.format(params))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user