Skip to content

authz_filter: extended ext_authz to support v2alpha api#3162

Merged
htuch merged 40 commits intoenvoyproxy:masterfrom
gsagula:authz_grpc_v2alpha
Jun 27, 2018
Merged

authz_filter: extended ext_authz to support v2alpha api#3162
htuch merged 40 commits intoenvoyproxy:masterfrom
gsagula:authz_grpc_v2alpha

Conversation

@gsagula
Copy link
Member

@gsagula gsagula commented Apr 23, 2018

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

Gabriel added 2 commits April 22, 2018 21:36
Signed-off-by: Gabriel <gsagula@gmail.com>
Signed-off-by: Gabriel <gsagula@gmail.com>
// Call status.
CheckStatus status;
// Optional http headers attribute.
KeyValueHeaders headers;
Copy link
Member Author

Choose a reason for hiding this comment

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

I think this could be HeaderMapPtr.

Copy link
Member

Choose a reason for hiding this comment

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

If it makes thing easier or more efficient, sure.

Copy link
Member Author

Choose a reason for hiding this comment

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

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>
@gsagula gsagula changed the title wip_authz_v2alpha: extended ext_authz gRPC implementation to support V2alpha api [WIP] Authz V2alpha: extended ext_authz gRPC implementation to support V2alpha api Apr 23, 2018
Copy link
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

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

Seems a totally reasonable approach, thanks for getting this rolling. @saumoh for further thoughts here.

// 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}];
Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

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;
Copy link
Member

Choose a reason for hiding this comment

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

Nit: prefer to zero initialize this.

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

using Filters::Common::ExtAuthz::CheckStatus;

switch (status) {
const bool update_status_code = response->status_code >= enumToInt(Http::Code::Continue) &&
Copy link
Member

Choose a reason for hiding this comment

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

Not sure I follow the logic here, can you add some comments?

Copy link
Contributor

Choose a reason for hiding this comment

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

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)

Copy link
Member Author

Choose a reason for hiding this comment

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

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;
Copy link
Member

Choose a reason for hiding this comment

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

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) {
Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

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;
Copy link
Contributor

Choose a reason for hiding this comment

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

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?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point too! I will add comments to explain the difference and when to use them.

@spikecurtis
Copy link
Contributor

CC @kflynn is this moving us toward what you wanted to accomplish?

Copy link
Contributor

@saumoh saumoh left a comment

Choose a reason for hiding this comment

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

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();
Copy link
Contributor

Choose a reason for hiding this comment

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

response may not have http_response ?

// Http entity body.
string body = 3;
}
// A field that supplies the attributes for an HTTP response.
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

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) &&
Copy link
Contributor

Choose a reason for hiding this comment

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

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);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

may be wrapping the encoding of response headers and body into a function would help with parsing of the logic here.

Copy link
Member Author

Choose a reason for hiding this comment

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

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_(_));
Copy link
Contributor

Choose a reason for hiding this comment

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

we should be checking the Response's CheckStatus in this file.

Copy link
Member Author

@gsagula gsagula Apr 25, 2018

Choose a reason for hiding this comment

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

I will address the tests after all the design changes.

@gsagula
Copy link
Member Author

gsagula commented Apr 25, 2018

@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.

Gabriel added 4 commits May 7, 2018 00:35
Signed-off-by: Gabriel <gsagula@gmail.com>
Signed-off-by: Gabriel <gsagula@gmail.com>
Signed-off-by: Gabriel <gsagula@gmail.com>
Copy link
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

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

This looks great, a few comments. @saumoh for another pass.

@@ -0,0 +1,12 @@
syntax = "proto3";

package envoy.api.v2.core;
Copy link
Member

Choose a reason for hiding this comment

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

Should this go in envoy.types?

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

