-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
WIP: http_filter: REST-based external authentication filter #2621
Conversation
Signed-off-by: Flynn <flynn@datawire.io>
Signed-off-by: Flynn <flynn@datawire.io>
…fying headers OK to be added. Signed-off-by: Flynn <flynn@datawire.io>
Signed-off-by: Flynn <flynn@datawire.io>
…hat's what. Signed-off-by: Flynn <flynn@datawire.io>
…d not the map. Sigh. Signed-off-by: Flynn <flynn@datawire.io>
Signed-off-by: Flynn <flynn@datawire.io>
…ixes emissary-ingress/emissary#154) Signed-off-by: Flynn <flynn@datawire.io>
SIgned-off-by: Flynn <flynn@datawire.io>
Thanks for getting this PR up for discussion, and the super detailed PR description. Tagging @saumoh here, hopefully we can kick off some discussion on the various authentication filter approaches here. It looks like some CI tests are failing although I didn't look very deeply at them. If you can investigate those in the meantime that'd be great. |
@louiscryan @wora @mandarjog @kyessenov @spikecurtis @rshriram Looping in folks who were involved in the earlier ext_auth PRs. I think we should have folks take a pass over the design here and offer up thoughts on how we can address the use case you describe with ideally a single ext auth approach. |
Thanks @kflynn ! Another way to slice the big distinctions between Ambassador's auth and the Many of the use cases you describe that Ambassador's filter supports are around sophisticated authentication flows: redirect unauthenticated users, attach authentication tokens, attach security claims, etc. This stuff is super useful! Moving forward I think it is important for us to take a modular approach and keep authentication and authorization separate. On the Istio project we are prototyping authentication filters and associated policies---I would love to see those efforts converged with the use cases described here. |
@dnoe Yup, looking at CI failures now. I'm actually a bit confused by the Clang build failures -- I ran the tests with In any case, I can definitely reproduce the failure here (with |
Signed-off-by: Flynn <flynn@datawire.io>
@spikecurtis Hearing more about what Istio's been exploring sounds great -- having Istio and Ambassador play nicely on this would be a really good thing. If realtime seems easiest there, happy to jump on a call or set up a face visit. And yes, it was a deliberate decision to allow Ambassador's auth to span both authn and authz -- while separating them certainly has advantages (especially in composability), allowing a single filter to span both avoids a fair amount of complexity, both in the code and in the user experience. It'll be very interesting to see if there's a good way to get the best of both worlds there. |
@kflynn Some Istio docs about authn (note you might need to join Istio Dev Group to view) |
I tend to agree with Flynn here that there is utility in allowing this
functionality to span authn & authz. While we may try to promote as much
authn functionality inline into Envoy that will not scale to all use-cases.
Similarly the remote authorization mechanism does need to be able to
influence the error response to the client in ways that we are unlikely to
want to configure into Envoy directly. If this means that 'authorization'
is too narrow a name for whats in Envoy today we should rename it
…On Thu, Feb 15, 2018 at 10:38 PM, Flynn ***@***.***> wrote:
@spikecurtis <https://github.com/spikecurtis> Hearing more about what
Istio's been exploring sounds great -- having Istio and Ambassador play
nicely on this would be a really good thing. If realtime seems easiest
there, happy to jump on a call or set up a face visit.
And yes, it was a deliberate decision to allow Ambassador's auth to span
both authn and authz -- while separating them certainly has advantages
(especially in composability), allowing a single filter to span both avoids
a fair amount of complexity, both in the code and in the user experience.
It'll be very interesting to see if there's a good way to get the best of
both worlds there.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2621 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AIoKPDmtcHM6jBIUMCUKZOYgayG-Pjf7ks5tVSJagaJpZM4SHFyv>
.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First pass on C++ nits. Won't speak with authority as to the non-language-related content of the diff as I'm new to the repo (this is my first review), but it seems peculiar to have to do an auth RPC on every request.
source/common/http/filter/extauth.cc
Outdated
namespace Http { | ||
|
||
/** | ||
* A pass-through filter that talks to an external authn/authz service. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Copy-pasting these (excellent) comments in header and source is destined for mismatch. Perhaps refer to one from the other?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
source/common/http/filter/extauth.cc
Outdated
*/ | ||
|
||
static LowerCaseString header_to_add(std::string("x-ambassador-calltype")); | ||
static LowerCaseString value_to_add(std::string("extauth-request")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't trigger here because of the literal, but be careful of most vexing parse. Prefer uniform initialization.
https://en.wikipedia.org/wiki/Most_vexing_parse#Uniform_initialization_syntax
static LowerCaseString header_to_add{std::string{"x-ambassador-calltype"}};
Applies elsewhere in this diff
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
// ...and hang onto a pointer to the original request headers. | ||
// | ||
// Note that using a reference here won't work. Move semantics are the | ||
// root of this issue, I think. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you generally want to avoid reference members, it's easier to have dangling references when you can't explicitly null/null check them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see reference members are pretty common in envoy. I stand by my comment but convention is convention...
source/common/http/filter/extauth.cc
Outdated
// RequestMessageImpl to hold all the details, and start it off as a | ||
// copy of the incoming request's headers. | ||
|
||
MessagePtr reqmsg(new RequestMessageImpl(HeaderMapPtr{new HeaderMapImpl(headers)})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uniform initialization
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
source/common/http/filter/extauth.h
Outdated
/** | ||
* ExtAuth filter itself. | ||
*/ | ||
class ExtAuth : Logger::Loggable<Logger::Id::filter>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you do want private inheritance, do so explicitly
source/common/http/filter/extauth.cc
Outdated
if (!config_->allowed_headers_.empty()) { | ||
// Yup. Let's see if any of them are present. | ||
|
||
for (std::string allowed_header : config_->allowed_headers_) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const ref? you're copying it into key, this copy is unnecessary
source/common/http/filter/extauth.cc
Outdated
if (!value.empty()) { | ||
// Not empty! Copy it into our request_headers_. | ||
|
||
std::string valstr(value.c_str()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uniform init
source/common/http/filter/extauth.cc
Outdated
static LowerCaseString header_to_add(std::string("x-ambassador-calltype")); | ||
static LowerCaseString value_to_add(std::string("extauth-request")); | ||
|
||
ExtAuth::ExtAuth(ExtAuthConfigConstSharedPtr config) : config_(config) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::move
the pointer in, especially if this filter is constructed per-request
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about ExtAuth::ExtAuth(const ExtAuthConfigConstSharedPtr& config) : config_(config) {}
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That still copies the shared pointer. Passing by value and moving avoids the copy in this ctor. The caller can move their copy into the ctor if possible.
source/common/http/filter/extauth.cc
Outdated
|
||
auth_request_ = | ||
config_->cm_.httpAsyncClientForCluster(config_->cluster_) | ||
.send(std::move(reqmsg), *this, Optional<std::chrono::milliseconds>(config_->timeout_)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, our Optional should be able to infer the type here. I'll look into either adding that or ideally switching to abseil/folly/whatever
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good point. It looks like that there is already a TODO for replacing optional. Would be possible to address this in a separated PR?
envoy/include/envoy/common/optional.h
Line 9 in 38d8271
* TODO: Replace with https://github.com/abseil/abseil-cpp/blob/master/absl/types/optional.h |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I'll find/create an issue and get to it eventually..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
source/common/http/filter/extauth.cc
Outdated
// What did we get back from the auth server? | ||
|
||
uint64_t response_code = Http::Utility::getResponseStatus(response->headers()); | ||
std::string response_body(response->bodyAsString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move body? generally fix extra copies of response body/headers in this method as you shouldn't need any
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
This is the part I like. Though I am not sure shipping an entire html body as part of the response is helpful all the time. One option might be to indicate a template file to be used as a body. I haven’t read the code yet, but for something like this, it might be useful to have a route level opaque metadata that triggers the filter, such that people can enable this for a few URIs (eg /login) out of all the routes. (Might alleviate the need for caching in short term). |
@jsedgwick Thanks for checking over the code! More comments inline as we dig into those points. @louiscryan, @rshriram Yeah -- in practice, the flexibility has proven a huge win, and we haven't seen much evidence that shipping the HTML body for the error case is a big problem yet. Admittedly we tend not to send large HTML bodies: mostly for us it's a 401 with a Finally, @spikecurtis, thanks for the pointers! I'm already a member of the |
I recommend the filter to be configurable. The set of headers used for auth
should be decided by the operators, not blindly forward everything. For
common cases, you only need a few headers.
Regarding the HTML response, that should be configurable as well. Let's say
you are using Facebook or Google for authentication, do you really want to
trust them to return HTML to your clients, which could be an IoT device or
a financial partner of your business.
Everyone wants to have more control over others, it doesn't mean everyone
else should trust them. Injecting HTML with JavaScript could be its own
disaster in the worst case scenario.
|
Signed-off-by: Gabriel <gsagula@gmail.com>
@jsedgwick Thank you for the code review. I will be working on this PR from now on. Fixes will be pushed later this evening (PST). |
Signed-off-by: Gabriel <gsagula@gmail.com>
Signed-off-by: Gabriel <gsagula@gmail.com>
Signed-off-by: Gabriel <gsagula@gmail.com>
Signed-off-by: Gabriel <gsagula@gmail.com>
Signed-off-by: Gabriel <gsagula@gmail.com>
source/common/http/filter/extauth.cc
Outdated
|
||
ENVOY_STREAM_LOG(trace, "ExtAuth Auth responded with code {}", *callbacks_, response_code); | ||
|
||
if (!response_body.empty()) { | ||
if (!response->body()->length()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot to roll back this. I will do it on the next pass.
@jsedgwick Feel free to take another look. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM except one thing, code wise. I will try to learn the context and give semantic feedback as well, though it looks like you're getting that from a few angles already :)
json_config.getStringArray("allowed_headers", true), prefix}); | ||
|
||
return [config](Http::FilterChainFactoryCallbacks& callbacks) -> void { | ||
callbacks.addStreamDecoderFilter(std::make_shared<Http::ExtAuth>(config)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so after the change to ExtAuth
ctor, this is where you can std::move config around to have zero-copy behavior
return [config = std::move(config)](Http::FilterChainFactoryCallbacks& callbacks) mutable -> void {
callbacks.addStreamDecoderFilter(std::make_shared<Http::ExtAuth>(std::move(config))));
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, @jsedgwick. I was a bit confused about where I should do the move, but that makes perfect sense.
Met with @kflynn and @dpn today, the plan to move forward with this is to get some agreement at the design level on how this and ext_authz relate. @kflynn volunteered to write up the use cases that inspired this approach to auth filtering in a Google Doc; hopefully the Istio folks can extend and we can agree on the core use cases that need to be addressed. Ideally we can converge solutions and be able to cover all use cases. |
Please share the doc with me when it is ready. I want to make sure the
design of the API meets practical requirements. Thanks.
|
I'll post a link here when the doc ready, yeah. Also, in case there's any confusion: @gsagula is working with me on code cleanup. :) |
@louiscryan @wora @mandarjog @kyessenov @spikecurtis @rshriram @gsagula @kflynn @htuch @dnoe @saumoh et al: My general feeling on this PR and functionality in general is that we should merge into the existing extauth HTTP filter that @saumoh has been working on. This would involve:
I think this is the best way forward as I really don't want to end up with 2 auth filters and I see no real reason we can't make everyone happy with the right configuration and API. So along those lines my recommendation is to:
Anyhow feel free to leave this open if it helps but that's my suggestion on how to proceed FWIW. Thank you very much for working on this @kflynn @gsagula I think this is going to be great for the community. |
@mattklein123, thanks for chiming in! Let's leave this open while I get the draft of the design doc done? There are a couple of points I'd like to make sure are clear before we close this one. For the record, we're definitely not opposed to merging filters. :) We do want to make sure that the folks already using Ambassador's extauth have a smooth path forward, though. Thanks! |
@kflynn I'll let you guys decide the design. Meanwhile, If you would like to share the doc with me, it helps in case I need to jump in the code. Thanks! |
I'm with @mattklein123 and don't see a reason we can't make everyone happy with the right design. What I care about with the separation between authN and authZ is primarily about being able to compose them. @kflynn happy to help where I can with the design. Ping me if you want to have any design discussions or get in front of a virtual whiteboard. |
Same here and for @mjog
…On Fri, Feb 23, 2018 at 1:49 PM, Spike Curtis ***@***.***> wrote:
I'm with @mattklein123 <https://github.com/mattklein123> and don't see a
reason we can't make everyone happy with the right design.
What I care about with the separation between authN and authZ is primarily
about being able to compose them.
@kflynn <https://github.com/kflynn> happy to help where I can with the
design. Ping me if you want to have any design discussions or get in front
of a virtual whiteboard.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2621 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AIoKPOnDJn-BaXsHE6ALoF5GmO1myN7vks5tXzKHgaJpZM4SHFyv>
.
|
OK, at long last, design doc is here: https://docs.google.com/document/d/1L3aRk9yaT6Lsb4epI6rdjCdStOuwTfhoeSP82zunkGg/edit?usp=sharing @htuch @dnoe @mattklein123 @louiscryan @spikecurtis @mandarjog @saumoh and anyone else interested... :) |
(Looks like the sharing settings weren't correct earlier -- fixed now, I think.) |
@kflynn Thanks! I've made a couple comments inline on the doc where differences in design goals jumped out at me. It feels like the next step is to propose something specific for how the existing |
@spikecurtis How about send me email at flynn@datawire.io and we'll see about getting something scheduled? Thanks! |
I think it makes sense to have a broader discussion. Flynn if you're
willing to coordinate I would appreciate it.
…On Mon, Mar 5, 2018 at 2:37 PM Spike Curtis ***@***.***> wrote:
@kflynn <https://github.com/kflynn> Thanks!
I've made a couple comments inline on the doc where differences in design
goals jumped out at me.
It feels like the next step is to propose something specific for how the
existing ext_authz API might be extended for these use cases (or,
conclude that it makes more sense to have an entirely separate API). Again,
happy to jump on a call and spitball some ideas.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2621 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AIoKPPy4wV1gVXgK5QPATJKNyGi5dl6mks5tbb4pgaJpZM4SHFyv>
.
|
It would be good to provide a synopsis of how this conversation goes at the next Envoy community call (next Tue). Alternatively, we can have the discussion there, but I have a feeling this is > 20 minutes in scope. |
I'll coordinate, yeah. @htuch -- definitely agreed! Even if we haven't been able to have the call by Tuesday ( ;) ) I can talk about the situation and where we are. |
In other news: we have the design doc open, and it's pretty clear that we have both a desire to work out a way to meet everyone's goals with one mechanism, and an appreciation that we have some work to do to get there. So I'm going to close this PR tomorrow. Further discussion can center around the doc for the moment, and the meeting to come. If anyone thinks that this'll make it harder for them to be heard, or for us to get to a good outcome, sing out! |
@kflynn you mentioned closing this PR in your last comment, so I will go ahead and close. If please comment if you would like to re open. |
* wasm file gen * build and push * clean up * add check option * update prow script mode * update prow script * update * add a make target * fix * update wasm gen script * generate stats plugin wasm * clean up * fix log
Description:
This is the external authentication filter that's been powering Ambassador's authentication mechanism for months now. It's used by many - perhaps most - Ambassador installations, including production use at Datawire and elsewhere.
So clearly this PR is a bit overdue, for which I apologize. [ ;) ] That's doubly true in light of Tigera's recent work: Ambassador's auth mechanism is very different from the mechanism that
external_authz.proto
embodies, and it seems like a discussion is warranted.Distinctions Between Methods
The big distinctions between Ambassador's auth and the
external_authz.proto
mechanism are:However, we have had requests for gRPC as well as REST; supporting both would probably be a good thing.
"The response" includes HTTP status code, headers, body, the works. This has turned out to be very important for our users, as it provides a lot of flexibility and control.
For example, at Datawire, Ambassador fronts a dashboard service, which is to be accessed by a web browser, and we currently use Auth0 for the heavy lifting of authentication. Giving the auth service control over the response to the client allows us to respond to an unauthenticated request with a 301 to redirect the browser to Auth0's login widget, which makes for a much cleaner user experience... but we can just hand a 401 to a program that would be confused by the redirect.
Which headers are copied can be configured; only headers specifically permitted are ever copied. This has also turned out to offer a lot of flexibility: for example, the auth service can insert a header indicating what level of access a the authenticated user has, and that header can trigger a routing decision. Or, as we do at Datawire, the extauth service can inject a token into the request after a successful first-time authentication.
Protocol Details
The protocol between the extauth filter and the extauth service is very simple:
Any request that arrives at the filter is sent on to the auth service.
method
,URL
, and headers other thanContent-Length
are forwarded unchanged.Content-Length
header is given a value of0
.If the auth service responds with an HTTP status of
200
, the request is allowed to continue.allowed_headers
listed in the extauth filter config are copied from the auth service's response into the request before it continues upstream.200
will approve the request; any other2yz
response is not an approval.If the auth service responds with an HTTP status other than
200
(including other2yz
responses), the request is not allowed to continue.It's worth noting, given the above, that:
method
that the client will use.200
for requests that don't require authentication, butFuture Work
Under consideration but not part of this PR:
Risk Level: Medium
Testing:
This is WIP because we need to add coverage testing! CI tests should all be passing, though.
Docs Changes: needed but not written until we sort out the path forward [ :) ]
Release Notes: N/A