vlaci / openconnect-sso

Wrapper script for OpenConnect supporting Azure AD (SAMLv2) authentication to Cisco SSL-VPNs
GNU General Public License v3.0
290 stars 124 forks source link

PulseSecure support with SAML and 2FA #11

Open bene2342 opened 4 years ago

bene2342 commented 4 years ago

I tried connecting to a Pulse Secure appliance which is configured with GSuite and 2FA, unfortunately it was not working. It would be great if that could be added. Some output I able to share.


[info     ] Authenticating to VPN endpoint [openconnect_sso.app] address=sslvpn.example.net name=UNNAMED
Traceback (most recent call last):
  File "/home/bene/.local/bin/openconnect-sso", line 8, in <module>
    sys.exit(main())
  File "/home/bene/.local/pipx/venvs/openconnect-sso/lib/python3.7/site-packages/openconnect_sso/cli.py", line 150, in main
    return app.run(args)
  File "/home/bene/.local/pipx/venvs/openconnect-sso/lib/python3.7/site-packages/openconnect_sso/app.py", line 28, in run
    return asyncio.get_event_loop().run_until_complete(_run(args))
  File "/usr/lib/python3.7/asyncio/base_events.py", line 584, in run_until_complete
    return future.result()
  File "/home/bene/.local/pipx/venvs/openconnect-sso/lib/python3.7/site-packages/openconnect_sso/app.py", line 98, in _run
    auth_response = await authenticate_to(selected_profile, credentials, display_mode)
  File "/home/bene/.local/pipx/venvs/openconnect-sso/lib/python3.7/site-packages/openconnect_sso/authenticator.py", line 21, in authenticate
    response = self._start_authentication()
  File "/home/bene/.local/pipx/venvs/openconnect-sso/lib/python3.7/site-packages/openconnect_sso/authenticator.py", line 65, in _start_authentication
    return parse_response(response)
  File "/home/bene/.local/pipx/venvs/openconnect-sso/lib/python3.7/site-packages/openconnect_sso/authenticator.py", line 133, in parse_response
    xml = objectify.fromstring(resp.content)
  File "src/lxml/objectify.pyx", line 1808, in lxml.objectify.fromstring
  File "src/lxml/etree.pyx", line 3235, in lxml.etree.fromstring
  File "src/lxml/parser.pxi", line 1876, in lxml.etree._parseMemoryDocument
  File "src/lxml/parser.pxi", line 1764, in lxml.etree._parseDoc
  File "src/lxml/parser.pxi", line 1127, in lxml.etree._BaseParser._parseDoc
  File "src/lxml/parser.pxi", line 601, in lxml.etree._ParserContext._handleParseResultDoc
  File "src/lxml/parser.pxi", line 711, in lxml.etree._handleParseResult
  File "src/lxml/parser.pxi", line 640, in lxml.etree._raiseParseError
  File "<string>", line 1256
lxml.etree.XMLSyntaxError: Opening and ending tag mismatch: meta line 7 and head, line 1256, column 10```
vlaci commented 4 years ago

Please run openconnect-sso with -l debug argument and paste the contents of log line starting with [debug ] Auth init response received

bene2342 commented 4 years ago

Thanks for your reply, I can paste at least the beginning as starts with [debug ] Auth init response received [openconnect_sso.authenticator] content=b'\n<!DOCTYPE html>\n<html and then there are ~75k characters of html code, not sure if that's really helping but if so let me know and I'll paste the entire output.

vlaci commented 4 years ago

Are you sure you are connecting on an AnyConnect compatible endpoint? Does anyconnect work on that endpoint?

The reason I ask this is that openconnect-sso (and openconnect in anyconnect compatibility mode too) expects that the endpoint it connects to serves an XML like this:

<?xml version="1.0" encoding="UTF-8"?>
<config-auth client="vpn" type="auth-request" aggregate-auth-version="2">
<opaque is-for="sg">
<tunnel-group>Q-Employee</tunnel-group>
<auth-method>single-sign-on-v2</auth-method>
<group-alias>Employee (Global)</group-alias>
<config-hash>1567100134259</config-hash>
</opaque>
<auth id="main">
<title>Login</title>
<message>Please complete the authentication process in the AnyConnect Login window.</message>
<banner></banner>
<sso-v2-login>...</sso-v2-login>
<sso-v2-login-final>...</sso-v2-login-final>
<sso-v2-logout>...</sso-v2-logout>
<sso-v2-logout-final>...</sso-v2-logout-final>
<sso-v2-token-cookie-name>acSamlv2Token</sso-v2-token-cookie-name>
<sso-v2-error-cookie-name>acSamlv2Error</sso-v2-error-cookie-name>
<form>
<input type="sso" name="sso-token"></input>
...
</select>
</form>
</auth>
</config-auth>
bene2342 commented 4 years ago

I'm trying to connect to a PulseSecure PSA5000 which is working fine as long as it's not using SAML. If the login is username/password + 2FA, everything works as expected. For openconnect I'm adding the paramenter --juniper.

vlaci commented 4 years ago

Unfortunately openconnect-sso is only compatible with the protocol Cisco's AnyConnect is using.

It seems to me that unlike AnyConnect, Pulse is starting with the web for authentication. I have found that we'd need to parse the DSID cookie https://github.com/russdill/juniper-vpn-py/blob/master/juniper-vpn.py#L258. openconnect-sso also would need to be able to execute the host checker code, according to juniper-vpn-py.

bene2342 commented 4 years ago

thanks for looking into that. I had a look at the repo you mentioned and also gave it a try, same behavior. I guess the main problem here is that the Pusle is using GSuite SAML and wants to open a browser window to get the needed credentials. At least that's what the Pulse Linux client is doing, problem with that is, it's using libwebkit-1.0.0 which is kinda old.

eoprede commented 2 years ago

@bene2342 @vlaci It has been a while since you have asked your question, but this page was one of the top ones that came up during my search, so I figured I may as well share my findings here. Pulse Secure does rely on user going through web pages to authenticate and receive authentication cookie (named DSID) and it will be nearly impossible to automate the process of logging in all the possible SAML providers, as you will be effectively screen scraping for specific fields and any interface change can break your code. However it is extremely easy to just fire up the web browser, let the user log in manually, then extract the cookie and fire up openconnect with that cookie authentication. Here's the barebones version of what I am talking about, written in python and using selenium to open up a browser for user to interact with. It should work with any SAML provider out there:

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
import subprocess

host = "your.vpn.com" #could be "your.vpn.com/saml" if you have different authentication endpoints
user = "username"
# declare webdriver to control browser. You can use different browser if you prefer
driver = webdriver.Chrome("./chromedriver")
# Configure wait, 60 seconds should be more than enough to enter all the credentials
wait = WebDriverWait(driver, 60)
# open up the VPN web page
driver.get("https://"+host)
# Keep checking for cookie to appear in the web browser (after user is done with authentication process)
dsid = wait.until(lambda driver: driver.get_cookie("DSID"))
# We don't need browser once we got cookie
driver.quit()
# Run a shell command to start openconnect
subprocess.run(["openconnect", "-C", dsid["value"], "--protocol=pulse", "-u", user, host])
azrdev commented 9 months ago

Here's the barebones version of what I am talking about, written in python and using selenium to open up a browser for user to interact with.

Keep in mind that this will be complicated if a host checker is in use (at least our company pulse server requires the host checker before authentication on the website)