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 __future__ import unicode_literals
from functools import wraps 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.Config import GlobalConfig
from uds.core.util import log from uds.core.util import log
from uds.core import auths from uds.core import auths
@ -53,24 +53,25 @@ authLogger = logging.getLogger('authLog')
USER_KEY = 'uk' USER_KEY = 'uk'
PASS_KEY = 'pk' PASS_KEY = 'pk'
def getIp(request): def getIp(request, translateProxy = True):
''' '''
Obtains the IP of a Django Request, even behind a proxy Obtains the IP of a Django Request, even behind a proxy
Returns the obtained IP, that is always be a valid ip address. Returns the obtained IP, that is always be a valid ip address.
''' '''
try: try:
if translateProxy is False:
raise KeyError() # Do not allow HTTP_X_FORWARDED_FOR
request.ip = request.META['HTTP_X_FORWARDED_FOR'].split(",")[0] request.ip = request.META['HTTP_X_FORWARDED_FOR'].split(",")[0]
except KeyError: except KeyError:
request.ip = request.META['REMOTE_ADDR'] request.ip = request.META['REMOTE_ADDR']
return request.ip 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): def webLoginRequired(view_func):
''' '''
Decorator to set protection to acces page Decorator to set protection to access page
To use this decorator, the view must receive 'response' and 'user' Look for samples at uds.core.web.views
example: view(response, user)
''' '''
@wraps(view_func) @wraps(view_func)
def _wrapped_view(request, *args, **kwargs): def _wrapped_view(request, *args, **kwargs):
@ -96,6 +97,25 @@ def webLoginRequired(view_func):
return view_func(request, *args, **kwargs) return view_func(request, *args, **kwargs)
return _wrapped_view 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): def __registerUser(authenticator, authInstance, username):
''' '''
Check if this user already exists on database with this authenticator, if don't, create it with defaults 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__) logger = logging.getLogger(__name__)
GLOBAL_SECTION = 'UDS' GLOBAL_SECTION = 'UDS'
SECURITY_SECTION = 'Security'
class Config(object): class Config(object):
''' '''
@ -230,16 +230,21 @@ class GlobalConfig(object):
# If disallow login using /login url, and must go to an authenticator # If disallow login using /login url, and must go to an authenticator
DISALLOW_GLOBAL_LOGIN = Config.section(GLOBAL_SECTION).value('disallowGlobalLogin', '0') 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 initDone = False
@staticmethod @staticmethod
def initialize(): def initialize():
try: try:
# All configurations are upper case if GlobalConfig.initDone is False:
# Tries to initialize database data for global config so it is stored asap and get cached for use # All configurations are upper case
for v in GlobalConfig.__dict__.itervalues(): # Tries to initialize database data for global config so it is stored asap and get cached for use
if type(v) is Config._Value: for v in GlobalConfig.__dict__.itervalues():
v.get() if type(v) is Config._Value:
v.get()
GlobalConfig.initDone = True
except: except:
logger.debug('Config table do not exists!!!, maybe we are installing? :-)') 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 django.http import HttpResponse
from uds.core.util.Cache import Cache from uds.core.util.Cache import Cache
from uds.core.util import net from uds.core.util import net
from uds.core.auths.auth import getIp from uds.core.auths import auth
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -49,6 +49,7 @@ CONTENT_TYPE = 'text/plain'
def dict2resp(dct): def dict2resp(dct):
return '\r'.join(( k + '\t' + v for k, v in dct.iteritems())) return '\r'.join(( k + '\t' + v for k, v in dct.iteritems()))
@auth.trustedSourceRequired
def guacamole(request, tunnelId): def guacamole(request, tunnelId):
logger.debug('Received credentials request for tunnel id {0}'.format(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) 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 # Remove key from cache, just 1 use
# Cache has a limit lifetime, so we will allow to "reload" the page # Cache has a limit lifetime, so we will allow to "reload" the page
# cache.remove(tunnelId) # cache.remove(tunnelId)

View File

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

View File

@ -57,9 +57,7 @@ class HTML5RDPTransport(Transport):
iconFile = 'rdp.png' iconFile = 'rdp.png'
needsJava = False # If this transport needs java for rendering 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) 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)
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')) 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')) 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')) 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): def initialize(self, values):
if values is None: if values is None:
return return
a = ''
if self.guacamoleServer.value[0:4] != 'http': if self.guacamoleServer.value[0:4] != 'http':
raise Transport.ValidationException(_('The server must be http or https')) 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 # Same check as normal RDP transport
def isAvailableFor(self, ip): def isAvailableFor(self, ip):
@ -124,7 +117,7 @@ class HTML5RDPTransport(Transport):
username = username + '@' + username username = username + '@' + username
# Build params dict # 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)) logger.debug('RDP Params: {0}'.format(params))

View File

@ -175,7 +175,7 @@ class NXTransport(Transport):
# Fix username/password acording to os manager # Fix username/password acording to os manager
username, password = userService.processUserPassword(username, password) 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): def getHtmlComponent(self, theId, os, componentId):
# We use helper to keep this clean # We use helper to keep this clean

View File

@ -33,6 +33,7 @@
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from uds.core.util import OsDetector
import logging, os, sys import logging, os, sys
logger = logging.getLogger(__name__) 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' }) applet = reverse('uds.web.views.transcomp', kwargs = { 'idTransport' : idTransport, 'componentId' : '1' })
# Gets the codebase, simply remove last char from applet # Gets the codebase, simply remove last char from applet
codebase = applet[:-1] codebase = applet[:-1]
@ -71,9 +73,14 @@ def generateHtmlForNX(transport, idUserService, idTransport, ip, user, password,
'height:' + str(extra['height']), 'height:' + str(extra['height']),
'is:' + idUserService '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 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 += '<div>' + msg + '</div>'
res += '<p>' + _('you can obtain it for your platform from') + '<a href="http://www.nomachine.com/download.php">' + _('nochamine web site') + '</a></p></div>'
return res return res

View File

@ -32,7 +32,7 @@
''' '''
from ..auths.AdminAuth import needs_credentials 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 import logging
@ -42,7 +42,10 @@ logger = logging.getLogger(__name__)
def getConfiguration(credentials): def getConfiguration(credentials):
res = [] res = []
addCrypt = credentials.isAdmin 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: if cfg.isCrypted() is True and addCrypt is False:
continue continue
res.append( {'section': cfg.section(), 'key' : cfg.key(), 'value' : cfg.get(), 'crypt': cfg.isCrypted(), 'longText': cfg.isLongText() } ) res.append( {'section': cfg.section(), 'key' : cfg.key(), 'value' : cfg.get(), 'crypt': cfg.isCrypted(), 'longText': cfg.isLongText() } )