authz_filter: extended ext_authz to support v2alpha api#3162
authz_filter: extended ext_authz to support v2alpha api#3162htuch merged 40 commits intoenvoyproxy:masterfrom
Conversation
Signed-off-by: Gabriel <gsagula@gmail.com>
Signed-off-by: Gabriel <gsagula@gmail.com>
| // Call status. | ||
| CheckStatus status; | ||
| // Optional http headers attribute. | ||
| KeyValueHeaders headers; |
There was a problem hiding this comment.
I think this could be HeaderMapPtr.
There was a problem hiding this comment.
If it makes thing easier or more efficient, sure.
There was a problem hiding this comment.
I think both if possible to move it later. I'll need to take a closer look at the HeaderMap api though.
Signed-off-by: Gabriel <gsagula@gmail.com>
| // downstream client or, to modify/add request headers being dispatched to the upstream. | ||
| message HttpResponse { | ||
| // Http status code. | ||
| uint32 status_code = 1 [(validate.rules).uint32 = {gte: 100, lt: 600}]; |
There was a problem hiding this comment.
Non-actionable, but if we were to redo the APIs from the ground up, I think I would add a dedicated HttpStatusCode object with the related constraints. We've done this now in a lot of places.
There was a problem hiding this comment.
Sounds good. Should I place it here in a separated file like the http_uri.proto? https://github.com/gsagula/envoy/tree/e6d6df09c84a189a43762a1e6839103e8d02c51e/api/envoy/api/v2/core
| // Optional http body attribute. | ||
| Buffer::InstancePtr body; | ||
| // Extra http status code attribute. | ||
| uint32_t status_code; |
There was a problem hiding this comment.
Nit: prefer to zero initialize this.
| using Filters::Common::ExtAuthz::CheckStatus; | ||
|
|
||
| switch (status) { | ||
| const bool update_status_code = response->status_code >= enumToInt(Http::Code::Continue) && |
There was a problem hiding this comment.
Not sure I follow the logic here, can you add some comments?
There was a problem hiding this comment.
may be save the specific code we do want to use and base rest of the logic off it? (in case u think that will help)
There was a problem hiding this comment.
Agreed! That's super confusing and, I definitely change it. The idea is that when the authz service defines a new status code for "reject/denied", this logic will checks whether the code is valid and not equal to 403. If that evaluates to true, set a custom Status Code header and ResponseStatInfo. I will simplify this.
| // Call status. | ||
| CheckStatus status; | ||
| // Optional http headers attribute. | ||
| KeyValueHeaders headers; |
There was a problem hiding this comment.
If it makes thing easier or more efficient, sure.
| if (response->status == CheckStatus::Denied || | ||
| (response->status == CheckStatus::Error && !config_->failureModeAllow())) { | ||
| Http::HeaderMapPtr response_headers; | ||
| if (update_status_code) { |
There was a problem hiding this comment.
This logic is a bit hard to follow due to the multiple reasons we can get here combined with the above code. Comments and/or restructure would be appreciated.
There was a problem hiding this comment.
See my comment above. I will simplify this.
| string body = 3; | ||
| } | ||
| // A field that supplies the attributes for an HTTP response. | ||
| HttpResponse http_response = 2; |
There was a problem hiding this comment.
The comment here needs to explain whether this affects the response to the downstream client, or upstream server.
It also needs to explain the relationship between this field and the status field. For example, can I set the status to "OK" and then also provide this field? What should Envoy do in that case?
There was a problem hiding this comment.
Good point too! I will add comments to explain the difference and when to use them.
|
CC @kflynn is this moving us toward what you wanted to accomplish? |
saumoh
left a comment
There was a problem hiding this comment.
Thanks for the PR. Some minor comments.
| status = CheckStatus::Denied; | ||
| span.setTag(Constants::get().TraceStatus, Constants::get().TraceUnauthz); | ||
| authz_response->status = CheckStatus::Denied; | ||
| authz_response->status_code = response->http_response().status_code(); |
There was a problem hiding this comment.
response may not have http_response ?
| // Http entity body. | ||
| string body = 3; | ||
| } | ||
| // A field that supplies the attributes for an HTTP response. |
There was a problem hiding this comment.
could u please add a comment about how this is not going to be used for TCP filters since the CheckResponse is used by both filters.
There was a problem hiding this comment.
Good call! I will do that.
| using Filters::Common::ExtAuthz::CheckStatus; | ||
|
|
||
| switch (status) { | ||
| const bool update_status_code = response->status_code >= enumToInt(Http::Code::Continue) && |
There was a problem hiding this comment.
may be save the specific code we do want to use and base rest of the logic off it? (in case u think that will help)
| callbacks_->encodeData(*response->body.get(), true); | ||
| } else { | ||
| callbacks_->encodeHeaders(std::move(response_headers), true); | ||
| } |
There was a problem hiding this comment.
may be wrapping the encoding of response headers and body into a function would help with parsing of the logic here.
There was a problem hiding this comment.
I think so too. I will try to simplify this logic, but first I will make sure that we have tests for all the permutations.
| client_.check(request_callbacks_, request, Tracing::NullSpan::instance()); | ||
|
|
||
| EXPECT_CALL(request_callbacks_, onComplete(CheckStatus::Error)); | ||
| EXPECT_CALL(request_callbacks_, onComplete_(_)); |
There was a problem hiding this comment.
we should be checking the Response's CheckStatus in this file.
There was a problem hiding this comment.
I will address the tests after all the design changes.
|
@saumoh @htuch @spikecurtis Thank you for the reviews. They are very helpful! I will do some work this week, but mostly over the weekend. I let you know as soon as it's ready. |
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>
| @@ -0,0 +1,12 @@ | |||
| syntax = "proto3"; | |||
|
|
|||
| package envoy.api.v2.core; | |||
| // Http attributes for ok responses. | ||
| message OkHttpResponse { | ||
| // Http entity headers. | ||
| map<string, string> headers = 2; |
There was a problem hiding this comment.
NIt: this should be field number 1. How come you decided to duplicate headers rather than factor it out?
There was a problem hiding this comment.
this should be field number 1
👍
How come you decided to duplicate headers rather than factor it out?
Sorry, I'm not sure if I follow your question. I decided to separate the content into two distinct messages and then use them inside of a oneof block in CheckResponse. Both DeniedHttpResponse & OkHttpResponse need headers.
| } | ||
| // Only reassign http status if it's greater than 0 and different than 403. | ||
| if (response->denied_response().status().code() && | ||
| response->denied_response().status().code() != authz_response->status_code) { |
There was a problem hiding this comment.
Nit: You can probably just drop the second conjunct here.
| std::make_pair(Http::LowerCaseString(header.first), header.second)); | ||
| } | ||
| // Only reassign http status if it's greater than 0 and different than 403. | ||
| if (response->denied_response().status().code() && |
| } else { | ||
| header_map = std::make_unique<Http::HeaderMapImpl>(*getDeniedHeader()); | ||
| } | ||
| for (const auto& header : reponse->headers) { |
There was a problem hiding this comment.
I wonder if it make sense to have a utility or method on header map to handle these copies, this pattern comes up a fair bit.
| authz_response->status_code = enumToInt(Http::Code::Forbidden); | ||
|
|
||
| if (response->has_denied_response()) { | ||
| for (const auto& header : response->denied_response().headers()) { |
There was a problem hiding this comment.
There's some missing coverage, check out https://49526-65214191-gh.circle-artifacts.com/0/build/envoy/generated/coverage/coverage.source_extensions_filters_common_ext_authz_ext_authz_impl.cc.html.
There was a problem hiding this comment.
Haven't addressed all the tests yet. Will do.
|
ill do another pass soon. |
saumoh
left a comment
There was a problem hiding this comment.
Thanks for the changes. This is looking good. Just some minor comments.
|
|
||
| // We can get completion inline, so only call continue if that isn't happening. | ||
| if (!initiating_call_) { | ||
| if (response->status == CheckStatus::OK) { |
There was a problem hiding this comment.
The conditional check and addition of headers should be outside the check for initiating_call_.
We want to add those headers whether the callback is invoked on the stack or async.
|
|
||
| // Http attributes for ok responses. | ||
| message OkHttpResponse { | ||
| // Http entity headers. |
There was a problem hiding this comment.
These headers are in addition to existing headers or in addition to existing headers. A comment would help. Thanks.
| } | ||
| } | ||
|
|
||
| Http::HeaderMapPtr Filter::getHeaderMap(const Filters::Common::ExtAuthz::ResponsePtr& reponse) { |
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>
|
I still need to work on some tests and documentation, but that's it. Grpc and the HTTP clients. |
|
Not sure what is causing ASAN errors. It seems not related to my changes. It will require more investigation. |
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>
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>
|
@htuch I think all the previous comments have been addressed. Please take another look whenever you get a chance. Thanks! I've brought all tests up to a 100%, except for this one https://66526-65214191-gh.circle-artifacts.com/0/build/envoy/generated/coverage/coverage.source_extensions_filters_common_ext_authz_ext_authz_http_impl.cc.html which is 95%. |
|
@gsagula thanks! Do you think you can manage that last 5%? I will take a look at this PR today. |
|
@htuch In order to get the last 5% I will need this to return false: I couldn't think of an easy way of doing that, but I can try. |
| // External Authorization filter calls out to an external service over either: | ||
| // | ||
| // 1. gRPC Authorization API defined by :ref:`CheckRequest <envoy_api_msg_service.auth.v2alpha.CheckRequest>`. | ||
| // 2. raw HTTP Authorization server by passing the request headers to the service. |
|
@htuch Thanks, I will take a look at this later today. |
|
@gsagula reach out to me on Slack if you need any assistance with debug. |
Signed-off-by: Gabriel <gsagula@gmail.com>
Signed-off-by: Gabriel <gsagula@gmail.com>
Signed-off-by: Gabriel <gsagula@gmail.com>
|
@htuch The sanitizer failures seem to be unrelated to my changes. I will try to merge master later and try it again. Thanks. |
Signed-off-by: Gabriel <gsagula@gmail.com>
|
@htuch Thank you for all the reviews and the patience. |
Description: This PR extends the current Ext_Authz filter to allow optional HTTP attributes being passed from the Authorization service down to client or, to the upstream services. I would like to get some feedback on the changes to the current gRPC async client and filter before moving to implementation of HTTP part of this extension and tests.
*issue: #2828
Risk Level: Medium
Testing: Manual, unit testing.
Docs Changes: envoyproxy/data-plane-api#563