1
0
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:
Adolfo Gómez 2013-04-03 02:54:35 +00:00
parent 0bb7cecf8f
commit 3140dd0bca
11 changed files with 199 additions and 52 deletions

View File

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

View File

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

View File

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

View File

@ -30,6 +30,7 @@
'''
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
TEMP_ENV = 'temporary'
GLOBAL_ENV = 'global'

View File

@ -30,6 +30,8 @@
'''
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
class Serializable(object):
'''

View File

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

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

View File

@ -30,6 +30,7 @@
'''
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
def __init__():
'''

View File

@ -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__)
@ -47,19 +50,31 @@ def dict2resp(dct):
def guacamole(request, tunnelId):
logger.debug('Received credentials request for tunnel id {0}'.format(tunnelId))
try:
cache = Cache('guacamole')
val = cache.get(tunnelId, None)
cache = Cache('guacamole')
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)

View File

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

View File

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