Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to connect when only okta auth is used #116

Open
nicklan opened this issue Aug 23, 2018 · 53 comments
Open

How to connect when only okta auth is used #116

nicklan opened this issue Aug 23, 2018 · 53 comments
Labels
question SAML SAML authentication stuff

Comments

@nicklan
Copy link

nicklan commented Aug 23, 2018

My company uses only Okta to authenticate to GlobalProtect. I've tried lots of ways of calling openconnect but nothing seems to work for me. A couple of representative examples:

$ openconnect --protocol=gp vpn.server --user user.name --dump -vvv
Please enter your username and password
Password: 
POST https://vpn.server/ssl-vpn/login.esp
Attempting to connect to server ip:443
Connected to ip:443
SSL negotiation with vpn.server
Connected to HTTPS on vpn.server
> POST /ssl-vpn/login.esp HTTP/1.1
> Host: vpn.server
> User-Agent: PAN GlobalProtect
> X-Pad: 0000000000000000000000000000000000000000000000000000000
> Content-Type: application/x-www-form-urlencoded
> Content-Length: 201
> 
> jnlpReady=jnlpReady&ok=Login&direct=yes&clientVer=4100&prot=https:&clientos=linux-64&server=vpn.server&computer=user&user=user&passwd=[redacted]
Got HTTP response: HTTP/1.1 200 OK
Date: Thu, 23 Aug 2018 18:30:43 GMT
Content-Type: text/html
Content-Length: 128
Connection: keep-alive
ETag: "23605b6a6da2"
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
x-private-pan-sslvpn: gateway-not-exist
Expires: Thu, 19 Nov 1981 08:52:00 GMT
X-FRAME-OPTIONS: DENY
Set-Cookie: PHPSESSID=[snip]; secure; HttpOnly
Set-Cookie: PHPSESSID=[snip]; secure; HttpOnly
Strict-Transport-Security: max-age=31536000;
X-XSS-Protection: 1; mode=block;
X-Content-Type-Options: nosniff
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; img-src * data:; style-src 'self' 'unsafe-inline';
HTTP body length:  (128)
< 
< var respStatus = "Error";
< var respMsg = "Authentication failure: Invalid username or password";
< thisForm.inputStr.value = "";
< 
Authentication failure: Invalid username or password
Failed to obtain WebVPN cookie

or:

$ openconnect --protocol=gp --usergroup=portal vpn.server --user user --dump -vvv
Please enter your username and password
Password: 
POST https://vpn.server/global-protect/getconfig.esp
Attempting to connect to server ip:443
Connected to ip:443
SSL negotiation with vpn.server
Connected to HTTPS on vpn.server
> POST /global-protect/getconfig.esp HTTP/1.1
> Host: vpn.server
> User-Agent: PAN GlobalProtect
> X-Pad: 00000000
> Content-Type: application/x-www-form-urlencoded
> Content-Length: 184
> 
> jnlpReady=jnlpReady&ok=Login&direct=yes&clientVer=4100&prot=https:&clientos=linux-64&server=vpn.server&computer=user&user=user&passwd=[redacted]
Got HTTP response: HTTP/1.1 512 Custom error
Date: Thu, 23 Aug 2018 18:21:07 GMT
Content-Type: application/xml; charset=UTF-8
Content-Length: 0
Connection: keep-alive
ETag: "7875b6a6da2"
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
x-private-pan-globalprotect: auth-failed
Expires: Thu, 19 Nov 1981 08:52:00 GMT
X-FRAME-OPTIONS: DENY
Set-Cookie: PHPSESSID=[snip]; secure; HttpOnly
Set-Cookie: PHPSESSID=[snip]; secure; HttpOnly
HTTP body length:  (0)
Unexpected 512 result from server
Invalid username or password.
Please enter your username and password
Username: 

Let me know if you need more info, and thanks for the project!

@dlenski
Copy link
Owner

dlenski commented Aug 23, 2018

Starting with the disclaimer that I don't use or have access to Okta myself@arthepsy and @cizra have figured out a lot of things about how it works.

See this comment with a summary: #98 (comment)

Breaking it down a little further…

  • Basically, Okta sends you through some web-based login forms.
  • After a successful login, you get to an Okta web page that contains a portal-userauthcookie.
  • This cookie has to be forwarded to the GlobalProtect VPN instead of the password.
  • In 8b2bc5f, I implemented a simple mechanism to provide an "alternative secret field" instead of the password to support this.
    • After extracting the portal-userauthcookie from the web login, you can test this manually by changing your command line to:
      openconnect --protocol=gp --usergroup=portal:portal-userauthcookie vpn.server --user user --dump -vvv
    • And then you should probably check out the repo arthepsy/pan-globalprotect-okta, which contains some wrapper scripts to automate the process of doing the Okta web-based logins and then running openconnect with the output.

