You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@guacamole.apache.org by Colin McGuigan <co...@walkingshadows.org> on 2017/09/22 21:39:43 UTC

Handling a SAML POST response

tldr: The SAML POST body is getting thrown away, and I don't know how to keep
that from happening.

Longer: I'm writing a SAML authentication extension, based off of Mike
Jumper's OpenID extension:
https://github.com/mike-jumper/guacamole-auth-openid

I have successfully set up Mike Jumper's extension and used it to
authenticate via OpenID, where the flow is:

1. User visits guacamole server, unauthorized
2. Guacamole extension checks for id_token, doesn't find it, throws
GuacamoleInvalidCredentialsException
3. Javascript redirects user to identity provider site
4. User authorizes with identity provider
5. Identity provider redirects user back to guacamole site
(<site>/?id_token=...)
6. Javascript detects id_token, redirects user again (<site>/#/id_token=...)
(I don't fully understand the point of this step, but not relevant to my
actual question)
7. Guacamole extension receives the id token and verifies it.

This all works.

Now on my SAML extension, step 1-4 are conceptually the same, and work fine. 
Step 5 is where things break down.  The IDP isn't sending information back
in the URL, as is done with the id_token request parameter -- instead, it's
a POST with the SAMLRequest data in the request body.  I see this POST going
to the guacamole site.  

However, when it hits the extension, the request body is empty, which is not
what I want -- I want the SAMLRequest body that the IDP sent.

I /presume/ that what is happening is that client-side Javascript is
executing a separate POST to guacamole/api/tokens, and that it is this
request that is actually being handled by the authentication extension. 
However, this request does not contain the original request body, hence, my
problem.

Sadly, I'm not proficient enough in the JS framework to fully understand
what's going on here, if there's an easy way to pass the request body along,
or if I'm entirely off base.  If anyone could help me, I would very much
appreciate it.

Thanks in advance.



--
Sent from: http://apache-guacamole-incubating-users.2363388.n4.nabble.com/

Re: Handling a SAML POST response

Posted by Colin McGuigan <co...@walkingshadows.org>.
Yes.  You were entirely correct, that missing question mark was the problem. 
I feel more than a little silly for missing that.

The entire thing now works end to end.

Thank you all again for your assistance.  



--
Sent from: http://apache-guacamole-incubating-users.2363388.n4.nabble.com/

Re: Handling a SAML POST response

Posted by Nick Couchman <vn...@apache.org>.
On Thu, Sep 28, 2017 at 12:20 PM, Colin McGuigan <
colin_guacamole@walkingshadows.org> wrote:

> Nick;
>
> Thanks for all your help.  Let me elaborate.
>
> When I say I have a REST service, it's just as you described -- a WS
> annotated class that is returned from the authentication provider's
> getResource method.  I can call this REST service just fine, and know that
> it works.
>
>
Very nice.


> This service takes in as POST (from the SAML identity provider), calls the
> existing /api/tokens endpoint, passing all of the same content, and
> receives
> a Guacamole authentication token -- ie, the user is know authenticated by
> Guacamole (specifically by my authentication provider), and is stored in
> the
> session.  This also works.  I receive the token just fine.
>
> The problem is I need to pass this token, somehow, to the Guacamole UI so
> that when it calls /api/tokens itself, it can pass in the same token.  The
> essentials of the REST method:
>
>     @POST
>     @Path("/postredirect")
>     public Response redirectSamlPostToGet(@Context HttpServletRequest
> request, String content) throws GuacamoleException, URISyntaxException {
>         try {
>                 String token = callTokenService(request, content);
>                 return Response.seeOther(new URI("http://
> <site>/guacamole/#/token=" +
> token)).build();
>         } catch (Exception e) {
>                 logger.error("Error occurred in postredirect", e);
>                 throw new RuntimeException(e);
>         }
>     }
>
> There is no errors in the logs.  In network traffic I see the redirect
> happen correctly.  However, Guacamole is ignoring the token=<token> portion
> of the URL.  I've tried using id_token instead, but that is also ignored.
>
>
What if you try:

 return Response.seeOther(new URI("http://<site>/guacamole/#/?token=" +
token)).build();

(Add the ? between the token parameter and the Guacamole URL).  Does that
work?

-Nick

Re: Handling a SAML POST response

Posted by Nick Couchman <vn...@apache.org>.
On Thu, Sep 28, 2017 at 12:20 PM, Colin McGuigan <
colin_guacamole@walkingshadows.org> wrote:

> Nick;
>
> Thanks for all your help.  Let me elaborate.
>
> When I say I have a REST service, it's just as you described -- a WS
> annotated class that is returned from the authentication provider's
> getResource method.  I can call this REST service just fine, and know that
> it works.
>
> This service takes in as POST (from the SAML identity provider), calls the
> existing /api/tokens endpoint, passing all of the same content, and
> receives
> a Guacamole authentication token -- ie, the user is know authenticated by
> Guacamole (specifically by my authentication provider), and is stored in
> the
> session.  This also works.  I receive the token just fine.
>
> The problem is I need to pass this token, somehow, to the Guacamole UI so
> that when it calls /api/tokens itself, it can pass in the same token.  The
> essentials of the REST method:
>
>     @POST
>     @Path("/postredirect")
>     public Response redirectSamlPostToGet(@Context HttpServletRequest
> request, String content) throws GuacamoleException, URISyntaxException {
>         try {
>                 String token = callTokenService(request, content);
>                 return Response.seeOther(new URI("http://
> <site>/guacamole/#/token=" +
> token)).build();
>         } catch (Exception e) {
>                 logger.error("Error occurred in postredirect", e);
>                 throw new RuntimeException(e);
>         }
>     }
>
> There is no errors in the logs.  In network traffic I see the redirect
> happen correctly.  However, Guacamole is ignoring the token=<token> portion
> of the URL.  I've tried using id_token instead, but that is also ignored.
>
>
Hey, Colin,
I have a couple of follow-up questions for you regarding this.  I'm in the
midst of trying to implement a SAML authentication extension for Guacamole,
as well, and am probably 95% of the way there, but am running into a couple
of issues and curious if you had any insight from your experiences.

First, I decided, instead of generating my own token, to just try to pass
the SAMLResponse through as a parameter to the main Guacamole URL and then
process it that way.  I don't know if you tried this track at all, but I
ran into some errors with the headers being too large.  I bumped up the
header size on both Tomcat and Nginx, which got rid of the warnings, but
now the page just aborts loading - no apparent log errors, it just quits.
Does this match anything you experienced when trying to get this to work?
I don't really want the end users to have to adjust default configurations
just to get this to work, so that's less than ideal, anyway.  It looks to
me like the main source of this is that the "Referer" header that it passes
on is humongous - it contains the SAML response, plus several other items
embedded in it, that aren't really going to be useful or necessary for
this, but combining those with the actual SAMLResponse makes it larger than
the default 8k header size.

Second, when you did your token service (callTokenService above) to
generate the token you pass back to the main Guacamole URL, is that
something you're generating manually, embedding username (and whatever
other attributes) in that token, or are you actually calling the Guacamole
token generating service, so that it's already a valid Guacamole token?

