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

Saml external browser #1217

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

filippor
Copy link

@filippor filippor commented Apr 29, 2024

add parameter --ext-browser-saml[=]

  • start listening on listen port (default)
  • print an address to manually open in a browser
  • ( the gateway server will send browser a javascrip to call localhost:8020/?id=<auth-id> )
  • receive the http request and extract the auth-d
  • use auth-id to autenticate on address /remote/saml/auth_id?id=

there is also parameter --auth-id= if retrieved externally

NEED HELP to review printed messages and update man pages

the ext-browser-saml can be configured configuration file but in this case the port is required
es

host = vpn.sample.com
port = 10443
ext-browser-saml = 8020

start listening on port 8020 or parameter
print a address to open in browser
when id received proceed with the authemntication
@filippor filippor mentioned this pull request Apr 29, 2024
@LorenzISR
Copy link

as i've mentioned in #867, imo this is not a good idea. external programms such as https://github.com/gm-vm/openfortivpn-webview are a cleaner approach to handling this -- maby look into packaging it for your distro instead?

expose auth-id option
@filippor filippor marked this pull request as ready for review April 29, 2024 10:16
@filippor
Copy link
Author

LorenzISR

Can you be more explicit this pr use a different method than openfortivpn-webview

@LorenzISR
Copy link

i know that, i've read your code.

you are adding a bunch of code for functionality that openfortipvn-webview can already do, but better. if it gets packaged for distros that is actually a much nicer user experience than having to manually run an external browser. that is why i've said that you should look into packaging it instead.

@filippor
Copy link
Author

filippor commented Apr 29, 2024

The advantage is that this solution could use the default browser that keep the session and if it is valid doesn't need to reautenticate. And i think it is easy to integrate in external gui or NetworkManager
and can be executed with
xdg-open https://<gateway>:<port>/remote/saml/start?redirect=1 &sudo openfortivpn <gateway>:<port> --ext-browser-saml

@exzombie
Copy link

I hope to be able to test this in the coming weeks.

I'm a regular user of code from an unfinished MR in NetworkManager-fortisslvpn which uses the same approach as openfortipvn-webview, with the benefits of NetworkManager integration. While it's a life saver in my situation, I have to say that the approach taken in this PR would be preferable. The VPN is not very stable, and I am often on a flaky uplink, necessitating many reconnections. Typing the password and dealing with 2FA can be quite annoying.

We can debate whether this functionality belongs into openfortivpn itself instead of an external tool. I think having it in openfortivpn has the benefit of making it available to all users. For comparison, having openfortivpn-webview available doesn't do much for those of us who need NetworkManager, and while the Gnome version of the NM user interface now has an unfinished reimplementation of what openfortivpn-webview offers, other interfaces (like KDE Plasma) don't have it. In other words, everybody would need to integrate a webview, which is not trivial. On the other hand, if openfortivpn can handle this by itself, adding SAML to NM interfaces is one new checkbox away.

The way I see it, this is just another way for openfortivpn to get the cookie, complementing the stdin method. Authentication is delegated to an external tool (i.e., the default browser), same as always.

@robdevops
Copy link

robdevops commented Apr 30, 2024

Working well on Ubuntu. I do strongly prefer this method because it is a much simpler command that does not require additional packages, nor the awkward pipe into sudo, nor re-authentication because it uses my existing browser session.

This is how convoluted my launch command needs to be with openfortivpn-webview:
$ sudo whoami # cache sudo credentials because you can't (easily) type your password into a pipe
$ ~/openfortivpn-webview-1.2.0.AppImage --trusted-cert='sha256/+NbF2hzIunRbOynladSfCD9gj9HEKYHc4Y7h9dlGqQg=' vpn.example.com:10443 | sudo ./openfortivpn --cookie-on-stdin --trusted-cert f8d6c5da1cc8ba755b3b29e569d49f082f608fd1c42981dce18ee1f5d946a908 vpn.example.com:10443

Vs this branch:
$ sudo ./openfortivpn --ext-browser-saml vpn.example.com:10443 --trusted-cert f8d6c5da1cc8ba755b3b29e569d49f082f608fd1c42981dce18ee1f5d946a908

It is better in every way: quicker to install, easier to use, and saves a good 40 secs by using existing session.

@robdevops
Copy link

Hi @LorenzISR

a bunch of code for functionality that openfortipvn-webview can already do, but better. if it gets packaged for distros that is actually a much nicer user experience

