[X2Go-Commits] [x2gobroker] 01/04: Add support for dynamic cookie based auth after initial password auth. (Fixes: #447).

git-admin at x2go.org git-admin at x2go.org
Fri Mar 7 22:15:39 CET 2014


This is an automated email from the git hooks/post-receive script.

x2go pushed a commit to branch master
in repository x2gobroker.

commit 893b4ce7190ef088dc096144feb5c99be6d71cf7
Author: Mike Gabriel <mike.gabriel at das-netzwerkteam.de>
Date:   Fri Mar 7 21:37:59 2014 +0100

    Add support for dynamic cookie based auth after initial password auth. (Fixes: #447).
---
 debian/changelog                  |    6 ++
 etc/x2gobroker.conf               |   30 +++++++--
 x2gobroker/brokers/base_broker.py |  135 ++++++++++++++++++++++---------------
 x2gobroker/defaults.py            |    7 +-
 x2gobroker/web/json.py            |    9 +--
 x2gobroker/web/plain.py           |   10 ++-
 x2gobroker/web/uccs.py            |    4 +-
 7 files changed, 123 insertions(+), 78 deletions(-)

diff --git a/debian/changelog b/debian/changelog
index ced44bc..0b2dd12 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,5 +1,6 @@
 x2gobroker (0.0.3.0-0x2go1) UNRELEASED; urgency=low
 
+  [ Mike Gabriel ]
   * New upstream version (0.0.3.0):
     - Add SSH support to X2Go Session Broker. (Fixes: #153).
     - Move x2gobroker executable to /usr/bin.
@@ -113,6 +114,11 @@ x2gobroker (0.0.3.0-0x2go1) UNRELEASED; urgency=low
       sub-package.
     + Builds for EPEL-7 also have to systemd aware.
 
+  [ Josh Lukens ]
+  * New upstream version (0.0.3.0):
+    - Add support for dynamic cookie based auth after initial password auth. (Fixes:
+      #447).
+
  -- Mike Gabriel <mike.gabriel at das-netzwerkteam.de>  Fri, 07 Jun 2013 23:25:30 +0200
 
 x2gobroker (0.0.2.3-0~x2go1) unstable; urgency=low
diff --git a/etc/x2gobroker.conf b/etc/x2gobroker.conf
index 19ea93b..b8b8974 100644
--- a/etc/x2gobroker.conf
+++ b/etc/x2gobroker.conf
@@ -24,20 +24,38 @@
 
 [global]
 
-# Allow unauthenticated connections? Then set check-credentials to false.
-#check-credentials = true
+# Allow unauthenticated connections? Then set both require-password and require-cookie to false.
+
+# Veriy username/password combination sent by client
+#require-password = true
 
 # To secure server-client communication the client can start the communication
 # with a pre-set, agreed on authentication ID. Set the below value to true
 # to make the X2Go Session Broker require this feature
-#require-cookie-auth = false ### NOT-IN-USE-YET
+#require-cookie = false
 
 # X2Go supports two different cookie authentication modes (static and dynamic).
-#use-static-cookie = true ### NOT-IN-USE-YET
+# Dynamic cookies send new cookie to client on every request.  This could possibly
+# cause issues if a client ever tries multiple requests at the same time.
+#use-static-cookie = true
+
+# Once a client is authenticated their password is not revalidated until this
+# many seconds have elapsed from their initial authentication.
+#auth-timeout = 36000
+
+# Client cookies (both static and dynamic) must be stored as local files.
+# This is the directory where those files will be stored.  Please make sure
+# the permissions are set to allow the x2go broker process to write to this directory
+#cookie-directory = '/var/log/x2gobroker/cookies'
 
 # Every server-client communication (between X2Go Client and broker) has to be
-# accompanied by this initial authentication cookie.
-#my-cookie = <aaaavveeeerrrrryyyyylooonnnnggggssttrrriiinnnggg> ### NOT-IN-USE-YET
+# accompanied by this initial authentication cookie if require-cookie is set above.
+# This should be in the format of a UUID.
+#my-cookie = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+
+# By default the broker will pin user sessions to the IP address from which they
+# origionally authenticate.  If you would like to skip that check set this to false.
+#verify-ip = true
 
 # X2Go Session Broker knows about two output formats: a text/plain based output
 # and a text/json based output that is compatible with UCCS. The different outputs
diff --git a/x2gobroker/brokers/base_broker.py b/x2gobroker/brokers/base_broker.py
index 7fb3172..0fa010d 100644
--- a/x2gobroker/brokers/base_broker.py
+++ b/x2gobroker/brokers/base_broker.py
@@ -31,6 +31,7 @@ import uuid
 import netaddr
 import random
 import time
+import os.path
 
 # X2Go Broker modules
 import x2gobroker.config
@@ -726,7 +727,7 @@ class X2GoBroker(object):
         else:
             return []
 
-    def check_access(self, username='', password='', cookie=None, cookie_only=False):
+    def check_access(self, username='', password='', ip='', cookie=None):
         """\
         Check if a given user with a given password may gain access to the
         X2Go session broker.
@@ -735,80 +736,102 @@ class X2GoBroker(object):
         @type username: C{unicode}
         @param password: a password that authenticates the user against the X2Go session broker
         @type password: C{unicode}
+        @param ip: the ip address of the client
+        @type ip: C{unicode}
         @param cookie: an extra (static or dynamic) authentication token
         @type cookie: C{unicode}
-        @param cookie_only: do only check the auth_cookie, not username/password
-        @type cookie_only: C{bool}
 
         @return: returns C{True} if the authentication has been successful
-        @rtype: C{bool}
+        @rtype: C{bool},C{unicode}
 
         """
         ### FOR INTRANET LOAD BALANCER WE MAY JUST ALLOW ACCESS TO EVERYONE
         ### This is handled through the config file, normally /etc/x2go/x2gobroker.conf
 
-        if not self.config.get_value('global', 'check-credentials'):
+        if not self.config.get_value('global', 'require-password') and not self.config.get_value('global', 'require-cookie'):
             logger_broker.debug('base_broker.X2GoBroker.check_access(): access is granted without checking credentials, prevent this in {configfile}'.format(configfile=self.config_file))
-            return True
+            return True, None
         elif username == 'check-credentials' and password == 'FALSE':
             # this catches a validation check from the UCCS web frontend...
-            return False
+            return False, None
 
         ### IMPLEMENT YOUR AUTHENTICATION LOGIC IN THE self._do_authenticate(**kwargs) METHOD
         ### when inheriting from the base.X2GoBroker class.
-
-        access = False
-        if cookie_only is False:
-            access = self._do_authenticate(username=username, password=password)
-            logger_broker.debug('base_broker.X2GoBroker.check_access(): result of authentication check is: {access}'.format(access=access))
-        else:
-            access = True
-
-        ### HANDLING OF DYNAMIC AUTHENTICATION ID HASHES
-
-        # using cookie authentication as extra security?
-        if self.config.get_value('global', 'require-cookie-auth'):
-
-            if type(cookie) is types.StringType:
-                cookie = unicode(cookie)
-
-            if self.config.get_value('global', 'use-static-cookie'):
-
-                # evaluate access based on static authentication ID feature
-                access = access and ( cookie == self.config.get_value('global', 'my-cookie') )
+        if type(cookie) is types.StringType:
+            cookie = unicode(cookie)
+
+        if (((cookie == None) or (cookie == "")) and self.config.get_value('global', 'require-cookie')):
+            #cookie required but we did not get one - catch wrong cookie case later
+            logger_broker.debug('base_broker.X2GoBroker.check_access(): cookie required but none given.')
+            return False, None
+
+        # check if cookie sent was our preset cookie from config file
+        next_cookie = self.config.get_value('global', 'my-cookie')
+        access = (cookie == next_cookie )
+        logger_broker.debug('base_broker.X2GoBroker.check_access(): checking if our configured cookie was submitted: {access}'.format(access=access))
+
+        # the require cookie but not password case falls through to returning value of access
+        if self.config.get_value('global', 'require-password'):
+
+            # using files to store persistant cookie information because global variables do not work across threads in WSGI
+            cookie_directory=self.config.get_value('global', 'cookie-directory')
+            if (not os.path.isdir(cookie_directory)):
+                logger_broker.debug('base_broker.X2GoBroker.check_access(): cookie-directory {cookie_directory} does not exist trying to craete it'.format(cookie_directory=cookie_directory))
+                try:
+                    os.makedirs(cookie_directory);
+                except:
+                    logger_broker.warning('base_broker.X2GoBroker.check_access(): could not create cookie-directory {cookie_directory} failing to authenticate'.format(cookie_directory=cookie_directory))
+                    return False, None
+
+            if access or cookie == None or cookie == "":
+                # this should be the first time we have seen this user or they are using old client so verify their passwrd
+                access = self._do_authenticate(username=username, password=password)
+                logger_broker.debug('base_broker.X2GoBroker.check_access(): checking for valid password: {access}'.format(access=access))
+
+                if access:
+                    #create new cookie for this user
+                    #each user gets one or more tuples of IP, time stored as username_UUID files so they can connect from multiple sessions
+                    next_cookie = str(uuid.uuid4())
+                    fh = open(cookie_directory+"/"+username+"_"+next_cookie,"w")
+                    fh.write('{ip} {time}'.format(ip=ip, time=time.time()))
+                    fh.close()
+                    logger_broker.debug('base_broker.X2GoBroker.check_access(): Giving new cookie: {cookie} to user {username} at ip {ip}'.format(cookie=next_cookie,username=username,ip=ip))
 
             else:
-
-                # evaluate access based on dynamic authentication ID feature
-                if self._dynamic_cookie_map.has_key(username):
-                    access = access and ( cookie == self._dynamic_cookie_map[username] )
-                    if access:
-                        self._dynamic_cookie_map[username] = uuid.uuid5(namespace=cookie, name=username)
-
+                # there is a cookie but its not ours so its either wrong or subsequent password auth
+                if os.path.isfile(cookie_directory+"/"+username+"_"+cookie):
+
+                    logger_broker.debug('base_broker.X2GoBroker.check_access(): found valid auth key for user cookie: {usercookie}'.format(usercookie=username+"_"+cookie))
+                    fh=open(cookie_directory+"/"+username+"_"+cookie,"r")
+                    origip,origtime= fh.read().split()
+                    fh.close()
+                    os.unlink(cookie_directory+"/"+username+"_"+cookie)
+
+                    # found cookie - make sure IP and time are good
+                    if self.config.get_value('global', 'verify-ip') and (ip != origip):
+                        logger_broker.debug('base_broker.X2GoBroker.check_access(): IPs differ (new: {ip} old: {origip}) - rejecting user'.format(ip=ip,origip=origip))
+                        return False, None
+                    if (time.time() - float(origtime)) > self.config.get_value('global', 'auth-timeout'):
+                        logger_broker.debug('base_broker.X2GoBroker.check_access(): Too much time elapsed since origional auth - rejecting user')
+                        return False, None
+                    if self.config.get_value('global', 'use-static-cookie'):
+                        #if using static cookies keep same cookie as user presented
+                        next_cookie = cookie
+                    else:
+                        #otherwise give them new random cookie
+                        next_cookie = str(uuid.uuid4())
+
+                    logger_broker.debug('base_broker.X2GoBroker.check_access(): Giving cookie: {cookie} to ip {ip}'.format(cookie=next_cookie, ip=ip))
+                    fh = open(cookie_directory+"/"+username+"_"+next_cookie,"w")
+                    fh.write('{ip} {time}'.format(ip=ip, time=origtime))
+                    fh.close()
+                    access = True
                 else:
-                    access = access and ( cookie == self.config.get_value('global', 'my-cookie') )
-                    if access:
-                        # generate a first uuid, initialize the dynamic authencation ID security feature
-                        self._dynamic_cookie_map[username] = uuid.uuid4()
-
-        return access
-
-    def get_next_cookie(self, username):
-        """\
-        Get the next expected authentication cookie for the given user name.
-
-        @param username: query next authentication cookie for this user
-        @type username: C{unicode}
-
-        @return: returns next authentication cookie for the given username, None if no cookie has been generated, yet
-        @rtype: C{unicode} or C{None}
-
-        """
-        try:
-            return self._dynamic_cookie_map[username]
-        except KeyError:
-            return None
+                    # client sent us an unknown cookie so failing auth
+                    logger_broker.debug('base_broker.X2GoBroker.check_access(): User {username} from {ip} presented cookie {cookie} which is not recognized - rejecting user'.format(username=username, cookie=cookie, ip=ip))
+                    return False, None
 
+        return access, next_cookie
 
     def get_remote_agent(self, profile_id, exclude_agents=[], ):
         """\
diff --git a/x2gobroker/defaults.py b/x2gobroker/defaults.py
index e65fd31..9027ed0 100644
--- a/x2gobroker/defaults.py
+++ b/x2gobroker/defaults.py
@@ -180,9 +180,12 @@ X2GOBROKER_HOME = os.path.normpath(os.path.expanduser('~{broker_uid}'.format(bro
 # defaults for X2Go Sessino Broker configuration file
 X2GOBROKER_CONFIG_DEFAULTS = {
     'global': {
-        u'check-credentials': True,
-        u'require-cookie-auth': False,
+        u'require-password': True,
+        u'require-cookie': False,
         u'use-static-cookie': True,
+        u'auth-timeout': 36000,
+        u'cookie-directory': '/var/log/x2gobroker/cookies',
+        u'verify-ip': True,
         u'my-cookie': uuid.uuid4(),
         u'enable-plain-output': True,
         u'enable-json-output': True,
diff --git a/x2gobroker/web/json.py b/x2gobroker/web/json.py
index b217050..1f10b31 100644
--- a/x2gobroker/web/json.py
+++ b/x2gobroker/web/json.py
@@ -119,17 +119,14 @@ class X2GoBrokerWeb(_RequestHandler):
         output = ''
 
         logger_broker.debug ('username: {username}, password: {password}, task: {task}, profile_id: {profile_id}, cookie: {cookie}'.format(username=username, password='XXXXX', task=task, profile_id=profile_id, cookie=cookie))
-        if broker_backend.check_access(username=username, password=password, cookie=cookie):
+        access, next_cookie = broker_backend.check_access(username=username, password=password, ip=ip, cookie=cookie)
+        if access:
 
             ###
             ### CONFIRM SUCCESSFUL AUTHENTICATION FIRST
             ###
 
-            if global_config['require-cookie-auth'] and not global_config['use-static-cookie']:
-
-                ### FIXME: make up a nice protocol for this, disabled for now
-                #output += "AUTHID: {authid}<br />".format(authid=broker_backend.get_next_authid(username=data.user))
-                pass
+            ### FIXME: find good way to pass next cookie to client - stored in next_cookie
 
             ###
             ### X2GO BROKER TASKS
diff --git a/x2gobroker/web/plain.py b/x2gobroker/web/plain.py
index 9d58742..22b4964 100644
--- a/x2gobroker/web/plain.py
+++ b/x2gobroker/web/plain.py
@@ -115,17 +115,15 @@ class X2GoBrokerWeb(_RequestHandler):
         output = ''
 
         logger_broker.debug ('username: {username}, password: {password}, task: {task}, profile_id: {profile_id}, cookie: {cookie}'.format(username=username, password='XXXXX', task=task, profile_id=profile_id, cookie=cookie))
-        if broker_backend.check_access(username=username, password=password, cookie=cookie):
+        access, next_cookie = broker_backend.check_access(username=username, password=password, ip=ip, cookie=cookie)
+        if access:
 
             ###
             ### CONFIRM SUCCESSFUL AUTHENTICATION FIRST
             ###
 
-            if global_config['require-cookie-auth'] and not global_config['use-static-cookie']:
-
-                ### FIXME: make up a nice protocol for this, disabled for now
-                #output += "AUTHID: {authid}<br />".format(authid=broker_backend.get_next_authid(username=data.user))
-                pass
+            if next_cookie is not None:
+                output += "AUTHID:{authid}\n".format(authid=next_cookie)
 
             output += "Access granted\n"
             ###
diff --git a/x2gobroker/web/uccs.py b/x2gobroker/web/uccs.py
index 917704f..87dc64a 100644
--- a/x2gobroker/web/uccs.py
+++ b/x2gobroker/web/uccs.py
@@ -42,11 +42,11 @@ def credentials_validate(username, password):
     #        from x2gobroker.conf are available here...
     broker = x2gobroker.brokers.base_broker.X2GoBroker()
     broker.enable()
-    access = broker.check_access(username=username, password=password)
+    access, next_cookie = broker.check_access(username=username, password=password)
     # UCCS only allows email addresses for remote login 
     if not access and "@" in username:
         username = username.split('@')[0]
-        access = broker.check_access(username=username, password=password)
+        access, next_cookie = broker.check_access(username=username, password=password)
     if username == 'check-credentials' and password == 'FALSE':
         username = 'anonymous'
     return username, access

--
Alioth's /srv/git/_hooks_/post-receive-email on /srv/git/code.x2go.org/x2gobroker.git



More information about the x2go-commits mailing list