Thanks,
Nick

Re: Handling a SAML POST response

Posted by Colin McGuigan <co...@walkingshadows.org>.
Nick;

Thanks for all your help.  Let me elaborate.

When I say I have a REST service, it's just as you described -- a WS
annotated class that is returned from the authentication provider's
getResource method.  I can call this REST service just fine, and know that
it works.

This service takes in as POST (from the SAML identity provider), calls the
existing /api/tokens endpoint, passing all of the same content, and receives
a Guacamole authentication token -- ie, the user is know authenticated by
Guacamole (specifically by my authentication provider), and is stored in the
session.  This also works.  I receive the token just fine.

The problem is I need to pass this token, somehow, to the Guacamole UI so
that when it calls /api/tokens itself, it can pass in the same token.  The
essentials of the REST method:

    @POST
    @Path("/postredirect")
    public Response redirectSamlPostToGet(@Context HttpServletRequest
request, String content) throws GuacamoleException, URISyntaxException {
    	try {
    		String token = callTokenService(request, content);
	    	return Response.seeOther(new URI("http://<site>/guacamole/#/token=" +
token)).build();
    	} catch (Exception e) {
    		logger.error("Error occurred in postredirect", e);
    		throw new RuntimeException(e);
    	}
    }

There is no errors in the logs.  In network traffic I see the redirect
happen correctly.  However, Guacamole is ignoring the token=<token> portion
of the URL.  I've tried using id_token instead, but that is also ignored.



