1
0
mirror of https://github.com/dkmstr/openuds.git synced 2024-12-22 13:34:04 +03:00

* Added new "Security" setting, so we can set up trusted sources for requests.

* Secured access from Tunneler to Broker. Now pam & html5 requests can only be done from trusted sources. (They will not work only from trusted hosts)
* Upgraded jars for transports to fix Mac NX functionallity
This commit is contained in:
Adolfo Gómez 2013-04-20 03:46:03 +00:00
parent 66461057ec
commit b51d918881
13 changed files with 59 additions and 45 deletions

View File

@ -36,7 +36,7 @@ Provides useful functions for authenticating, used by web interface.
from __future__ import unicode_literals
from functools import wraps
from django.http import HttpResponseRedirect
from django.http import HttpResponseRedirect, HttpResponseForbidden
from uds.core.util.Config import GlobalConfig
from uds.core.util import log
from uds.core import auths
@ -53,24 +53,25 @@ authLogger = logging.getLogger('authLog')
USER_KEY = 'uk'
PASS_KEY = 'pk'
def getIp(request):
def getIp(request, translateProxy = True):
'''
Obtains the IP of a Django Request, even behind a proxy
Returns the obtained IP, that is always be a valid ip address.
'''
try:
if translateProxy is False:
raise KeyError() # Do not allow HTTP_X_FORWARDED_FOR
request.ip = request.META['HTTP_X_FORWARDED_FOR'].split(",")[0]
except KeyError:
request.ip = request.META['REMOTE_ADDR']
return request.ip
# Decorator to make easier protect pages
# Decorator to make easier protect pages that needs to be logged in
def webLoginRequired(view_func):
'''
Decorator to set protection to acces page
To use this decorator, the view must receive 'response' and 'user'
example: view(response, user)
Decorator to set protection to access page
Look for samples at uds.core.web.views
'''
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
@ -96,6 +97,25 @@ def webLoginRequired(view_func):
return view_func(request, *args, **kwargs)
return _wrapped_view
# Decorator to protect pages that needs to be accessed from "trusted sites"
def trustedSourceRequired(view_func):
'''
Decorator to set protection to access page
look for sample at uds.dispatchers.pam
'''
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
'''
Wrapped function for decorator
'''
from uds.core.util import net
getIp(request, False)
if net.ipInNetwork(request.ip, GlobalConfig.TRUSTED_SOURCES.get(True)) is False:
return HttpResponseForbidden()
return view_func(request, *args, **kwargs)
return _wrapped_view
def __registerUser(authenticator, authInstance, username):
'''
Check if this user already exists on database with this authenticator, if don't, create it with defaults

View File

@ -39,7 +39,7 @@ import logging
logger = logging.getLogger(__name__)
GLOBAL_SECTION = 'UDS'
SECURITY_SECTION = 'Security'
class Config(object):
'''
@ -230,16 +230,21 @@ class GlobalConfig(object):
# If disallow login using /login url, and must go to an authenticator
DISALLOW_GLOBAL_LOGIN = Config.section(GLOBAL_SECTION).value('disallowGlobalLogin', '0')
# Allowed "trusted sources" for request
TRUSTED_SOURCES = Config.section(SECURITY_SECTION).value('Trusted Hosts', '*')
initDone = False
@staticmethod
def initialize():
try:
# All configurations are upper case
# Tries to initialize database data for global config so it is stored asap and get cached for use
for v in GlobalConfig.__dict__.itervalues():
if type(v) is Config._Value:
v.get()
if GlobalConfig.initDone is False:
# All configurations are upper case
# Tries to initialize database data for global config so it is stored asap and get cached for use
for v in GlobalConfig.__dict__.itervalues():
if type(v) is Config._Value:
v.get()
GlobalConfig.initDone = True
except:
logger.debug('Config table do not exists!!!, maybe we are installing? :-)')

View File

@ -36,7 +36,7 @@ from __future__ import unicode_literals
from django.http import HttpResponse
from uds.core.util.Cache import Cache
from uds.core.util import net
from uds.core.auths.auth import getIp
from uds.core.auths import auth
import logging
logger = logging.getLogger(__name__)
@ -49,6 +49,7 @@ CONTENT_TYPE = 'text/plain'
def dict2resp(dct):
return '\r'.join(( k + '\t' + v for k, v in dct.iteritems()))
@auth.trustedSourceRequired
def guacamole(request, tunnelId):
logger.debug('Received credentials request for tunnel id {0}'.format(tunnelId))
@ -57,23 +58,6 @@ def guacamole(request, tunnelId):
val = cache.get(tunnelId, None)
logger.debug('Value of cache element: {0}'.format(val))
# Add request source ip to request object
getIp(request)
# Ensure request for credentials are allowed
allowFrom = val['allow-from'].replace(' ', '')
# and remove allow-from from parameters
del val['allow-from']
logger.debug('Checking validity of ip in network(s) {1}'.format(request.ip, allowFrom))
if net.ipInNetwork(request.ip, allowFrom) is False:
logger.error('Ip {0} not allowed (not in range {1})'.format(request.ip, allowFrom))
raise Exception() # Ip not allowed
# Remove key from cache, just 1 use
# Cache has a limit lifetime, so we will allow to "reload" the page
# cache.remove(tunnelId)

