]> www.infradead.org Git - users/dwmw2/openconnect.git/commitdiff
Add tests of using portal-userauthcookie to continue through gateway
authorDaniel Lenski <dlenski@gmail.com>
Thu, 20 May 2021 01:35:55 +0000 (18:35 -0700)
committerDaniel Lenski <dlenski@gmail.com>
Tue, 15 Jun 2021 17:24:19 +0000 (10:24 -0700)
This test sets up fake-gp-server.py to require a one-time password on the
portal *and* on the gateway, and to set 'portal-userauthcookie' after
successful login to the gateway.  For OpenConnect to successfully login
within prompting for a second password, the 'portal-userauthcookie' value
from the portal must be forwarded to the gateway login request.

Signed-off-by: Daniel Lenski <dlenski@gmail.com>
tests/fake-gp-server.py
tests/gp-auth-and-config

index 585c5ac4f839ea4cb558a4b5ef8e980d3629a80c..fb284a32d6bbcedda3fc11f40433d19c936169e2 100755 (executable)
@@ -61,12 +61,18 @@ def check_form_against_session(*fields, use_query=False, on_failure=None):
 ########################################
 
 
-# Get parameters into the initial session setup in order to configure gateways, 2FA requirement
+# Get parameters into the initial session setup in order to configure:
+#   gateways: list of gateway names for portal to offer (all will point to same HOST:PORT as portal)
+#   portal_2fa: if set, require challenge-based 2FA to complete /global-protect/getconfig.esp request
+#   gateway_2fa: if set, require challenge-based 2FA to complete /ssl-vpn/login.esp request
+#   portal_cookie: if set (to 'portal-userauthcookie' or 'portal-prelogonuserauthcookie'), then
+#                  the portal getconfig response will include the named "cookie" field which should
+#                  be used to automatically continue login on the gateway
 @app.route('/global-protect/testconfig.esp', methods=('GET','POST',))
 @app.route('/ssl-vpn/testconfig.esp', methods=('GET','POST',))
 def testconfig():
