From 4f327da9f318c7a707ab6dec5dc9fafa143f6452 Mon Sep 17 00:00:00 2001 From: Daniel Lenski Date: Wed, 19 May 2021 18:35:55 -0700 Subject: [PATCH] Add tests of using portal-userauthcookie to continue through gateway 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 --- tests/fake-gp-server.py | 41 ++++++++++++++++++++++++++++++---------- tests/gp-auth-and-config | 6 ++++++ 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/tests/fake-gp-server.py b/tests/fake-gp-server.py index 585c5ac4..fb284a32 100755 --- a/tests/fake-gp-server.py +++ b/tests/fake-gp-server.py @@ -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('{}'.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}'.format(portal_cookie, val) + else: + pc = '' return ''' {} 600 - '''.format(gwlist) + {}'''.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 ''' @@ -173,8 +194,8 @@ def gateway_login(): -1 4100 {preferred_ip} - - + {portal-userauthcookie} + {portal-prelogonuserauthcookie} {ipv6} '''.format(ipv6=preferred_ipv6 or '', **session) diff --git a/tests/gp-auth-and-config b/tests/gp-auth-and-config index ace70f17..289af1ef 100755 --- a/tests/gp-auth-and-config +++ b/tests/gp-auth-and-config @@ -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" -- 2.49.0