Expanding on my previous comment, the user experience is superior with this PR for the following reasons:

  • Even if openfortipvn-webview were packaged, in order to continue supporting non-GUI systems, it can never be pulled in as a dependency of the openfortipvn package, so on any system with a working browser, the openfortipvn-webview method will always be more complex to install.
  • The openfortipvn-webview launch command is convoluted with openfortipvn-webview, requiring shell tricks like pipes or stdin redirects, so it will always be harder to use, especially when combined with sudo and password input.
  • Both methods use an external browser. With this PR, it is guaranteed the user will have browser package options that have an active maintainer, as long as they are on a supported OS.
  • This PR supports hardware auth methods like YubiKey, FIDO/U2F, smart cards, and Passkeys. It is a more universal solution.
  • The reuse of existing browser session cookies can significantly reduce the number of user interactions and average time to connect.

I think the last two items in particular are high value for user experience. I respectfully request you reconsider use cases where this PR adds real value.

@LorenzISR
Copy link

pipes are not "tricks", they are normal features of a shell, and they have always been. you will need sudo anyways, regardless if you use webview or not. i am not using sudo but doas instead but using pipes works perfectly fine for me. openfortivpn-webview can safe the credentials (i think?), you'll just have to click the button for your SSO provider to safe them. i don't understand why openfortivpn-webview would not work with hardware auth methods?

that said, i am not a maintainer but just a random person on the internet who's worked on this problem before and this is just my opinion.

@robdevops
Copy link

pipes are not "tricks", they are normal features of a shell, and they have always been. you will need sudo anyways, regardless if you use webview or not. i am not using sudo but doas instead but using pipes works perfectly fine for me.

I'll clarify. If sudo is at the beginning of the command, it can cleanly prompt for keyboard input before openfortivpn is executed. In the openfortivpn-webview case where sudo is after a pipe, you have sudo writing to stdout, openfortivpn-webview also writing to stdout, sudo reading from stdin, and finally openfortivpn reading from stdin. The output of sudo and openfortivpn-webview gets mixed together and the sudo password prompt is buried above line feeds from openfortivpn-webview, with openfortivpn-webview printing to the part of the screen where the users password input indicator is supposed to be printed. It's clunky. There are ways to make this smoother, but my point is it's an inferior user experience out of the box. If further steps are required to make this smoother, then the existence of those steps themselves make for a more complicated user experience.

i don't understand why openfortivpn-webview would not work with hardware auth methods?

I thought I read on another discussion that it didn't. Apologies if I am wrong.

openfortivpn-webview can safe the credentials (i think?), you'll just have to click the button for your SSO provider to safe them. i don't understand why openfortivpn-webview would not work with hardware auth methods?

openfortivpn-webview does cache, but you may be overlooking that SAML is commonly used for single sign-on, where web apps can use the VPN initiated cookie or vice versa. That is, the browser may already be signed in before the VPN is ever used, and likewise, signing into the VPN may also sign into various web apps. Using openfortivpn-webview segregates the cache of the VPN from the cache of web apps, breaking the single sign-on model by resulting in more consecutive sign on interactions.

@LorenzISR
Copy link

sudo does not read the password from stdin.

i'd argue that re-using the sign-in from the browser might even be a security risk for some and not desired; having to sign in once for your VPN is fine imo and this is also how it works on windows.

@gm-vm
Copy link
Contributor

gm-vm commented May 3, 2024

This seems to do what it is described here.

That's neat, I don't remember having this option in my official client (and I no longer have access to the VPN to check it now), but this definitely removes the need of using a custom made browser.

@LorenzISR this seems to be a feature of the gateway that lets clients use any browser for the authentication. It is different from launching the browser from within openfortivpn.

@robdevops
Copy link

@gm-vm that's correct. I use this option in the official mac client.

@filippor
Copy link
Author

filippor commented May 3, 2024

If needed I can split this PR
1 for adding the authentication method with ID and another to listen for id on port.
But I think the changes are not so big.

@herpus-derpus
Copy link

I am of the opinion this PR would vastly improve all linux user experience for everyone I work with.

My default browser session requires me to SAML auth with a second factor each day. Currently I have to do this twice each morning since openfortivpn-webview requires it separately.

If the openfortivpn client could make use of my already authenticated session in my default browser, this would significantly improve the SSO experience for myself and all other linux desktop users where I work. They would be over the moon. I don't see a decrease in security for us, since we need to SAML auth daily anyway as policy means our session expire each day.

@herpus-derpus
Copy link

Presumably I could build openfortivpn from this filippor:saml-external-take2 branch ?
Is that easy ?

@filippor
Copy link
Author

filippor commented May 17, 2024

Presumably I could build openfortivpn from this filippor:saml-external-take2 branch ? Is that easy ?
It is not complicated to compile the instruction is in the readme on this repository

