]> www.infradead.org Git - users/dwmw2/openconnect.git/commitdiff
Add tests of GlobalProtect auth with gateway selection and challenge-based 2FA
authorDaniel Lenski <dlenski@gmail.com>
Thu, 29 Apr 2021 18:18:47 +0000 (11:18 -0700)
committerDaniel Lenski <dlenski@gmail.com>
Mon, 3 May 2021 21:50:21 +0000 (14:50 -0700)
Signed-off-by: Daniel Lenski <dlenski@gmail.com>
tests/fake-gp-server.py
tests/gp-auth-and-config

index 375d9d75b2cfe223d9ba57a9d65f7720d52d6ee1..11e905c7a983184321dc6f75c8e2e87f20ad50b1 100755 (executable)
@@ -60,16 +60,22 @@ def check_form_against_session(*fields, use_query=False, on_failure=None):
 ########################################
 
 
-# TODO: need a way to get parameters into the initial session setup (just as
-# fake-{f5,fortinet,juniper}-server.py do), in order to configure gateways,
-# 2FA requirement, etc.
+# Get parameters into the initial session setup in order to configure gateways, 2FA requirement
+@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(','),
+                   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)
 
 
 # Respond to initial prelogin requests
 @app.route('/global-protect/prelogin.esp', methods=('GET','POST',))
 @app.route('/ssl-vpn/prelogin.esp', methods=('GET','POST',))
 def prelogin():
-    session.update(step='prelogin')
+    session.update(step='%s-prelogin' % ('portal' if 'global-protect' in request.path else 'gateway'))
     return '''
 <prelogin-response>
 <status>Success</status>
@@ -85,13 +91,30 @@ def prelogin():
 </prelogin-response>'''.format(request.path)
 
 
+def challenge_2fa(where):
+    # select a random inputStr of 4 hex digits, and randomly return challenge in either XML or Javascript-y form
+    inputStr = '%04x' % randint(0x1000, 0xffff)
+    session.update(step='%s-2FA' % where, inputStr=inputStr)
+    if randint(1, 2) == 1:
+        tmpl = '<challenge><respmsg>2FA challenge from %s</respmsg><inputstr>%s</inputstr></challenge>'
+    else:
+        tmpl = ('var respStatus = "Challenge";\n'
+                'var respMsg = "2FA challenge from %s";\n'
+                'thisForm.inputStr.value = "%s";\n')
+    return tmpl % (where, inputStr)
+
+
 # Respond to portal getconfig request
 @app.route('/global-protect/getconfig.esp', methods=('POST',))
 def portal_config():
-    session.update(step='portal-config', user=request.form.get('user'), passwd=request.form.get('passwd'))
-    if not (request.form.get('user') and request.form.get('passwd')):
+    portal_2fa = session.get('portal_2fa')
+    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)
     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)
@@ -105,10 +128,13 @@ def portal_config():
 # Respond to gateway login request
 @app.route('/ssl-vpn/login.esp', methods=('POST',))
 def gateway_login():
-    session.update(step='gateway-login')
-    if not (request.form.get('user') and request.form.get('passwd')):
+    gw_2fa = session.get('gw_2fa')
+    inputStr = request.form.get('inputStr') or None
+    if 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')):
         return 'Invalid username or password', 512
-    session.update(user=request.form.get('user'), passwd=request.form.get('passwd'))
+    session.update(step='gateway-login', user=request.form.get('user'), passwd=request.form.get('passwd'), inputStr=None)
 
     for k, v in (('jnlpReady', 'jnlpReady'), ('ok', 'Login'), ('direct', 'yes'), ('clientVer', '4100'), ('prot', 'https:')):
         if request.form.get(k) != v:
index 48e37a9249a546c5fd8f297a5088a783f7485388..a66681c821c880855bdf4de3baac03e1fd1a95d9 100755 (executable)
@@ -41,13 +41,29 @@ echo -n "Authenticating with username/password via portal... "
 
 echo ok
 
+echo -n "Authenticating with username/password, and selecting gateway, via portal... "
+( echo "test" | LD_PRELOAD=libsocket_wrapper.so $OPENCONNECT --protocol=gp -q "$ADDRESS:443/global-protect/testconfig.esp?gateways=foo,bar,baz" --authgroup=bar -u test $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 gateway... "
 ( echo "test" | LD_PRELOAD=libsocket_wrapper.so $OPENCONNECT --protocol=gp -q $ADDRESS:443/gateway -u test $FINGERPRINT --cookieonly >/dev/null 2>&1) ||
     fail $PID "Could not receive cookie from fake GlobalProtect server"
 
 echo ok
 
-# TODO: add tests with 2FA
+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"
+
+echo ok
+
+echo -n "Authenticating with username/password/token via gateway... "
+( echo "test" | LD_PRELOAD=libsocket_wrapper.so $OPENCONNECT --protocol=gp -q "$ADDRESS:443/ssl-vpn/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"
+
+ok
 
 echo -n "Authenticating with username/password via portal, then proceeding to tunnel stage... "
 echo "test" | LD_PRELOAD=libsocket_wrapper.so $OPENCONNECT --protocol=gp -q $ADDRESS:443/portal -u test $FINGERPRINT >/dev/null 2>&1