… and lastly, if anyone is willing and able to let me play around with their Okta VPN login for a couple hours, perhaps while screensharing, then I can probably simplify it further. Please email me if so. (I won't need to do anything besides test the authentication process.)

@nicklan
Copy link
Author

nicklan commented Aug 24, 2018

Awesome, thanks for the pointers. Our Okta stuff seems to require a bunch of javascript nonsense, so the pan-globalprotect-okta script doesn't quite work, but I'm gonna work to modify it and pass through a browser and see if I can get that going.

I sadly can't lend you any Okta credentials as mine are work related.

@cizra
Copy link

cizra commented Aug 24, 2018

The login process in my VPN is so involved I found it easier to steal auth cookies from Firefox (using Python's browsercookie) and then use RoboBrowser to get the token

@dlenski
Copy link
Owner

dlenski commented Aug 24, 2018

Yeah, I think @cizra's approach of doing the authentication in the browser and then stealing the cookies is probably the lowest-hassle approach.

@dlenski
Copy link
Owner

dlenski commented Aug 27, 2018

@nicklan, did you figure out a good way to handle this? It'd be really awesome if you Okta users could write up a how-to guide on OpenConnect+Okta, since this comes up a lot :-D

@nicklan
Copy link
Author

nicklan commented Aug 30, 2018

So, I can't seem to get this going. By hacking up the scripts a bit, I'm able to use a combo of my browser and python to get the auth-usercookie. But then I can't actually connect. If I try the command you gave above I get:

$ openconnect --protocol=gp --usergroup portal:portal-userauthcookie [vpn-server] --user user@domain.com --dump -vvv
Please enter your username and password
portal-userauthcookie: 
POST https://[vpn-server]/global-protect/getconfig.esp
Attempting to connect to server [ip]:443
Connected to [ip]:443
SSL negotiation with [vpn-server]
Connected to HTTPS on [vpn-server]
> POST /global-protect/getconfig.esp HTTP/1.1
> Host: [vpn-server]
> User-Agent: PAN GlobalProtect
> X-Pad: 000000
> Content-Type: application/x-www-form-urlencoded
> Content-Length: 250
> 
> jnlpReady=jnlpReady&ok=Login&direct=yes&clientVer=4100&prot=https:&clientos=linux-64&server=[vpn-server]&computer=[user]&user=[user%40domain.com]&portal-userauthcookie=[portal-userauthcookie]
Got HTTP response: HTTP/1.1 512 Custom error
Date: Thu, 30 Aug 2018 21:23:54 GMT
Content-Type: application/xml; charset=UTF-8
Content-Length: 0
Connection: keep-alive
ETag: "7875b6a6da2"
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
x-private-pan-globalprotect-extension: auth-failed-password-empty
x-private-pan-globalprotect: auth-failed
Expires: Thu, 19 Nov 1981 08:52:00 GMT
X-FRAME-OPTIONS: DENY
Set-Cookie: PHPSESSID=830323c162065c6cc87905149999bb66; secure; HttpOnly
Set-Cookie: PHPSESSID=830323c162065c6cc87905149999bb66; secure; HttpOnly
HTTP body length:  (0)
Unexpected 512 result from server
Invalid username or password.

and if I try the one that the arthepsy/pan-globalprotect-okta script spits out, I get:

$ echo "[portal-authusercookie]" | openconnect --protocol=gp -u "user@domain.com"/portal:portal-userauthcookie --passwd-on-stdin https://[vpn-server] --dump -vvv
Please enter your username and password
POST https://[vpn-server]/ssl-vpn/login.esp
Attempting to connect to server [ip]:443
Connected to [ip]:443
SSL negotiation with [vpn-server]
Connected to HTTPS on [vpn-server]
> POST /ssl-vpn/login.esp HTTP/1.1
> Host: [vpn-server]
> User-Agent: PAN GlobalProtect
> X-Pad: 0000000000000000000000
> Content-Type: application/x-www-form-urlencoded
> Content-Length: 554
> 
> jnlpReady=jnlpReady&ok=Login&direct=yes&clientVer=4100&prot=https:&clientos=linux-64&server=[vpn-server]&computer=user&user=user%40domain.com%2fportal%3aportal-userauthcookie&passwd=[portal-authusercookie]
Got HTTP response: HTTP/1.1 200 OK
Date: Thu, 30 Aug 2018 21:36:12 GMT
Content-Type: text/html
Content-Length: 128
Connection: keep-alive
ETag: "23605b6a6da2"
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
x-private-pan-sslvpn: gateway-not-exist
Expires: Thu, 19 Nov 1981 08:52:00 GMT
X-FRAME-OPTIONS: DENY
Set-Cookie: PHPSESSID=2bf0184b434694627b6a969bdb10e021; secure; HttpOnly
Set-Cookie: PHPSESSID=2bf0184b434694627b6a969bdb10e021; secure; HttpOnly
Strict-Transport-Security: max-age=31536000;
X-XSS-Protection: 1; mode=block;
X-Content-Type-Options: nosniff
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; img-src * data:; style-src 'self' 'unsafe-inline';
HTTP body length:  (128)
< 
< var respStatus = "Error";
< var respMsg = "Authentication failure: Invalid username or password";
< thisForm.inputStr.value = "";
< 
Authentication failure: Invalid username or password
Failed to obtain WebVPN cookie

@dlenski
Copy link
Owner

dlenski commented Aug 30, 2018

@nicklan,

If I try the command you gave above I get:

…
x-private-pan-globalprotect-extension: auth-failed-password-empty
x-private-pan-globalprotect: auth-failed
Invalid username or password

This is just a random idea, but are you sure that the @domain.com needs to be in the as-submitted username?

Also, are you certain that the cookie you're receiving should be named portal-userauthcookie and submitted to the portal, not the gateway? (We've seen ~5 other variants with GP VPNs)

and if I try the one that the arthepsy/pan-globalprotect-okta script spits out, I get:

$ echo "[portal-authusercookie]" | openconnect --protocol=gp -u "user@domain.com"/portal:portal-userauthcookie --passwd-on-stdin https://[vpn-server] --dump -vvv

There's a mistake in this one. The /portal:portal-userauthcookie should definitely not be jammed into the username If you fix that, you get back to the first version you tried.

———

Unfortunately, I know almost nothing of how Okta can integrate with GlobalProtect. There seems to be a lot of possible variation.

I would suggest that you play around with test-globalprotect-login.py. It's a Python 2.7 script to test the login process and it's much easier to edit and tweak than the C code. You can run it with --help to see the options.

To mimic what you've been trying so far (username + portal-userauthcookie, blank password, submit to portal login interface), try the following:

./test-globalprotect-login.py -u "user@domain.com" -p "" \
    https://[vpn-server]/global-protect/getconfig.esp \
    portal-userauthcookie=deadbeef01234567

@dlenski
Copy link
Owner

dlenski commented Aug 31, 2018

@nicklan, it turns out that I'm the one who broke @arthepsy's script (:man_facepalming:) in a too-hasty PR.

I'm not sure if it'll make any difference, but you may want to test it after fixing the syntax error I mentioned above (you can just apply dlenski/pan-globalprotect-okta@2029f19).

@arthepsy
Copy link

P.S. I've commited the fix and also added OKTA totp support (previously, it supported only Google).

@nicklan
Copy link
Author

nicklan commented Aug 31, 2018

Thanks for the replies. I'm using the updated script and still getting the same error. At the core:

Unexpected 512 result from server
Invalid username or password.

I've tried both with and without the @domain.com part of my username.

Here's the response I get for the getconfig request in the script. Lots redacted, so let me know if there might be useful info I left out:

[INFO] getconfig request
# getconfig.response:
status code: 200, text:
<?xml version="1.0" encoding="UTF-8" ?>
<policy>
	<portal-name>GP_Portal</portal-name>
	<portal-config-version>4100</portal-config-version>
	<version>4.1.1-14                                                        </version>
	<client-role>global-protect-full</client-role>
	<agent-user-override-key>****</agent-user-override-key>
	<root-ca>
		<entry name="RootCert">
			<cert>
-----BEGIN CERTIFICATE-----
[snip]
-----END CERTIFICATE-----
			</cert>
			<install-in-cert-store>yes</install-in-cert-store>
		</entry>
		<entry name="IntermediateCert">
			<cert>
-----BEGIN CERTIFICATE-----
[snip]
-----END CERTIFICATE-----
			</cert>
			<install-in-cert-store>yes</install-in-cert-store>
		</entry>
	</root-ca>
	<connect-method>on-demand</connect-method>
	<on-demand>yes</on-demand>
	<refresh-config>yes</refresh-config>
	<refresh-config-interval>24</refresh-config-interval>
	<authentication-modifier>
		<none/>
	</authentication-modifier>
	<authentication-override>
		<accept-cookie>yes</accept-cookie>
		<generate-cookie>yes</generate-cookie>
		<cookie-lifetime><lifetime-in-minutes>5</lifetime-in-minutes></cookie-lifetime>
		<cookie-encrypt-decrypt-cert>RootCert</cookie-encrypt-decrypt-cert>
	</authentication-override>
	<use-sso>yes</use-sso>
		<ip-address></ip-address>
		<host></host>
	<gateways>
		<cutoff-time>5</cutoff-time>
		<external>
			<list>
				<entry name="[gw]">
					<priority-rule>
						<entry name="Any">
							<priority>1</priority>
						</entry>
					</priority-rule>
					<priority>1</priority>
					<manual>yes</manual>
					<description>gw1-us</description>
				</entry>
			</list>
		</external>
	</gateways>
	<gateways-v6>
		<cutoff-time>5</cutoff-time>
		<external>
			<list>
				<entry name="gw1-us">
					<fqdn>[gw1]</fqdn>
					<priority-rule>
						<entry name="Any">
							<priority>1</priority>
						</entry>
					</priority-rule>
					<priority>1</priority>
					<manual>yes</manual>
				</entry>
			</list>
		</external>
	</gateways-v6>
[snip lots of agent-config stuff, let me know if any of it would be useful]
<user-email>user@domain.com</user-email>
<portal-userauthcookie>[base64 encoded cookie]</portal-userauthcookie>
<portal-prelogonuserauthcookie>empty</portal-prelogonuserauthcookie>
<scep-cert-auth-cookie>[base64 encoded string]</scep-cert-auth-cookie>
</policy>
---
[INFO] portal-userauthcookie: [the cookie from above]

echo "[the cookie from above]" | openconnect --dump -vvv --protocol=gp -u "user@domain.com" --usergroup portal:portal-userauthcookie --passwd-on-stdin https://vpn.server

I'll try messing with the test-globalprotect-login.py you linked and see if I can figure anything out.

Thanks for all the help!

@nicklan
Copy link
Author

nicklan commented Aug 31, 2018

So after trying every combination of things I can think of, I'm not able to get any further than some variation of:

./test-globalprotect-login.py -v -u "user@domain.com" -p "" https://[vpn-server]/global-protect/getconfig.esp  portal-userauthcookie=[cookie]
Password: 

Request body:
 'jnlpReady=jnlpReady&clientVer=4100&ok=Login&passwd=[pass]&prot=https%3A&direct=yes&server=[vpn-server]&computer=[hostname]&portal-userauthcookie=[cookie]&user=user%40domain.com'

Response:
HTTP/1.1 512
Date: Fri, 31 Aug 2018 20:41:20 GMT
Content-Type: application/xml; charset=UTF-8
Content-Length: 0
Connection: keep-alive
ETag: "7875b6a6da2"
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
x-private-pan-globalprotect: auth-failed
Expires: Thu, 19 Nov 1981 08:52:00 GMT
X-FRAME-OPTIONS: DENY
Set-Cookie: PHPSESSID=[id]; secure; HttpOnly, PHPSESSID=[id]; secure; HttpOnly


Traceback (most recent call last):
  File "./test-globalprotect-login.py", line 69, in <module>
    res.raise_for_status()
  File "/usr/lib/python2.7/site-packages/requests/models.py", line 939, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 512 Server Error: Custom error for url: https://[vpn-server]/global-protect/getconfig.esp

Are there some docs for how this is supposed to work so I can try some other stuff?

Thanks!

@dlenski
Copy link
Owner

dlenski commented Sep 1, 2018

Are there some docs for how this is supposed to work so I can try some other stuff?

Unfortunately not. All public knowledge about how the GlobalProtect VPN works comes from my reverse engineering, with various bits and pieces that others have helped me fill in — especially in this area of authentication.

Here's the response I get for the getconfig request in the script. Lots redacted, so let me know if there might be useful info I left out:

I'd say you included all of the interesting/relevant parts of the getconfig.esp response. 👍

This gives me an idea!

Here's an outline of the what the official GP VPN clients do…

  1. Client gets the portal pre-login instructions (POST /global-protect/prelogin.esp)
  2. Client follows these instructions (could be normal username+password, could be SAML redirection to Okta, etc.)
  3. Client gets the portal config (POST /global-protect/getconfig.esp) using some kind of credentials, including possibly some cookies obtained in step (2), and obtains a list of appropriate <gateways>.
  4. Client logs into a gateway (POST /ssl-vpn/login.esp) using some kind of credentials, and receives an authcookie.
  5. Client submits the authcookie to get the gateway config (POST /ssl-vpn/getconfig.esp) and receives routing+ESP parameters.
    • If necessary *sigh*… HIP report check (POST /ssl-vpn/hipreportcheck.esp) and submission (POST /ssl-vpn/hipreport.esp).
  6. Client connects to the HTTPS-based tunnel (GET /ssl-tunnel-connect.sslvpn) or the ESP-based tunnel.

The OpenConnect C binary can start this process at either (3) or (5).

Up until now, I thought you were getting stuck at (3). But it appears that @arthepsy's script is getting you through (3) successfully. So the logical next step is to (4), not repeating (3).

Grab my latest test-globalprotect-login.py, take the portal-userauthcookie that you get from the portal config, and the gateway address, and try throwing all the passwords and cookies at it…

$ ./test-globalprotect-login.py -u "$USER" -p "$PASSWORD" \
     https://$GATEWAY/ssl-vpn/login.esp \
     portal-userauthcookie=$PUAC \
     portal-prelogonuserauthcookie=empty \
     scep-cert-auth-cookie=$SCAC

If all goes well, then 🍻, and you'll get this output:

{ ... headers ...}
<jnlp><application-desc><argument> ...

Extracted connection cookie from <jnlp>. Use this to connect:

    openconnect --protocol=gp --usergroup=gateway $GATEWAY \
        --cookie "domain=$DOMAIN&computer=$HOSTNAME&portal=$PORTAL&user=$USER&authcookie=9164c5c9185e5a352d5e0cbf6427d6f8&preferred-ip="

Run that command and you're on your way to (5). If so, the Okta script ought to be modified to direct the user to the gateway rather than the portal as the logical next step.

@dlenski
Copy link
Owner

dlenski commented Sep 1, 2018

PS: Although GlobalProtect is a black box, Okta has plenty of API documentation: https://developer.okta.com/docs/api/resources/oidc

You may want to scrutinize the web pages that lead to the portal-userauthcookie, because they probably give some clues about exactly what needs to be submitted next.

@nicklan
Copy link
Author

nicklan commented Sep 2, 2018

Arg! This seemed very promising, but trying to log into the gateway still just gives me a 512 as:

HTTP/1.1 512
Date: Sun, 02 Sep 2018 19:48:01 GMT
Content-Type: text/html
Content-Length: 127
Connection: keep-alive
ETag: "23605b6a6da2"
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
x-private-pan-sslvpn: auth-failed
Expires: Thu, 19 Nov 1981 08:52:00 GMT
X-FRAME-OPTIONS: DENY
Set-Cookie: PHPSESSID=[snip; secure; HttpOnly, PHPSESSID=[snip; secure; HttpOnly, PHPSESSID=[snip]; secure; HttpOnly


var respStatus = "Error";
var respMsg = "Authentication failed: Invalid username or password";
thisForm.inputStr.value = "";

I did have to enable --no-verify although I could probably avoid that by telling python to use the certs that are returned in step 3.

I will have a look at the okta docs and see what I can figure out.

Thanks!

@dlenski
Copy link
Owner

dlenski commented Sep 2, 2018

I did have to enable --no-verify although I could probably avoid that by telling python to use the certs that are returned in step 3.

👌

I will have a look at the okta docs and see what I can figure out.

Basically, what you want to keep in mind here is that you've already figured out how to login to the portal via Okta, and presumably the output of the Okta login process contains clues about what to do next to connect to the GlobalProtect server.

@Atoms
Copy link

Atoms commented Sep 3, 2018

i had this 512 error also, just like @arthepsy writes, fix connection string by appending \n before cookie output, and use printf instead of echo

@nicklan
Copy link
Author

nicklan commented Sep 3, 2018

@Atoms, you mean in the python script, like:

cmd = '\nprintf "{1}" | openconnect --dump -vvv --protocol=gp -u "{0}" --usergroup portal:portal-userauthcookie --passwd-on-stdin {2}'

I've tried that and it doesn't seem to make a difference. Of note, I can't seem to use the --passwd-on-stdin option, it just always gives:

$ [cmd paste]
Please enter your username and password
portal-userauthcookie: 
fgets (stdin): Inappropriate ioctl for devic

So I've been removing that and pasting the cookie in. But I dump out the contents of the request and I can see that it's sending the correct base64 encoded version of the cookie as part of the POST body, so I don't think that's the issue.

@Atoms
Copy link

Atoms commented Sep 3, 2018

Try moving newline in "\n{1}"

@dlenski
Copy link
Owner

dlenski commented Sep 3, 2018

i had this 512 error also, just like @arthepsy writes, fix connection string by appending \n before cookie output, and use printf instead of echo

This is not correct. OpenConnect is certainly smart enough to remove the trailing \n from a password passed to it.

Of note, I can't seem to use the --passwd-on-stdin option, it just always gives:

This doesn't make any sense. Unless openconnect is asking you for other input before the password, --passwd-on-stdin should simply do the right thing. I've use variants of this with basically every build of openconnect that's ever existed, with no problems.

echo "$PASSWORD" | openconnect --passwd-on-stdin --dump -vvv --prot=gp -u "$USERNAME" --usergroup portal:passwd "$HOST"

Everyone's using Linux here, right?

@arthepsy
Copy link

arthepsy commented Sep 3, 2018

@dlenski, no, this is correct, openconnect do ask for additional input and fails with ioctl error as @nicklan, @Atoms and me mentioned. This one - fgets (stdin): Inappropriate ioctl for device. But "fake" newline deals with it, always. There must be a bug, but I haven't had a time to figure it out jet, just slapped a newline and be fine for now. I do not understand how You are not experiencing it, if You really tested it. Three different people are experiencing the same issue.

I'm using FreeBSD, @Atoms is using Linux. Not sure about @nicklan, would guess it's Linux.

@arthepsy
Copy link

arthepsy commented Sep 4, 2018

And yes, to answer question, which You asked previously, without PIPE, it also waits for some input... before writing out "Please enter your username and password".

@arthepsy
Copy link

arthepsy commented Sep 4, 2018

Here's the backtrace for two read():

This is before "Please enter your username and password"

(gdb) bt
#0  0x0000000803fde4e8 in _read () from /lib/libc.so.7
#1  0x0000000803fddda0 in getdtablesize () from /lib/libc.so.7
#2  0x0000000803fcf2d6 in __srget () from /lib/libc.so.7
#3  0x0000000803fc0b78 in fgets () from /lib/libc.so.7
#4  0x00000000004067f2 in read_stdin (string=0x60d850, hidden=0, allow_fail=0) at main.c:688
#5  0x0000000000404c13 in main (argc=8, argv=0x7fffffffe9b0) at main.c:1276

This is after "portal-userauthcookie":

(gdb) bt
#0  0x0000000803fde4e8 in _read () from /lib/libc.so.7
#1  0x0000000803fddda0 in getdtablesize () from /lib/libc.so.7
#2  0x0000000803fcf2d6 in __srget () from /lib/libc.so.7
#3  0x0000000803fc0b78 in fgets () from /lib/libc.so.7
#4  0x00000000004067b0 in read_stdin (string=0x7fffffffe240, hidden=<value optimized out>, allow_fail=0) at main.c:688
#5  0x0000000000407c64 in prompt_for_input (prompt=<value optimized out>, vpninfo=0x807262000, hidden=1) at main.c:1890
#6  0x00000000004062cd in process_auth_form_cb (_vpninfo=0x807262000, form=0x80727a000) at main.c:2007
#7  0x000000080084236f in process_auth_form (vpninfo=0x807262000, form=<value optimized out>) at library.c:1147
#8  0x000000080085b2b0 in gpst_login (vpninfo=0x807262000, portal=1, pw_or_cookie_field=0x80721f9c7 "portal-userauthcookie") at auth-globalprotect.c:348
#9  0x0000000000405408 in main (argc=8, argv=0x7fffffffe9b0) at main.c:1557

@arthepsy
Copy link

arthepsy commented Sep 4, 2018

So, first read happens in https://github.com/dlenski/openconnect/blob/master/main.c#L1276 and second in https://github.com/dlenski/openconnect/blob/master/main.c#L2007. Actually, that was my initial guess, that it asks for password and later for cookie, but didn't verify.

Looks like issue is in this check: https://github.com/dlenski/openconnect/blob/master/main.c#L2003

@dlenski
Copy link
Owner

dlenski commented Sep 4, 2018

Good catch @arthepsy. 🤦‍♂️

I had submitted a patch upstream to add better hints for which form fields should be considered "the password", but they weren't accepted. For now, you should be able kludge around this problem with a patch like…

diff --git a/main.c b/main.c
index 379cf5d..d2e21c0 100644
--- a/main.c
+++ b/main.c
@@ -1999,8 +1999,7 @@ static int process_auth_form_cb(void *_vpninfo,
             empty = 0;
 
         } else if (opt->type == OC_FORM_OPT_PASSWORD) {
-            if (password &&
-                !strncmp(opt->name, "pass", 4)) {
+            if (password) {
                 opt->_value = password;
                 password = NULL;
             } else {

@nicklan
Copy link
Author

nicklan commented Sep 5, 2018

HA, got it! :) Turns out it wants a

clientos='Windows'

arg as part of the POST body for the /ssl-vpn/login.esp request, and then everything "just works". There are a few other kinks to work out with the certs and so on, but I did manage to connect and use the VPN.

Thanks again for all the help, and lmk if there's any more stuff I can check for you while I'm in "debug" mode :)

@dlenski
Copy link
Owner

dlenski commented Sep 5, 2018

That's the only thing that was missing?

The gateway login works with clientos=Windows in the query string, but fails with the default value of clientos=linux64 in the query string?

@nicklan
Copy link
Author

nicklan commented Sep 5, 2018

yep. if clientos is missing or linux64 i get the 512, otherwise all works just fine.

@dlenski
Copy link
Owner

dlenski commented Sep 5, 2018

That is a pretty infuriatingly misleading error. You can use openconnect --os=win to set the clientos value accordingly.

I try to avoid lying to the server about the OS whenever possible, but it sounds like it's necessary to lie to the gateway in this case.

So once you add the magic value of clientos and workaround the issue with gateway certs, everything works fine? You use @arthepsy's script to get the portal-prelogonuserauthcookie, submit that to the gateway, and it connects?

@dlenski
Copy link
Owner

dlenski commented Sep 5, 2018

This seems closely related to #86, where a user encountered a different misleading error when clientos had something other than the magic value of Windows.

@dlenski
Copy link
Owner

dlenski commented Sep 9, 2018

?kerberos-support=yes&tmp=tmp&clientVer=4100&host-id=BLAHBLAH&clientos=Windows&os-version=Microsoft+Windows+10+Enterprise+%2c+64-bit&ipv6-support=yes

Huh… so you have to put this query string boilerplate onto the end of the prelogin request, otherwise it won't include the SAML login info?

Does it matter if the host-id is unspecified or given a generic value, let's say repeated as?

How do you propose to get the cookie from browser into openconnect, after visiting the URL?

TBD… 🤷‍♂️

@cizra
Copy link

cizra commented Sep 10, 2018

Just ran a couple of experiments — the missing bit seems to be ?clientos=Windows (?clientos=Mac works too, Linux doesn't).

The host-id doesn't matter.

@dlenski
Copy link
Owner

dlenski commented Sep 10, 2018

Thanks, @cizra. This wouldn't be the only case where GlobalProtect fails in a misleading way due to the clientos not being set exactly as expected 😠 🤦‍♂️. See 7f7d739.

dlenski added a commit that referenced this issue Sep 10, 2018
Per this comment (#116 (comment)),
`clientos=Windows` (or `=Mac`) is required to get the servers to respond correctly when
using SAML. This is similar to other cases where the official clients and servers
sometimes respond with bizarre misleading errors if `clientos` doesn't have one of the magic
acceptable values (see 7f7d739).

The official clients also include `os-version` (free-form),
`kerberos-support=yes`, and in newer versions `ipv6-support=yes` as well.
@nicklan
Copy link
Author

nicklan commented Sep 10, 2018

Okay, I have a modified version of https://github.com/arthepsy/pan-globalprotect-okta that works with my config: https://github.com/nicklan/pan-globalprotect-okta

@aclindsa
Copy link

@nicklan I tried to compare this to a change I just made to see if our clients were doing similar things, but am having trouble separating your code changes from your whitespaces changes. Do you have a version where the whitespace changes are made separately?

@nicklan
Copy link
Author

nicklan commented Sep 11, 2018

@aclindsa yeah, I maybe should have stuck with tabs, so it was easier to see, but my emacs really likes spaces :)

Umm, diff -w should give you what you want

gnutlsmirror pushed a commit to openconnect/openconnect that referenced this issue Sep 21, 2018
…rdless of actual OS

I've tried, whenever possible not to lie to the server about the client
configuration.  However, there are now multiple cases where a GlobalProtect
server responds with a misleading error when the `ssl-vpn/login.esp` request doesn't
contain the exact, magic value of `clientos=Windows`.

* dlenski/openconnect#86
  ("Assign private IP address failed" unless `clientos=Windows` in `ssl-vpn/login.esp` request)

* dlenski/openconnect#116
  ("Invalid username or password" unless `clientos=Windows` in `ssl-vpn/login.esp` request)

These cases are very difficult to debug because they seem to be suggesting totally unrelated
errors.

For reliability, this patch makes openconnect always specify `clientos=Windows` in relevant
requests, regardless of the actual `vpninfo->platname`. (The `vpninfo->platname` value can
still be sent in the "free-form" `os-version` field, as far as I can tell.)

Signed-off-by: Daniel Lenski <dlenski@gmail.com>
crazymanjinn pushed a commit to crazymanjinn/openconnect that referenced this issue Sep 28, 2018
…rdless of actual OS

I've tried, whenever possible not to lie to the server about the client
configuration.  However, there are now multiple cases where a GlobalProtect
server responds with a misleading error when the `ssl-vpn/login.esp` request doesn't
contain the exact, magic value of `clientos=Windows`.

* dlenski#86
  ("Assign private IP address failed" unless `clientos=Windows` in `ssl-vpn/login.esp` request)

* dlenski#116
  ("Invalid username or password" unless `clientos=Windows` in `ssl-vpn/login.esp` request)

These cases are very difficult to debug because they seem to be suggesting totally unrelated
errors.

For reliability, this patch makes openconnect always specify `clientos=Windows` in relevant
requests, regardless of the actual `vpninfo->platname`. (The `vpninfo->platname` value can
still be sent in the "free-form" `os-version` field, as far as I can tell.)

Signed-off-by: Daniel Lenski <dlenski@gmail.com>
dlenski added a commit that referenced this issue Sep 28, 2018
Per this comment (#116 (comment)),
`clientos=Windows` (or `=Mac`) is required to get the servers to respond correctly when
using SAML. This is similar to other cases where the official clients and servers
sometimes respond with bizarre misleading errors if `clientos` doesn't have one of the magic
acceptable values (see 7f7d739).

The official clients also include `os-version` (free-form),
`kerberos-support=yes`, and in newer versions `ipv6-support=yes` as well.
dlenski added a commit that referenced this issue Sep 28, 2018
Per this comment (#116 (comment)),
`clientos=Windows` (or `=Mac`) is required to get the servers to respond correctly when
using SAML. This is similar to other cases where the official clients and servers
sometimes respond with bizarre misleading errors if `clientos` doesn't have one of the magic
acceptable values (see 7f7d739).

The official clients also include `os-version` (free-form),
`kerberos-support=yes`, and in newer versions `ipv6-support=yes` as well.
gnutlsmirror pushed a commit to openconnect/openconnect that referenced this issue Oct 1, 2018
…forms, including preliminary SAML support

Until recently, I've believed the prelogin.esp to be useless, because the
initial GlobalProtect login form always contains the same two fields:
username and password.

However, the prelogin response is also important for signalling when SAML
login is required.  When the VPN uses SAML login, the official GP clients
redirect the user to a web-based authentication flow (e.g.  Okta,
dlenski/openconnect#116).

That auth flow eventually sends the official client back to the GP VPN,
armed with a special cookie field, `portal-userauthcookie` or
`prelogin-cookie`, that needs to be submitted in place of the password
(already supported by openconnect as of 8b2bc5f).

This preliminary SAML support simply includes the SAML method and URL in the
form banner, and fails with an error message if the cookie field name was
not specified (since it cannot be autodetected).

Signed-off-by: Daniel Lenski <dlenski@gmail.com>
@ffainelli
Copy link

@dlenski did you decide on a way to pass the cookie to openconnect on the command line?

@dlenski
Copy link
Owner

dlenski commented Jan 13, 2019

@dlenski did you decide on a way to pass the cookie to openconnect on the command line?

Yes. You can use the normal password-passing mechanism in OpenConnect v8.x. The only reason this didn't work was because of a bug/oversight, which was fixed in b6dc821:

$ echo "COOKIE_FIELD_VALUE" | \
  openconnect --protocol=gp -u username --passwd-on-stdin \
  portal.company.com/portal:COOKIE_FIELD_NAME

You can also use the new, more flexible --form-entry mechanism added in OpenConnect v8.x, which makes it possible to set arbitrary fill-in values for arbitrary fields in arbitrary forms. See 0263090.

Does that answer this question?

@zdave
Copy link

zdave commented Mar 30, 2019

In case it's of use to anyone, I've uploaded a simple script that is working for me here: https://github.com/zdave/openconnect-gp-okta/blob/master/openconnect-gp-okta

It authenticates directly with the gateway rather than the portal, and only supports Okta's push MFA method. Use like:

openconnect-gp-okta <gateway> --username <okta username> -- --csd-wrapper path/to/openconnect/hipreport.sh

As every setup seems to be different I don't expect this will work for many other people, however @ffainelli it ought to work for you as we work for the same company...

@ffainelli
Copy link

@zdave it does work for me, thanks a lot! Any chance you could drop me an email?

@dlenski dlenski added the SAML SAML authentication stuff label Aug 28, 2019
@dlenski
Copy link
Owner

dlenski commented Sep 17, 2019

Inspired by @zdave's nice, simple clean implementation of Okta auto-login (zdave/openconnect-gp-okta)… and by sheer necessity, because I need to use a couple VPNs where the programmatic login simply doesn't work…

I created a tool to do the SAML login interactively with a GUI, from the Linux CLI: https://github.com/dlenski/gp-saml-gui

It pops up a graphical login window using GTK WebKit2 WebView, then grabs the cookies once you login successfully, and outputs their values so you can use them in a script to connect with OpenConnect.

whew

@aclindsa
Copy link

@dlenski Beautiful, thank you for taking the time to do this!

@jkondapalli03
Copy link

is this master ready?

@ElectricRCAircraftGuy
Copy link

For anyone who stumbles upon this thread but is looking for something slightly different, I just wrote this and it might be relevant to your case: How to use "openconnect" (via the openconnect-sso wrapper) with SAML and Duo two-factor authentication via Okta Single-Sign-on (SSO)

@mumubin
Copy link

mumubin commented Mar 1, 2023

This method works for me , it's updated at 2022.

@dlenski
Copy link
Owner

dlenski commented Mar 2, 2023

The context surrounding this issue is extremely different from what it was 4.5 years ago when the issue was originally opened. Locking to prevent further comments.

Open issues on https://github.com/dlenski/gp-saml-gui, https://github.com/zdave/openconnect-gp-okta/blob/master/openconnect-gp-okta, or https://gitlab.com/openconnect/openconnect instead.

Repository owner locked and limited conversation to collaborators Mar 2, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
question SAML SAML authentication stuff
Projects
None yet
Development

No branches or pull requests