View File

@ -33,12 +33,14 @@
from django.http import HttpResponseNotAllowed, HttpResponse
from uds.core.util.Cache import Cache
from uds.core.auths import auth
import logging
logger = logging.getLogger(__name__)
# We will use the cache to "hold" the tickets valid for users
@auth.trustedSourceRequired
def pam(request):
response = ''
cache = Cache('pam')

View File

@ -57,9 +57,7 @@ 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/https & 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)
guacamoleServer = gui.TextField(label=_('Tunnel Server'), order = 1, tooltip = _('Host of the tunnel server (use http/https & port if needed) as accesible from users'), defvalue = 'https://', length = 64)
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'))
@ -68,13 +66,8 @@ class HTML5RDPTransport(Transport):
def initialize(self, values):
if values is None:
return
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):
@ -124,7 +117,7 @@ class HTML5RDPTransport(Transport):
username = username + '@' + username
# Build params dict
params = { 'protocol':'rdp', 'hostname':ip, 'username': username, 'password': password, 'allow-from': self.allowRequestsFrom.value }
params = { 'protocol':'rdp', 'hostname':ip, 'username': username, 'password': password }
logger.debug('RDP Params: {0}'.format(params))

View File

@ -175,7 +175,7 @@ class NXTransport(Transport):
# Fix username/password acording to os manager
username, password = userService.processUserPassword(username, password)
return generateHtmlForNX(self, idUserService, idTransport, ip, username, password, extra)
return generateHtmlForNX(self, idUserService, idTransport, ip, os, username, password, extra)
def getHtmlComponent(self, theId, os, componentId):
# We use helper to keep this clean

View File

@ -33,6 +33,7 @@
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from uds.core.util import OsDetector
import logging, os, sys
logger = logging.getLogger(__name__)
@ -53,7 +54,8 @@ def simpleScrambler(data):
def generateHtmlForNX(transport, idUserService, idTransport, ip, user, password, extra):
def generateHtmlForNX(transport, idUserService, idTransport, ip, os, user, password, extra):
isMac = os['OS'] == OsDetector.Macintosh
applet = reverse('uds.web.views.transcomp', kwargs = { 'idTransport' : idTransport, 'componentId' : '1' })
# Gets the codebase, simply remove last char from applet
codebase = applet[:-1]
@ -71,9 +73,14 @@ def generateHtmlForNX(transport, idUserService, idTransport, ip, user, password,
'height:' + str(extra['height']),
'is:' + idUserService
]))
if isMac is True:
msg = '<p>' + _('In order to use this transport, you need to install first OpenNX Client for mac') + '</p>'
msg += '<p>' + _('You can oibtain it from ') + '<a href="http://opennx.net/download.html">' + _('OpenNx Website') + '</a></p>'
else:
msg = '<p>' + _('In order to use this transport, you need to install first Nomachine Nx Client version 3.5.x') + '</p>'
msg +='<p>' + _('you can obtain it for your platform from') + '<a href="http://www.nomachine.com/download.php">' + _('nochamine web site') + '</a></p>'
res = '<div idTransport="applet"><applet code="NxTransportApplet.class" codebase="%s" archive="%s" width="140" height="22"><param name="data" value="%s"/></applet></div>' % (codebase, '1', data )
res += '<div><p>' + _('In order to use this transport, you need to install first Nomachine Nx Client version 3.5.x') + '</p>'
res += '<p>' + _('you can obtain it for your platform from') + '<a href="http://www.nomachine.com/download.php">' + _('nochamine web site') + '</a></p></div>'
res += '<div>' + msg + '</div>'
return res

View File

@ -32,7 +32,7 @@
'''
from ..auths.AdminAuth import needs_credentials
from uds.core.util.Config import Config
from uds.core.util.Config import Config, GLOBAL_SECTION, SECURITY_SECTION
import logging
@ -42,7 +42,10 @@ logger = logging.getLogger(__name__)
def getConfiguration(credentials):
res = []
addCrypt = credentials.isAdmin
for cfg in Config.enumerate():
priorities = { GLOBAL_SECTION: 0, SECURITY_SECTION: 1 }
for cfg in (v[0] for v in sorted([(x, priorities.get(x.section(), 20)) for x in Config.enumerate()], key = lambda c:c[1])):
if cfg.isCrypted() is True and addCrypt is False:
continue
res.append( {'section': cfg.section(), 'key' : cfg.key(), 'value' : cfg.get(), 'crypt': cfg.isCrypted(), 'longText': cfg.isLongText() } )