or to retrieve the token you could use this project https://github.com/filippor/XdgOpenSaml/tree/main it also use the external browser a binary that not require javacompiled for linux-x86-64 is here

@dsl400
Copy link

dsl400 commented May 20, 2024

Without knowing this even existed and driven only by the need of a specific feature I written a solution that kinda solves the same problem, there are few extra features like specifying routes, dns entries and domain prefixes in the config file to override the configuration coming from the server.
In my case the server uses microsoft services with 2FA. While I do undertand the need for a SAML sign in method on systems without gui I am not really sure that openfortivpn-webview is a good idea and here are some reasons:

  • bloat
  • security
  • overcomplicated

I really would not push a "home made browser" over a mainstream browser.

and ....

that said, i am not a maintainer but just a random person on the internet who's worked on this problem before and this is just my opinion.

@itss0076
Copy link

Rather than trying to put the browser spawning and web server listening bits into the (run as root) openfortivpn process, why not use the privilege-separated interface already provided for openfortvpn-webview?

Make a new process (run unprivileged) that does the SAML dance with the VPN appliance, by shelling out to the user's browser and listening for a response on port 8020, and then outputs the "SVPNCOOKIE=..." string for piping into "sudo openfortivpn --cookie-on-stdin ...".

@filippor
Copy link
Author

The implementation as external program already exist at this repo https://github.com/filippor/XdgOpenSaml.
The idea of this pr is to integrate all the process in this software.

@@ -87,6 +87,8 @@ const struct vpn_config invalid_cfg = {
.user_agent = NULL,
.hostcheck = NULL,
.check_virtual_desktop = NULL,
.auth_id = NULL,
.listen_port = 0

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable names are very generic. Maybe, being more specific about what the listen_port is actually listen for might improve readability.

if (username[0] == '\0' && tunnel->config->password[0] == '\0') {
if (tunnel->config->auth_id != NULL) {
char url[256];
snprintf(url,sizeof(url), "/remote/saml/auth_id?id=%s",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Size check is missing. In case the auth ID gets too long, it will be cut off without notice.


static void print_url(struct vpn_config *cfg) {
char url[512];
snprintf(url, sizeof(url), "https://%s:%d/remote/saml/start?redirect=1",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Size check missing. For long server names, the string might be cut off.


static void print_url(struct vpn_config *cfg) {
char url[512];
snprintf(url, sizeof(url), "https://%s:%d/remote/saml/start?redirect=1",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Size check missing. For long server names, the string might be cut off.

cfg->gateway_host, cfg->gateway_port);

if (cfg->realm[0] != '\0') {
strncat(url, "&realm=", sizeof(url) - 1);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To my understanding the 3rd argument is how may bytes from the src argument it should copy, but not the total size of both strings combined.

Also it has to be checked if there is actual space in url to fit the additional string. According th the man page, behavior is undefined.

if (optarg != NULL) {
port = strtol(optarg, NULL, 0);
if (port < 1 || port > 65535) {
log_error("Specify a valid listen port or omit for parameter ext-browser-saml\n");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion:

Specify a valid listen port for parameter ext-browser-saml or omit it to use the default.

}
if (strcmp(long_options[option_index].name, "auth-id") == 0) {
free(cli_cfg.auth_id);
cli_cfg.auth_id = strdup(optarg);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it handy to have at least a zero length check to catch shell mistakes like empty variables when calling as early as possible.

if(cfg.auth_id){
log_warn("auth-id will be ignored conflict with --ext-browser-saml");
}
log_debug("Will listen on port \"%d\" for authentication id \n", cfg.listen_port);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's about the same message in idlistener.c:listen_for_id

free(cfg.auth_id);
cfg.auth_id = listen_for_id(&cfg);
}

if (geteuid() != 0) {
log_error("This process was not spawned with root privileges, which are required.\n");
ret = EXIT_FAILURE;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Sorry, for putting it here, but I didn't find out how to comment on files that there not changed.)

This MR is missing changes to README.md and doc/openfortivpn.1.in

read(newsockfd, buffer, MAX_REQUEST_SIZE - 1);
log_debug("Received HTTP request:\n%s\n", buffer);

char *id = parse_request(buffer);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to have a check if the id string has zero length, just in case something went wrong. I won't make sense to continue in that case.

if (cfg->realm[0] != '\0') {
strncat(url, "&realm=", sizeof(url) - 1);
char *dt = url + strlen(url);
url_encode(dt, cfg->realm);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the documentation of url_encode it is stated that the buffer size must be at least 3x the size of cfg->realm +1. I can't find that reflected in the code here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants