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

Add a forward authentication option v2 #1030

Closed
wants to merge 4 commits into from

Conversation

vladshub
Copy link
Contributor

It seems that there is no progress in #764 so I have re-based the work that was done there and added something that I have been waiting for #764 to be merged.
Please take a look
Now it is possible to delegate the authentication to a third party agent, like a OAuth endpoint. This authentication option extracts parameters from the original request, forward then to a authentication endpoint, if it succeed part of the response body can be added as json serialized headers.

Copy link
Member

@emilevauge emilevauge left a comment

Choose a reason for hiding this comment

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

Hey @vladshub, thanks a lot :)
I have few comments before merging:

  • What format is {} used in log.* ?
  • Could you add some documentation in doc/toml.md ?
  • Could you squash your commits ?

/cc @containous/traefik @diegooliveira


data := make(map[string]interface{})
if err := json.Unmarshal(body, &data); err != nil {
log.Debugf("Auth failed...")
Copy link
Member

Choose a reason for hiding this comment

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

You may log err here ;)

default:
byteValue, err := json.Marshal(object)
if err != nil {
log.Debugf("failed to Marshal object: {}", object)
Copy link
Member

@emilevauge emilevauge Jan 11, 2017

Choose a reason for hiding this comment

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

You may log err here ;)

@vladshub
Copy link
Contributor Author

Hi @emilevauge,
I have made the changes that you requested please take a look.

@vladshub vladshub force-pushed the feat-encoded-header branch 2 times, most recently from b38cc35 to 57233a5 Compare January 12, 2017 21:04
Copy link
Member

@emilevauge emilevauge left a comment

Choose a reason for hiding this comment

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

Thanks a lot @vladshub
LGTM
/cc @containous/traefik


## Enable authentication forwarding

Uses a query param from the request to check for authentication with an auth service.
Copy link
Contributor

Choose a reason for hiding this comment

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

Only a query param, what about a specific custom header?

@Toflar
Copy link

Toflar commented Jan 16, 2017

Hey, thank you for your contribution! I was waiting for this :-)
Any chance this could get another PR for httpcache? Right now it forwards every single request even though the response of the auth back end might be cached. That would be a superbe addition. In the meantime, I'll try to make a comment to rephrase the docs a little because I think they could be improved :-)

w.WriteHeader(http.StatusInternalServerError)
return
}
forwardReq.Header.Add("Accept", "application/json")
Copy link

Choose a reason for hiding this comment

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

Can we add the original Accept header here? Otherwise an auth back end cannot know what was sent. Maybe just use X-Accept? Not sure though as X headers should not be used anymore :)

Copy link
Contributor

Choose a reason for hiding this comment

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

@Toflar this Accept is to negotiate with the authentication end point. The objects lib (github.com/stretchr/stew/objects) that is used to extract information from the authentication end point and replay in the original request only knows how to work with Json. The original request might be in any format.

Copy link

Choose a reason for hiding this comment

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

@diegooliveira I see, I must have misunderstood the PR. I thought it forwards the original request as discussed in your PR.

# address = ":80"
# [entryPoints.http.auth.forward]
# address = "http://authserver.com/auth"
# [entryPoints.http.auth.Forward.requestParameters.email]
Copy link

Choose a reason for hiding this comment

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

Any reason why upper and lower case are mixed here? forward and Forward?

[entryPoints.http.auth.Forward.responseReplayFields.userName]
path = "user.name"
as = "" # No name transformation
in = "parameter"
Copy link

Choose a reason for hiding this comment

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

I didn't quite get it so I (tried) to read the source code an rewrote it a bit, what do you think of this (please also make sure it's correct what I'm saying :D):

Proposal:

Authentication forwarding is a mechanism to allow for arbitrary authentication back ends to be implemented. It works by forwarding any incoming request from traefik proxy to a specified endpoint (the setting key entryPoints.http.auth.forward.address). This endpoint address is called "authentication back end". The response of the authentication back end is then evaluated and depending on the HTTP status code the original request to traefik is either allowed or denied. The authentication back end has to send a response with an HTTP status code 200 to allow access. Any other status code will be used as response status code by traefik to the original request (so if your back end sends a response with 503, traefik will also send a 503 response).

In addition to this, traefik provides basic functionality to modify the original request's query parameters before they are sent to the authentication back end. This allows you to map a request parameter (in this case token) from e.g. traefik.com/secret?token=foobar to authserver.com/auth?theToken=foobar. Use the keywords name and as in the entryPoints.http.auth.Forward.requestParameters setting key as shown in the example.

Moreover, you can also replay certain information from the back end authentication response back to the original request received by traefik. For this to work, your authentication back end must send a JSON response.

Now I'm lost a bit because I would like to add the information about replaying back end auth information and I understand the header option but can you explain the parameter option for me?

Copy link
Contributor

Choose a reason for hiding this comment

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

The parameter option will take data from the auth server response JSON and at it as a query parameter to the backend server if authentication is successful. Header option will likewise take the data but send it as a header.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I could just add this sequence diagram if this would help clarify the flow.
traefik authentication forwarding

Copy link

Choose a reason for hiding this comment

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

I see. I thought it forwards the original request which would be way more powerful. See the discussion in the other PR for use cases: #764
Do you think we could do that instead? That would also make the configuration easier if you ask me.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 for request forwarding (headers only, not sure the body is necessary?), this way the auth service has all the info available and a whole load of config is avoided.

Copy link

Choose a reason for hiding this comment

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

In the other PR we discussed that this should be a config and not too big of a deal to implement. Why would you prevent users from forwarding the whole body if they need it?

Copy link
Contributor

Choose a reason for hiding this comment

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

@Toflar the original plans was to make a second pull request to implement the "full body forward", I think it's a good plan.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Toflar I have implemented forwarding of headers, regarding "forwarding the full body" I agree with @diegooliveira it should be another PR I have started implementing it but there are some aspects that look strange for example:

  1. I would need to force the request to be JSON but what will happen if the original request was XML will the auth server know how to handle it?
  2. Sending random body (and I say random because there is no original route in the request) to the auth server will be without context regarding the destination of the request.
  3. What HTTP method should be used since currently we use GET to send the query params to send the body we would need to post it and back to point 2 if we don't use the same verb or provide this context in the request we will have a problem and the body will be useless.
  4. To overcome issues issues 2-3 we would have to do one of 2 things create some kind of generic message that will be posted to the auth server with route, method and body if such exists and implement in the auth server support for such a request or a less good solution is to forward the request to the auth server with the same method but loos the path along the way at which point your auth server would have to be able to get the query string params that we want to forward for authentication and read the body if one exists in addition support all http methods in one end point...

Those are the issues just from the first 5 minuets of the implementation. I really think it would be best to open a different PR for this.

Copy link

Choose a reason for hiding this comment

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

I would need to force the request to be JSON but what will happen if the original request was XML will the auth server know how to handle it?

That's exactly the point. If you forward the request just like it is coming in, you are not responsible for that. The auth back end has to decide what it wants to handle. This allows the back end to provide authentication in whatever format, on whatever conditions, no matter if it's headers, body content, json or xml etc. If it gets XML and can only handle JSON it seems like the request is wrong so it should probably send a non 200 response.

Sending random body (and I say random because there is no original route in the request) to the auth server will be without context regarding the destination of the request.

The original Host has to be transferred, yes. We could use Traefik-Auth-Original-Host for that (for example) or make it configurable in best case.

What HTTP method should be used since currently we use GET to send the query params to send the body we would need to post it and back to point 2 if we don't use the same verb or provide this context in the request we will have a problem and the body will be useless.

The same as the original request. If it was a POST, we POST to the auth back end. If it was a GET, we GET.

Don't get me wrong, I really appreciate your work on this matter! I'm just trying to point out my ideas. The current PR is nice but it's very limiting and I'm not sure if it's a good idea to merge this and change it again later on. It will cause a lot of different configuration options whereas if we implemented it in a generic way, we basically leave it all to the auth back end. The only thing you can configure is if you want to forward the body or just the headers to save bandwidth if you don't need the body.

Side note: This is very much inspired by the way nginx has been doing it for a long time with the ngx_http_auth_request_module. You can find the C source here.

So for me, to cover all use cases, the config should look like this and the rest should be left to the auth back end:

[entryPoints.http.auth.forward]
    endpoint = "http://authserver.com/auth"
    forwardBody = true # default: false
    [entryPoints.http.auth.forward.headerMap]
    "Host" = "Traefik-Auth-Original-Host"   

@drampelt
Copy link
Contributor

Thanks for taking initiative on this, I've also been looking forward to it. I think it would be helpful to add more options for passing data to the auth server such as cookies and headers as well.

I'm fairly new to Go but I added cookies and a few fixes/improvements in my fork as I needed them for my auth server. You're welcome to use it or I can create a new PR with improvements once this is merged in.

if forwardResponse.StatusCode != 200 {
log.Debugf("Remote error %s. StatusCode: %s", forward.Address, forwardResponse.StatusCode)
w.WriteHeader(forwardResponse.StatusCode)
w.Write(body)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should probably return after this line, otherwise it continues trying to parse the JSON response even though there was an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks and done.

@diegooliveira
Copy link
Contributor

Hello all, I'm the author of #764. Sorry disappearing, vacations in the year end plus a huge project started January 2. @vladshub do you need any help to review this pr?

@diegooliveira
Copy link
Contributor

@vladshub there was a call to be allowed to replay the original request payload to the authentication end point. Do you plan to do that?

@vladshub vladshub force-pushed the feat-encoded-header branch 5 times, most recently from 086c28d to b6e7a9a Compare January 18, 2017 08:05
    A forward authentication that extracts parameters from the request and
then call a authenticate endpoint. If the authentication outcome is a 200
it allows to extract information from the response body and add to the
proxy call.
@vladshub vladshub force-pushed the feat-encoded-header branch 3 times, most recently from fe48a08 to 313e24b Compare January 18, 2017 10:28
@vladshub
Copy link
Contributor Author

@drampelt I have implemented forward of all headers so you get the cookies as well 😄

@emilevauge emilevauge closed this Jan 26, 2017
@Toflar
Copy link

Toflar commented Jan 26, 2017

As mentioned elsewhere, I'm not coding in Go yet. All I can contribute is what I already did here: review and comment and I'll do that again if it helps :-)