--
Sent from: http://apache-guacamole-incubating-users.2363388.n4.nabble.com/

Re: Handling a SAML POST response

Posted by Nick Couchman <vn...@apache.org>.
>
>
>> So, I think the approach you need to take is that, within the SAML
>> extension itself, you need to create a REST endpoint that consumes handles
>> a POST call to it, processes the data from the POST, and then translates
>> that to the correct call to /guacamole/api/tokens to tell Guacamole that
>> the login has succeeded.  You can have a look at the other REST source code
>> to see code that creates these types of services:
>>
>> https://github.com/apache/incubator-guacamole-client/tree/
>> master/guacamole/src/main/java/org/apache/guacamole/rest
>>
>> I've not actually implemented an extension-specific REST endpoint myself,
>> so I can't provide very detailed instructions, but it is possible - Mike
>> can probably provide further guidance, if needed.
>>
>
>
Here's a quick-and-dirty example of an extension-specific REST endpoint.  I
just did a quick modification to the JDBC base module.

- First, I created a new class inside the extension code.  I created a new
directory called "rest" and a file called TestRESTModule.java:

---TestRESTModule.java---
package org.apache.guacamole.auth.jdbc.rest;

import com.google.inject.Inject;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleResourceNotFoundException;

@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class TestRESTModule {

    private final String hello = "Hello, world.";

    @GET
    @Path("hello")
    public String getHello() {

        return hello;

    }

}
---End TestRESTModule.java---

- Next, in the Authentication Provider part of the module (for JDBC it's in
the InjectedAuthenticationProvider.java file), locate the getResource()
method and have it return this class (don't forget to import it):

    @Override
    public Object getResource() throws GuacamoleException {
        return new TestRESTModule();
    }

- Finally, log in to Guacamole, then pull up a tab with the URL (I'm using
the PostgreSQL JDBC module):

http://guacamole.example.com/guacamole/api/ext/postgresql/hello?token=<YOUR
LOGIN TOKEN>

And you should see "Hello, world."

Obviously this isn't very useful, but should give you an idea of one way to
go about this.  Whatever class you return in getResource() can have the
necessary methods to process the SAML POST, read in the body of the POST,
and then accomplish whatever needs to be done to cause the login to succeed
and reload the page.

Hope this is of some use, or you've already figured it out! :-)

-Nick

Re: Handling a SAML POST response

Posted by Nick Couchman <vn...@apache.org>.
On Wed, Sep 27, 2017 at 6:31 PM, Nick Couchman <vn...@apache.org> wrote:

>
>
> On Wed, Sep 27, 2017 at 5:35 PM, Colin McGuigan <colin_guacamole@
> walkingshadows.org> wrote:
>
>> So I went ahead and created an external web service that internally calls
>> /guacamole/api/tokens, and then redirects to /guacamole/#/token=<token>
>>
>
> When you say you created an external web service, what do you mean?
>
>
>>
>> Doesn't work.
>>
>> Investigation of the network traffic shows that the /guacamole/api/tokens
>> call does not have the token in it at all (in Mike's OpenID implementation
>> id_token is passed along this way, and I was hoping it would work the same
>> for token).  Changing the name of the parameter, so it's now redirecting
>> to
>> /guacamole/#/id_token=<token> also does not pass a token_id parameter to
>> /guacamole/api/tokens, which confuses me, because I saw this behavior with
>> the OpenID plugin.
>>
>> So new questions:
>>
>> 1) Is this a valid approach?  Ie, can a Guacamole authorization token even
>> be passed around in this manner?
>>
>> 2) Why is the token not being passed from /guacamole/#/token=<token> to
>> /guacamole/api/tokens?
>>
>>
>>
> So, I think the approach you need to take is that, within the SAML
> extension itself, you need to create a REST endpoint that consumes handles
> a POST call to it, processes the data from the POST, and then translates
> that to the correct call to /guacamole/api/tokens to tell Guacamole that
> the login has succeeded.  You can have a look at the other REST source code
> to see code that creates these types of services:
>
> https://github.com/apache/incubator-guacamole-client/
> tree/master/guacamole/src/main/java/org/apache/guacamole/rest
>
> I've not actually implemented an extension-specific REST endpoint myself,
> so I can't provide very detailed instructions, but it is possible - Mike
> can probably provide further guidance, if needed.
>