-    gateways, portal_2fa, gw_2fa = request.args.get('gateways'), request.args.get('portal_2fa'), request.args.get('gw_2fa')
-    session.update(gateways=gateways and gateways.split(','),
+    gateways, portal_2fa, gw_2fa, portal_cookie = request.args.get('gateways'), request.args.get('portal_2fa'), request.args.get('gw_2fa'), request.args.get('portal_cookie')
+    session.update(gateways=gateways and gateways.split(','), portal_cookie=portal_cookie,
                    portal_2fa=portal_2fa and bool(portal_2fa), gw_2fa=gw_2fa and bool(gw_2fa))
     prelogin = '/'.join(request.path.split('/')[:-1] + ['prelogin.esp'])
     return redirect(prelogin)
@@ -109,21 +115,29 @@ def challenge_2fa(where):
 @app.route('/global-protect/getconfig.esp', methods=('POST',))
 def portal_config():
     portal_2fa = session.get('portal_2fa')
+    portal_cookie = session.get('portal_cookie')
     inputStr = request.form.get('inputStr') or None
     if portal_2fa and not inputStr:
         return challenge_2fa('portal')
     if not (request.form.get('user') and request.form.get('passwd') and inputStr == session.get('inputStr')):
         return 'Invalid username or password', 512
 
-    session.update(step='portal-config', user=request.form.get('user'), passwd=request.form.get('passwd'), inputStr=None)
+    session.update(step='portal-config', user=request.form.get('user'), passwd=request.form.get('passwd'),
+                   # clear inputStr to ensure failure if same form fields are blindly retried on another challenge form:
+                   inputStr=None)
     gateways = session.get('gateways') or ('Default gateway',)
     gwlist = ''.join('<entry name="{}:{}"><description>{}</description></entry>'.format(app.config['HOST'], app.config['PORT'], gw)
                      for gw in gateways)
+    if portal_cookie:
+        val = session[portal_cookie] = 'portal-cookie-%d' % randint(1, 10)
+        pc = '<{0}>{1}</{0}>'.format(portal_cookie, val)
+    else:
+        pc = ''
 
     return '''<?xml version="1.0" encoding="UTF-8" ?>
     <policy><gateways><external><list>{}</list></external></gateways>
     <hip-collection><hip-report-interval>600</hip-report-interval></hip-collection>
-    </policy>'''.format(gwlist)
+    {}</policy>'''.format(gwlist, pc)
 
 
 # Respond to gateway login request
@@ -131,11 +145,16 @@ def portal_config():
 def gateway_login():
     gw_2fa = session.get('gw_2fa')
     inputStr = request.form.get('inputStr') or None
-    if gw_2fa and not inputStr:
+    if session.get('portal_cookie') and request.form.get(session['portal_cookie']) == session.get(session['portal_cookie']):
+        # a correct portal_cookie explicitly allows us to bypass other gateway login forms
+        pass
+    elif gw_2fa and not inputStr:
         return challenge_2fa('gateway')
-    if not (request.form.get('user') and request.form.get('passwd') and inputStr == session.get('inputStr')):
+    elif not (request.form.get('user') and request.form.get('passwd') and inputStr == session.get('inputStr')):
         return 'Invalid username or password', 512
-    session.update(step='gateway-login', user=request.form.get('user'), passwd=request.form.get('passwd'), inputStr=None)
+    session.update(step='gateway-login', user=request.form.get('user'), passwd=request.form.get('passwd'),
+                   # clear inputStr to ensure failure if same form fields are blindly retried on another challenge form:
+                   inputStr=None)
 
     for k, v in (('jnlpReady', 'jnlpReady'), ('ok', 'Login'), ('direct', 'yes'), ('clientVer', '4100'), ('prot', 'https:')):
         if request.form.get(k) != v:
@@ -154,6 +173,8 @@ def gateway_login():
         preferred_ipv6 = None
     session.update(preferred_ip=preferred_ip, portal=portal, auth=auth, domain=domain, computer=request.form.get('computer'),
                    ipv6_support=request.form.get('ipv6-support'), preferred_ipv6=preferred_ipv6)
+    session.setdefault('portal-prelogonuserauthcookie', '')
+    session.setdefault('portal-userauthcookie', '')
     session['authcookie'] = cookify(dict(session)).decode()
 
     return '''<?xml version="1.0" encoding="utf-8"?> <jnlp> <application-desc>
@@ -173,8 +194,8 @@ def gateway_login():
             <argument>-1</argument>
             <argument>4100</argument>
             <argument>{preferred_ip}</argument>
-            <argument/>
-            <argument/>
+            <argument>{portal-userauthcookie}</argument>
+            <argument>{portal-prelogonuserauthcookie}</argument>
             <argument>{ipv6}</argument>
             </application-desc></jnlp>'''.format(ipv6=preferred_ipv6 or '', **session)
 
index ace70f176355c74d735965140d9fbb9afb7b5f18..289af1efb0c40e296f128ac8e6b1b8b3d0e77b7c 100755 (executable)
@@ -53,6 +53,12 @@ echo -n "Authenticating with username/password via gateway... "
 
 echo ok
 
+echo -n "Authenticating with username/password/token via portal, then using portal-userauthcookie to continue through gateway... "
+( echo "test" | LD_PRELOAD=libsocket_wrapper.so $OPENCONNECT --protocol=gp -q "$ADDRESS:443/global-protect/testconfig.esp?portal_2fa=1&gateway_2fa=1&portal_cookie=portal-userauthcookie" -u test --token-mode=totp --token-secret=FAKE $FINGERPRINT --cookieonly >/dev/null 2>&1) ||
+    fail $PID "Could not receive cookie from fake GlobalProtect server"
+
+echo ok
+
 echo -n "Authenticating with username/password via portal, then +token via gateway... "
 ( echo "test" | LD_PRELOAD=libsocket_wrapper.so $OPENCONNECT --protocol=gp -q "$ADDRESS:443/global-protect/testconfig.esp?gw_2fa=1" -u test --token-mode=totp --token-secret=FAKE $FINGERPRINT --cookieonly >/dev/null 2>&1) ||
     fail $PID "Could not receive cookie from fake GlobalProtect server"