@j0hnsmith
Copy link
Contributor

Happy to help.

@vladshub
Copy link
Contributor Author

@emilevauge I can help but there is one thing.
There should be a unified design that everyone agrees on before we start.
Saying it should be like Nginx is not design since Nginx provides a much more elaborate configuration and manipulation....

@diegooliveira
Copy link
Contributor

Are we talking about a RFC process, something like this? I like the idea.

@vladshub
Copy link
Contributor Author

vladshub commented Jan 28, 2017

@diegooliveira that feels as an overkill I was talking more about a general feature map/plan and explicit boundaries ....

@j0hnsmith
Copy link
Contributor

I was thinking that would should start by getting consensus on the features that should definitely be included for v1 but here isn't really the place to do that.

I was just going to ask if there's an irc/slack channel but I found it (https://traefik.herokuapp.com/), joined as j0hnsmith.

@xeor xeor mentioned this pull request Feb 2, 2017
@emilevauge
Copy link
Member

@vladshub @Toflar @drampelt @diegooliveira @j0hnsmith
Who wants to take the lead on this and open a proposal (in issues) ?

@diegooliveira
Copy link
Contributor

@emilevauge @vladshub @Toflar @drampelt @diegooliveira @j0hnsmith what about opening a shared Google doc to make a draft proposal, this way we may get to a better solution. If you agree, please send a message with your email address and I'll create the doc with write privilege.

@Toflar
Copy link

Toflar commented Feb 2, 2017

I guess the simplest solution for now is as follows:

First step

  • forward only the headers, never the body, to a configurable auth_backend. Keep the original Host as Traefik-Original-Host (or make it configurable). Always use GET.
  • If the response code of the auth_backend is 200, allow the request to be processed. Otherwise respond by the same response code and body content of the auth_backend response.

Second step

  • Use something like httpcache to make sure the auth_backend is not fired by requests that are not needed.

Third step

  • Discuss if forwarding POST values makes sense (don't think so anymore but anyway)
  • Add advanced stuff like body processing and mapping onto the auth request (as done in this PR but should maybe also be done for XML using xPath or so?)
  • etc.

@Toflar
Copy link

Toflar commented Feb 2, 2017

Oh, that was just a little too late :) Maybe we can also just use a specific channel on traefik.slack.com?

@emilevauge
Copy link
Member

emilevauge commented Feb 2, 2017

@diegooliveira I like Google Doc, but I would prefer a simpler solution (without invite needed).
@Toflar slack is great, but we need to maintain a proposal, with comments, versions. Not sure this is the best tool for that.

What about a simple gist ? Simple, revisions, comments, and on Github :)

@Toflar
Copy link

Toflar commented Feb 2, 2017

Sure, feel free to create one. I'm okay with whatever solution you choose :)

@emilevauge
Copy link
Member

Let's use Google Doc instead (revisions need git on gist, not that simple ;) ): Authentication middleware proposal

@ammaskartik
Copy link

@emilevauge I am now using Traefik as our API "gateway" and am very interested in the auth middleware, could help as well if wanted/needed. Could i get read access to the proposal to review?

@drasko
Copy link

drasko commented May 1, 2017

Is anyone working on implementation (i.e. is proposal finished)?

@meggarr
Copy link

meggarr commented May 3, 2017

Is the proposal locked down and ready for development?

@ldez
Copy link
Contributor

ldez commented May 3, 2017

Currently we don't work on the feature, if you want to work on implementation and make a PR, we will be very pleased with this.

@meggarr
Copy link

meggarr commented May 4, 2017

But even the "Authentication middleware proposal" is not public... :(, and @vladshub's PR is rejected, just confused about about the plan..

@timoreimann
Copy link
Contributor

@meggarr you should be able to ask for permissions once you try to open the proposal, and @emilevauge should afterwards grant it to you.

@jkaberg
Copy link

jkaberg commented May 15, 2017

Something that simulates nginx's auth_request would be highly appreciated. If implemented like in NGINX, one would be abel to use existing solutions like Authelia (LDAP + 2FA) which is a big plus.

@drampelt
Copy link
Contributor

Hi everyone, back in January I ended up just forking the project and implementing a solution very specific to my needs at the time, and then unfortunately never got a chance to continue working on a standard solution.

I've opened up #1972 with a simplified implementation that I think is a good starting point for this feature. @emilevauge @vladshub @Toflar @diegooliveira @j0hnsmith if any of you are still interested in this I'd love to hear your thoughts.

@vladshub vladshub deleted the feat-encoded-header branch August 12, 2018 16:53
@Youssef-Harby

This comment was marked as off-topic.

@ldez
Copy link
Contributor

ldez commented Jul 16, 2022

Hello,

it's not the right place to ask a question, the community forum is the right place.
I can see that you already send a post https://community.traefik.io/t/cannot-pass-query-params-to-forword-auth-server-not-headers/15127

So I will flag your comment as offtopic.

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

Successfully merging this pull request may close these issues.