Some basic information on extension-specific REST resources is available
here:

http://guacamole.incubator.apache.org/doc/gug/guacamole-ext.html#ext-rest-resources

-Nick

Re: Handling a SAML POST response

Posted by Nick Couchman <vn...@apache.org>.
On Wed, Sep 27, 2017 at 5:35 PM, Colin McGuigan <
colin_guacamole@walkingshadows.org> wrote:

> So I went ahead and created an external web service that internally calls
> /guacamole/api/tokens, and then redirects to /guacamole/#/token=<token>
>

When you say you created an external web service, what do you mean?


>
> Doesn't work.
>
> Investigation of the network traffic shows that the /guacamole/api/tokens
> call does not have the token in it at all (in Mike's OpenID implementation
> id_token is passed along this way, and I was hoping it would work the same
> for token).  Changing the name of the parameter, so it's now redirecting to
> /guacamole/#/id_token=<token> also does not pass a token_id parameter to
> /guacamole/api/tokens, which confuses me, because I saw this behavior with
> the OpenID plugin.
>
> So new questions:
>
> 1) Is this a valid approach?  Ie, can a Guacamole authorization token even
> be passed around in this manner?
>
> 2) Why is the token not being passed from /guacamole/#/token=<token> to
> /guacamole/api/tokens?
>
>
>
So, I think the approach you need to take is that, within the SAML
extension itself, you need to create a REST endpoint that consumes handles
a POST call to it, processes the data from the POST, and then translates
that to the correct call to /guacamole/api/tokens to tell Guacamole that
the login has succeeded.  You can have a look at the other REST source code
to see code that creates these types of services:

https://github.com/apache/incubator-guacamole-client/tree/master/guacamole/src/main/java/org/apache/guacamole/rest

I've not actually implemented an extension-specific REST endpoint myself,
so I can't provide very detailed instructions, but it is possible - Mike
can probably provide further guidance, if needed.

Once you have that working, when you call the SAML authentication, you need
to make sure that SAML is redirecting back to your new REST endpoint, which
will then process the body of the POST request, authenticate the user in
Guacamole, and redirect on to the Guacamole home page or connection.

Mike or James, am I providing accurate information?

-Nick

Re: Handling a SAML POST response

Posted by Colin McGuigan <co...@walkingshadows.org>.
So I went ahead and created an external web service that internally calls
/guacamole/api/tokens, and then redirects to /guacamole/#/token=<token>

Doesn't work.

