From: Daniel Lenski Date: Mon, 22 Feb 2021 02:44:22 +0000 (-0800) Subject: make F5 and Fortinet tests go through config-pulling (up to the point of tunnel conne... X-Git-Tag: v8.20~325^2~10 X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=db17e88191642627eb851602523bf1cd39eff0f4;p=users%2Fdwmw2%2Fopenconnect.git make F5 and Fortinet tests go through config-pulling (up to the point of tunnel connection), rather than stopping after authentication The fake servers don't actually implement the tunnel, but they do implement the pre-tunnel configuration endpoints, and then simulate a "cookie rejected" response upon tunnel connection. OpenConnect distinguishes “cookie rejected” errors via an exit status of 2, whereas any other failure results in an exit status of 1. Signed-off-by: Daniel Lenski --- diff --git a/f5.c b/f5.c index e2210fe1..b36cdfaf 100644 --- a/f5.c +++ b/f5.c @@ -512,7 +512,7 @@ int f5_connect(struct openconnect_info *vpninfo) vpn_progress(vpninfo, PRG_ERR, _("Unexpected %d result from server\n"), ret); - ret = -EINVAL; + ret = (ret == 504) ? -EPERM : -EINVAL; goto out; } diff --git a/ppp.c b/ppp.c index ec7a8805..72ec1d40 100644 --- a/ppp.c +++ b/ppp.c @@ -1029,16 +1029,20 @@ int ppp_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable) * of the first packet */ if (ppp->check_http_response) { + const char *eol; ppp->check_http_response = 0; if (!memcmp(eh, "HTTP/", 5)) { const char *sol = (const char *)eh; const char *eol = memchr(sol, '\r', len) ?: memchr(sol, '\n', len); + const char *sp1 = memchr(sol, ' ', len); + const char *sp2 = memchr(sp1+1, ' ', len - (sp1-sol) + 1); + int status = sp1 && sp2 ? atoi(sp1+1) : -1; if (eol) len = eol - sol; vpn_progress(vpninfo, PRG_ERR, _("Got unexpected HTTP response: %.*s\n"), len, sol); vpninfo->quit_reason = "Received HTTP response (not a PPP packet)"; - return -EINVAL; + return (status >= 400 && status <= 499) ? -EPERM : -EINVAL; } } diff --git a/tests/Makefile.am b/tests/Makefile.am index 6b16a7a2..505aee09 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -62,8 +62,8 @@ if HAVE_CWRAP dist_check_SCRIPTS += auth-username-pass auth-certificate auth-nonascii cert-fingerprint id-test obsolete-server-crypto pfs if HAVE_PYTHON36_FLASK -dist_check_SCRIPTS += auth-fortinet -dist_check_SCRIPTS += auth-f5 +dist_check_SCRIPTS += fortinet-auth-and-config +dist_check_SCRIPTS += f5-auth-and-config endif if TEST_PKCS11 diff --git a/tests/auth-f5 b/tests/f5-auth-and-config similarity index 76% rename from tests/auth-f5 rename to tests/f5-auth-and-config index 8f76b798..c70eeb76 100755 --- a/tests/auth-f5 +++ b/tests/f5-auth-and-config @@ -41,6 +41,13 @@ echo -n "Authenticating with username/password... " echo ok +echo -n "Authenticating with username/password, then proceeding to tunnel stage... " +echo "test" | LD_PRELOAD=libsocket_wrapper.so $OPENCONNECT --protocol=f5 -q $ADDRESS:443 -u test $FINGERPRINT >/dev/null 2>&1 +test $? = 2 || # what OpenConnect returns when server rejects cookie upon tunnel connection, as the fake server does + fail $PID "Something went wrong in fake F5 server (other than the expected rejection of cookie)" + +echo ok + cleanup exit 0 diff --git a/tests/fake-f5-server.py b/tests/fake-f5-server.py index abb5c02a..5981ce75 100755 --- a/tests/fake-f5-server.py +++ b/tests/fake-f5-server.py @@ -105,6 +105,91 @@ def post_policy(): return resp +# Respond to 'GET /vdesk/vpn/index.php3?outform=xml&client_version=2.0 with an XML config +# [Save VPN resource name in the session for verification of client state later] +@app.route('/vdesk/vpn/index.php3') +@require_MRHSession +def profile_params(): + print(request.args) + assert request.args.get('outform') == 'xml' and request.args.get('client_version') == '2.0' + vpn_name = 'demo%d_vpn_resource' % random.randint(1, 100) + session.update(step='GET-profile-params', resourcename='/Common/'+vpn_name) + # print(session) + + return (f''' + + + + {vpn_name} + /Common/{vpn_name} + resourcename=/Common/{vpn_name} + + ''', + {'content-type': 'application/xml'}) + +# Respond to 'GET /vdesk/vpn/connect.php3?outform=xml&client_version=2.0&resourcename=RESOURCENAME +# with an ugliest-XML-you've-ever-seen config. +# [Save random HDLC flag and ur_Z for verification later.] +@app.route('/vdesk/vpn/connect.php3') +@require_MRHSession +@check_form_against_session('resourcename', use_query=True) +def options(): + assert request.args.get('outform') == 'xml' and request.args.get('client_version') == '2.0' + session.update(hdlc_framing=['no','yes'][random.randint(0, 1)], + Z=session['resourcename'] + str(random.randint(1, 100)), + ipv4='yes', ipv6=['no','yes'][random.randint(0, 1)], + sess=request.cookies['MRHSession'] + str(random.randint(1, 100))) + + return (f''' + + + {session['Z']} + {session['sess']} + {session['resourcename']} + {app.config['HOST']} + {app.config['PORT']} + {app.config['HOST']} + {app.config['PORT']} + https + 900 + {int(session['ipv4']=='yes')} + {int(session['ipv6']=='yes')} + 1 + {app.config['PORT']} + + 1.1.1.1 + 8.8.8.8 + 2606:4700:4700::1111 + 2001:4860:4860::8888 + + foo.com + 2 + 10.11.10.10/32 10.11.1.0/24 + ::/1 8000::/1 + + * + + {session['hdlc_framing']} + + ''', + {'content-type': 'application/xml'}) + + +# Respond to faux-CONNECT 'GET /myvpn' with 504 Gateway Timeout +# (what the real F5 server responds with when it doesn't like the parameters, intended +# to trigger "cookie rejected" error in OpenConnect) +@app.route('/myvpn') +@check_form_against_session('sess', 'hdlc_framing', 'ipv4', 'ipv6', 'Z', use_query=True) +def tunnel(): + try: + base64.urlsafe_b64decode(request.args.get('hostname') or None) + except (ValueError, TypeError): + raise AssertionError('Hostname is not a base64 string') + return make_response('', 504, {'X-VPN-Client-IP': '10.11.1.2', 'X-VPN-Client-IPv6': '2601::f00f:1234'}) + + # Respond to 'GET /remote/logout' by clearing session and MRHSession @app.route('/remote/logout') @require_MRHSession diff --git a/tests/fake-fortinet-server.py b/tests/fake-fortinet-server.py index 9d51bfbd..1f3be2dc 100755 --- a/tests/fake-fortinet-server.py +++ b/tests/fake-fortinet-server.py @@ -152,6 +152,49 @@ def complete_non_2fa(): return resp +# Respond to 'GET /fortisslvpn with a placeholder stub (since OpenConnect doesn't even try to parse this) +@app.route('/remote/fortisslvpn') +@require_SVPNCOOKIE +def html_config(): + return 'VPN config in HTML format' + + +# Respond to 'GET /fortisslvpn_xml with a fake config +@app.route('/remote/fortisslvpn_xml') +@require_SVPNCOOKIE +def xml_config(): + return (''' + + + + + + + + + + + + + + + + + + + ''', + {'content-type': 'application/xml'}) + + +# Respond to faux-CONNECT 'GET /remote/sslvpn-tunnel' with 403 Forbidden +# (what the real Fortinet server sends when it doesn't like the parameters, +# intended to trigger "cookie rejected" error in OpenConnect) +@app.route('/remote/sslvpn-tunnel') +@require_SVPNCOOKIE +def tunnel(): + abort(403) + + # Respond to 'GET /remote/logout' by clearing session and SVPNCOOKIE @app.route('/remote/logout') @require_SVPNCOOKIE diff --git a/tests/auth-fortinet b/tests/fortinet-auth-and-config similarity index 84% rename from tests/auth-fortinet rename to tests/fortinet-auth-and-config index 60d97ad1..79b86e6f 100755 --- a/tests/auth-fortinet +++ b/tests/fortinet-auth-and-config @@ -59,6 +59,13 @@ echo -n "Authenticating with username/password/token and NON-DEFAULT path... " echo ok +echo -n "Authenticating with username/password, then proceeding to tunnel stage... " +echo "test" | LD_PRELOAD=libsocket_wrapper.so $OPENCONNECT --protocol=fortinet -q $ADDRESS:443 -u test $FINGERPRINT >/dev/null 2>&1 +test $? = 2 || # what OpenConnect returns when server rejects cookie upon tunnel connection, as the fake server does + fail $PID "Something went wrong in fake Fortinet server (other than the expected rejection of cookie)" + +echo ok + cleanup exit 0