// Http attributes for ok responses.
message OkHttpResponse {
// Http entity headers.
map<string, string> headers = 2;
Copy link
Member

Choose a reason for hiding this comment

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

NIt: this should be field number 1. How come you decided to duplicate headers rather than factor it out?

Copy link
Member Author

@gsagula gsagula May 12, 2018

Choose a reason for hiding this comment

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

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) {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: You can probably just drop the second conjunct here.

Copy link
Member Author

Choose a reason for hiding this comment

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

👀

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() &&
Copy link
Member

Choose a reason for hiding this comment

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

Nit: code() > 0

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

} else {
header_map = std::make_unique<Http::HeaderMapImpl>(*getDeniedHeader());
}
for (const auto& header : reponse->headers) {
Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

authz_response->status_code = enumToInt(Http::Code::Forbidden);

if (response->has_denied_response()) {
for (const auto& header : response->denied_response().headers()) {
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Haven't addressed all the tests yet. Will do.

@saumoh
Copy link
Contributor

saumoh commented May 11, 2018

ill do another pass soon.

Copy link
Contributor

@saumoh saumoh left a comment

Choose a reason for hiding this comment

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

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) {
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, I see. Thanks!


// Http attributes for ok responses.
message OkHttpResponse {
// Http entity headers.
Copy link
Contributor

Choose a reason for hiding this comment

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

These headers are in addition to existing headers or in addition to existing headers. A comment would help. Thanks.

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

}
}

Http::HeaderMapPtr Filter::getHeaderMap(const Filters::Common::ExtAuthz::ResponsePtr& reponse) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: s/reponse/response/

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

@gsagula
Copy link
Member Author

gsagula commented May 12, 2018

@htuch @saumoh Thanks for looking at the code. I'm finalizing the HTTP client, and I will address your comments as soon as I finish it.

Gabriel added 2 commits May 16, 2018 17:24
Signed-off-by: Gabriel <gsagula@gmail.com>
Signed-off-by: Gabriel <gsagula@gmail.com>
@gsagula gsagula changed the title [WIP] Authz V2alpha: extended ext_authz gRPC implementation to support V2alpha api Authz V2alpha: extended ext_authz gRPC to support V2alpha api May 21, 2018
@gsagula gsagula changed the title Authz V2alpha: extended ext_authz gRPC to support V2alpha api authz_filter: extended ext_authz to support v2alpha api May 21, 2018
Gabriel added 2 commits May 20, 2018 23:15
Signed-off-by: Gabriel <gsagula@gmail.com>
Signed-off-by: Gabriel <gsagula@gmail.com>
@gsagula
Copy link
Member Author

gsagula commented May 21, 2018

I still need to work on some tests and documentation, but that's it. Grpc and the HTTP clients.

@gsagula
Copy link
Member Author

gsagula commented Jun 22, 2018

Not sure what is causing ASAN errors. It seems not related to my changes. It will require more investigation.

Gabriel added 9 commits June 22, 2018 22:16
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>
@gsagula
Copy link
Member Author

gsagula commented Jun 25, 2018

@htuch
Copy link
Member

htuch commented Jun 26, 2018

@gsagula thanks! Do you think you can manage that last 5%? I will take a look at this PR today.

@gsagula
Copy link
Member Author

gsagula commented Jun 26, 2018

@htuch In order to get the last 5% I will need this to return false: StringUtil::atoul(response->headers().Status()->value().c_str(), status_code)

I couldn't think of an easy way of doing that, but I can try.

htuch
htuch previously approved these changes Jun 26, 2018
Copy link
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

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

@gsagula not sure if I see the issue you describe in the code fragment. This PR LGTM overall and is awesome, I'll leave it open for that final fix if you think it's doable.

// 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.
Copy link
Member

Choose a reason for hiding this comment

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

Nit: s/raw/Raw/

@gsagula
Copy link
Member Author

gsagula commented Jun 26, 2018

@htuch Thanks, I will take a look at this later today.

@htuch
Copy link
Member

htuch commented Jun 27, 2018

@gsagula reach out to me on Slack if you need any assistance with debug.

Signed-off-by: Gabriel <gsagula@gmail.com>
Gabriel added 2 commits June 27, 2018 10:20
Signed-off-by: Gabriel <gsagula@gmail.com>
@gsagula
Copy link
Member Author

gsagula commented Jun 27, 2018

@htuch The sanitizer failures seem to be unrelated to my changes. I will try to merge master later and try it again. Thanks.

@htuch
Copy link
Member

htuch commented Jun 27, 2018

@gsagula can you merge master to pick up #3742?

@gsagula
Copy link
Member Author

gsagula commented Jun 27, 2018

Copy link
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

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

Awesome, thanks @gsagula for the contribution!

@htuch htuch merged commit 5244597 into envoyproxy:master Jun 27, 2018
@gsagula
Copy link
Member Author

gsagula commented Jun 27, 2018

@htuch Thank you for all the reviews and the patience.

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.

5 participants