Investigation of the network traffic shows that the /guacamole/api/tokens
call does not have the token in it at all (in Mike's OpenID implementation
id_token is passed along this way, and I was hoping it would work the same
for token).  Changing the name of the parameter, so it's now redirecting to
/guacamole/#/id_token=<token> also does not pass a token_id parameter to
/guacamole/api/tokens, which confuses me, because I saw this behavior with
the OpenID plugin.

So new questions:

1) Is this a valid approach?  Ie, can a Guacamole authorization token even
be passed around in this manner?

2) Why is the token not being passed from /guacamole/#/token=<token> to
/guacamole/api/tokens?



--
Sent from: http://apache-guacamole-incubating-users.2363388.n4.nabble.com/

Re: Handling a SAML POST response

Posted by Colin McGuigan <co...@walkingshadows.org>.
Thanks for the answers; I certainly didn't expect it to be so quick!

> Do you have the code available somewhere - github or something like that?
> I'd be happy to download it and take a look at what's happening.

Unfortunately, no, it's just POC at this point.

> Out of curiosity, is this configurable with the IDP?  I know when I wrote
> the CAS extension there's an option when you request CAS authentication
> that
> allows you to specify how you get the ticket back - as a post or get
> response.  I'm curious if the IDP you're using has something similar?  I
> think I ran into similar issues when I was trying to make the CAS
> extension
> use a POST instead of GET, but I can't remember.

The SAML protocol does support redirect responses, yes, but the SAML
response that comes back, with all of the assertions, is too large for a
redirect URL (~9,200 bytes, max length for a URL is ~2,048).  Unlike OpenID,
it doesn't look like there's any "here's a token, call this service to look
it up" implementation, which is annoying.

So:

I did find the work that Mike Jumper did for allowing extensions to host
their own REST APIs.  I am using this to host a method that is receiving the
SAML POST form.

This works fine, and I can validate the response.  The problem I have now is
getting that information into guacamole's session.

My first thought was just to call the same code as /api/tokens, but the
authentication code (authenticationService, etc) do not seem to be in
guacamole-common or guacamole-ext.  So the options that I've come up with so
far are:

1) Manually call /api/tokens from my ext web service, take it's token,
redirect to the main page passing the token, which will get passed back to
/api/tokens when the main page calls it.  This seems so pretty klunky, but
involves the least duplication of code.

2) What Mike suggested: Validate SAML response, generate my own token (with
blackjack, and hookers!), store it somewhere, pass it back, and then look it
up when /api/tokens gets called.  This seems less klunky than #1, but
requires essentially duplicating the token management code that's already in
use.

3) Throw abstraction out the window and just add my code to
guacamole-client.  Horribly klunky, torpedoes future upgrades without
additional effort.

4) Mystery fourth option

Is there a #4 that I'm missing?



--
Sent from: http://apache-guacamole-incubating-users.2363388.n4.nabble.com/

Re: Handling a SAML POST response

Posted by Mike Jumper <mi...@guac-dev.org>.
On Fri, Sep 22, 2017 at 2:39 PM, Colin McGuigan <
colin_guacamole@walkingshadows.org> wrote:

> tldr: The SAML POST body is getting thrown away, and I don't know how to
> keep
> that from happening.
>
> Longer: I'm writing a SAML authentication extension, based off of Mike
> Jumper's OpenID extension:
> https://github.com/mike-jumper/guacamole-auth-openid
>
>
FYI - that code is missing recent changes, as I moved things over to the
"openid-auth" branch of my development fork of incubator-guacamole-client,
to prepare things to be merged into mainline:

https://github.com/mike-jumper/incubator-guacamole-client/tree/openid-auth

...
> 5. Identity provider redirects user back to guacamole site
> (<site>/?id_token=...)
> 6. Javascript detects id_token, redirects user again
> (<site>/#/id_token=...)
> (I don't fully understand the point of this step, but not relevant to my
> actual question)
>

With OpenID Connect's "implicit flow" (which is what the
guacamole-auth-openid extension implements), the IDP sends the token back
within the URL fragment, with that token intended to be handled by
client-side code. The Guacamole extension manually rearranges that token
such that Guacamole's existing automatic authentication code will forward
it along to the authentication service for server-side verification and
handling.

OpenID Connect also provides a different flow which involves a POST to a
defined service, but Guacamole's authentication system doesn't work this
way. There is a .../api/tokens REST service which produces a JSON response
containing the auth token to be used for all future requests. POSTing to
that service would result in the generation of an auth token, but would not
result in the user being redirected back to Guacamole.


> ... Now on my SAML extension, step 1-4 are conceptually the same, and work
> fine.
> Step 5 is where things break down.  The IDP isn't sending information back
> in the URL, as is done with the id_token request parameter -- instead, it's
> a POST with the SAMLRequest data in the request body.  I see this POST
> going
> to the guacamole site.
>
> However, when it hits the extension, the request body is empty, which is
> not
> what I want -- I want the SAMLRequest body that the IDP sent.
>
>
The POST is not hitting the extension; it's hitting a static file.

I /presume/ that what is happening is that client-side Javascript is
> executing a separate POST to guacamole/api/tokens, and that it is this
> request that is actually being handled by the authentication extension.
> However, this request does not contain the original request body, hence, my
> problem.
>
>
Yes, this is exactly what is happening. The URL that the IDP is using is
not a service (it's static HTML and JavaScript), and the JavaScript will
not be able to see the body of that POST.

I see two possibilities:

1) Reconfigure things with the IDP such that the necessary token is
included in the URL, ideally via normal query parameters. Guacamole will
forward these automatically, and your extension will receive them in the
authentication request. Not sure whether this is possible with SAML.

2) Add a custom REST service within your extension that can accept the POST
and deal with the SAML body, generating some unique temporary token and
redirecting the user back to the main Guacamole page, including the token
in the URL. When the user is taken to that URL in their browser, Guacamole
will automatically send the token within the URL through the authentication
system, and your extension can validate and complete the process.

- Mike

Re: Handling a SAML POST response

Posted by vnick <vn...@apache.org>.
Colin McGuigan wrote
> tldr: The SAML POST body is getting thrown away, and I don't know how to
> keep
> that from happening.
> 
> Longer: I'm writing a SAML authentication extension, based off of Mike
> Jumper's OpenID extension:
> https://github.com/mike-jumper/guacamole-auth-openid

Do you have the code available somewhere - github or something like that? 
I'd be happy to download it and take a look at what's happening.


> Now on my SAML extension, step 1-4 are conceptually the same, and work
> fine. 
> Step 5 is where things break down.  The IDP isn't sending information back
> in the URL, as is done with the id_token request parameter -- instead,
> it's
> a POST with the SAMLRequest data in the request body.  I see this POST
> going
> to the guacamole site.

Out of curiosity, is this configurable with the IDP?  I know when I wrote
the CAS extension there's an option when you request CAS authentication that
allows you to specify how you get the ticket back - as a post or get
response.  I'm curious if the IDP you're using has something similar?  I
think I ran into similar issues when I was trying to make the CAS extension
use a POST instead of GET, but I can't remember.


> I /presume/ that what is happening is that client-side Javascript is
> executing a separate POST to guacamole/api/tokens, and that it is this
> request that is actually being handled by the authentication extension. 
> However, this request does not contain the original request body, hence,
> my
> problem.
> 
> Sadly, I'm not proficient enough in the JS framework to fully understand
> what's going on here, if there's an easy way to pass the request body
> along,
> or if I'm entirely off base.  If anyone could help me, I would very much
> appreciate it.

I think you're probably right, but it should be possible to pass it through. 
Anyhow, if you can make the code available I'll take a look and see if I can
figure anything out.

-Nick



--
Sent from: http://apache-guacamole-incubating-users.2363388.n4.nabble.com/