Support for external authorization grpc service.#2415
Support for external authorization grpc service.#2415htuch merged 22 commits intoenvoyproxy:masterfrom colabsaumoh:ext-auth-review-common
Conversation
Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
…ommon Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
|
@qiwzhang can you follow along here so that we are confident about
convergence with https://github.com/istio/proxy/tree/master/src/envoy/mixer
…On Tue, Jan 23, 2018 at 9:02 AM, htuch ***@***.***> wrote:
Thanks @saumoh <https://github.com/saumoh> for the PR series. Now that
#2393 <#2393> is merged, can you
rebase this and use the client factory for gRPC services provided there?
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#2415 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AIoKPD6enrLseQ8Evbk1u2Ol5NuPRxp3ks5tNhCcgaJpZM4RkxoI>
.
|
|
@htuch I've merged with the new gRPC manager changes. |
|
@saumoh can you get the build to pass? I'll take an in-depth pass tomorrow, but I think it's helpful to see the CI build passing. Thanks. |
| headers.iterate(fillHttpHeaders, mhdrs); | ||
|
|
||
| std::unique_ptr<AttributeContext_Request> req = std::unique_ptr<AttributeContext_Request>{new AttributeContext_Request()}; | ||
| ASSERT(req); |
There was a problem hiding this comment.
Very brief drive-by nits here:
- Don't need to
ASSERTon a basic memory allocation, we assume memory allocations always succeed or the world ends in Envoy. - Should write this as
auto req = std::make_unique<Attribute_Request>();for C++14.
| class ExtAuthzCheckRequestGenerator: public CheckRequestGenerator { | ||
| public: | ||
| ExtAuthzCheckRequestGenerator() {} | ||
| ~ExtAuthzCheckRequestGenerator() {} |
There was a problem hiding this comment.
Quick nit: the default constructor/destructors are provided by the compiler, no need to add them.
Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
|
@htuch Fixed the build and addressed your last set of comments. If you could please have a look. I am working on fixing coverage in the mean while. thanks. |
|
@saumoh there is coverage regression here, see |
|
@htuch I am adding more tests for some of the newly added code. That should help creep the numbers closer to 100%. |
htuch
left a comment
There was a problem hiding this comment.
OK great, this is looking like it's heading in the right direction, thanks for taking this on!
bazel/repositories.bzl
Outdated
| auth_bind_targets = [ | ||
| "auth" | ||
| ] | ||
| for t in auth_bind_targets: |
There was a problem hiding this comment.
FWIW, the recent merges of envoyproxy/data-plane-api#421 and #2430 will break this (and other parts of your PR). Be prepared to update when you merge master or pull in an updated data-plane-api SHA.
There was a problem hiding this comment.
Thanks for the heads up. I'll do another merge with master to pull in the latest data-plane-api and rework the impacted code.
include/envoy/ext_authz/ext_authz.h
Outdated
| /** | ||
| * Called when a check request is complete. The resulting status is supplied. | ||
| */ | ||
| virtual void complete(CheckStatus status) PURE; |
There was a problem hiding this comment.
Nit: I think onComplete is a clearer name.
include/envoy/ext_authz/ext_authz.h
Outdated
include/envoy/ext_authz/ext_authz.h
Outdated
| * and fill out the details in the authorization protobuf that is sent to authorization | ||
| * service. | ||
| */ | ||
| class CheckRequestGenerator { |
There was a problem hiding this comment.
This is really a helper utility class. Generally we don't put these in include/envoy. A litmus test is "will I need to mock this class?" and "will this class have potentially more than one implementation?". If no to both, put it in the source/common subtree.
There was a problem hiding this comment.
Initially I had it in the souce/common subtree. When I started writing the UT's for the filters I had to mock it. That's when I moved it here.
The usage in http filter is here
I think that the mock is needed. @htuch Should I just keep it here or move to source/common and have the mock reference it from there? please confirm. thx.
There was a problem hiding this comment.
Do you really need to mock the proto creation? These are basically straight line pure functions. Personally, I would mock the input parameters, since it's just as easy to do and results in easier to understand code.
There was a problem hiding this comment.
i think i know what you mean here. Let me try to redo the ut's without using the mock.
There was a problem hiding this comment.
@htuch last commit removes the mock for proto creation.
|
|
||
| // TODO(saumoh): Uncomment once unauthorization has been added to accesslog.proto | ||
| // | ||
| // if (request_info.getResponseFlag(RequestInfo::ResponseFlag::Unauthorized)) { |
There was a problem hiding this comment.
I would remove all of this for now and keep it locally. It will bit rot otherwise.
| async_client_factory_ = async_client_manager.factoryForGrpcService(grpc_service, scope); | ||
| } | ||
|
|
||
| ClientPtr GrpcFactoryImpl::create(const Optional<std::chrono::milliseconds>& timeout) { |
There was a problem hiding this comment.
I don't think you need the GrpcFactoryImpl if all you're doing is wrapping the Grpc::AsyncClientManager/Factory calls, just use those classes directly.
| std::unique_ptr<::envoy::api::v2::Address> ExtAuthzCheckRequestGenerator::getProtobufAddress( | ||
| const Network::Address::InstanceConstSharedPtr& instance) { | ||
|
|
||
| auto addr = std::make_unique<::envoy::api::v2::Address>(); |
There was a problem hiding this comment.
I think it's more natural in protobuf to invert this construction. What you have is:
- getConnectionPeer calls getProtobufAddress to get some remote address PB.
- getConnectionPeer then does a set_allocated_address with the result.
A much more common pattern is.
- getConnectionPeer calls
mutable_allocated_addresss()to allocate memory and obtain a pointer. - getProtobufAddress takes this pointer and then writes directly into the memory.
Of course, you would probably rename the methods. See https://github.com/envoyproxy/envoy/blob/master/source/common/config/address_json.cc#L10 and its callers for an example.
What you have is correct, I just find it noisier, since we have to deal with smart pointer creation and release, rather than idiomatically having the protobuf library mutable_foo() allocators do their work.
| req->set_allocated_http(httpreq); | ||
|
|
||
| return req; | ||
| } |
There was a problem hiding this comment.
I think it would be instructive to compare with https://github.com/envoyproxy/envoy/blob/master/source/common/access_log/grpc_access_log_impl.cc#L251. There is a lot of overlap between the access log proto data and construction and the auth request.
As a non-actionable reflection, it would have been great if we could have converged the common objects for both access logs and auth, but I don't think this is going to happen (@mattklein123 @wora).
There was a problem hiding this comment.
@htuch This really depends on how big the scale we are targeting. If we target millions of services, billions of requests per second, we can surely refactor the code, but it will take quite a bit work.
The general approach would be something like this:
- Reach an agreement on the standard Envoy attributes, and define them as a proto like AttributeContext.
- Envoy natively generate the standard attributes.
- Design the standard logs, metrics, events, traces , and other telemetry schemas.
- Generate standard telemetry data based on the standard attributes.
- Optional. Generate legacy telemetry data, such as access logs, from the standard attributes.
New users of Envoy won't need the legacy telemetry data, so it would just be disabled by a flag. We can delete the legacy code after a year, or just keep them on the side.
This refactoring would only be useful if we can agree on a rich set of standard telemetry data that appeal to wide range of users, so a customer can correlate telemetry data across services written by different vendors.
There was a problem hiding this comment.
@htuch Ref to the first paragraph, are you suggesting that I write this in a way that it's done in https://github.com/envoyproxy/envoy/blob/master/source/common/access_log/grpc_access_log_impl.cc#L251.
I am happy to do that but just want to confirm.
Separately, thanks for all the other review comments. I am working on making those changes.
| ClientPtr create(const Optional<std::chrono::milliseconds>&) override { | ||
| return ClientPtr{new NullClientImpl()}; | ||
| } | ||
| }; |
There was a problem hiding this comment.
I'm guessing you modeled this on the RateLimiterImpl, do you really need a null implementation?
Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
…ommon Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
|
@htuch Thanks for working with me on this PR. I've updated with the last set of pr comments.
|
Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
|
I think if you can do a few small PRs for extra utilities for header strings and network address proto that would be good to do now; the more general separation of attribute context stuff I'll leave for you and @wora to discuss. |
wora
left a comment
There was a problem hiding this comment.
Separation of attribute context is done, you can sync up to it. I think it is easier to do the split before it is being used.
| } | ||
|
|
||
| // Fill in the headers | ||
| auto mhdrs = httpreq.mutable_headers(); |
There was a problem hiding this comment.
We need a way to control which headers to send.
| } | ||
| } | ||
| } | ||
| peer.set_principal(principal); |
There was a problem hiding this comment.
We probably need some format normalization here. At least document what is the format used here?
| peer.set_principal(principal); | ||
|
|
||
| if (!service.empty()) { | ||
| peer.set_service(service); |
|
I'm not sure if we need to refactor it today; it's done at the API level already, we don't have any extra consumers yet. @wora if you feel strongly about pulling the AttributeContext code out of the We know it's not going to be used for logging, so I'm guessing it's for other auth filters? |
Title Add a few utility functions to the http/protocol and http_header_map.h files Another utility function to copy Address::Instance into it's protobuf representation Testing I have tested and used these new functions in the PR #2415 added a ut for the http changes. Risk Low: like really low. Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
|
@htuch Will merge #2495. thanks for the heads up. |
|
@saumoh Yeah, you can do that as a followup PR (I did something similar with the Google gRPC client library PRs, it makes it easier to separate out reasoning about thread stuff from the semantics of the changes). |
…ommon Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
| /** | ||
| * Cancel an inflight Check request. | ||
| */ | ||
| virtual void cancel() PURE; |
There was a problem hiding this comment.
Thinking about this a bit more, maybe we don't need to do the TLS thing at all here. This is unary RPC, so we don't have a significant cost in rebuilding the client each time, it should be a fairly small construction/destruction cost. I think what you have in this PR is fine, I would be more concerned if we had a bidi streaming auth check.
|
|
||
| // Set the address | ||
| auto addr = peer.mutable_address(); | ||
| if (!local) { |
There was a problem hiding this comment.
Nit: can you invert this such that it reads if (local) {, same below, it's a bit cleaner.
| Envoy::Http::StreamDecoderFilterCallbacks* cb = | ||
| const_cast<Envoy::Http::StreamDecoderFilterCallbacks*>(callbacks); | ||
|
|
||
| std::string service = getHeaderStr(headers.EnvoyDownstreamServiceCluster()); |
| typedef ConstSingleton<ConstantValues> Constants; | ||
|
|
||
| // TODO(htuch): We should have only one client per thread, but today we create one per filter stack. | ||
| // This will require support for more than one outstanding request per client. |
There was a problem hiding this comment.
As mentioned above, I think it's different for authz, since you are unary RPC, so you can probably put this aside for now.
| * createHttpCheck is used to extract the attributes from the stream and the http headers | ||
| * and fill them up in the CheckRequest proto message. | ||
| * @param callbacks supplies the Http stream context from which data can be extracted. | ||
| * @param headers supplies the heeader map with http headers that will be used to create the |
| client_.onSuccess(std::move(response), span_); | ||
| } | ||
|
|
||
| { |
There was a problem hiding this comment.
Can you split this into multiple tests? The scoped-grouping-of-a-bunch-of-related-tests is fine for small simple tests, but I think here it would be clearer to have them as distinct tests.
|
|
||
| client_.onCreateInitialMetadata(headers); | ||
|
|
||
| response.reset(new envoy::service::auth::v2::CheckResponse()); |
There was a problem hiding this comment.
Nit: prefer std::make_unique, here and elsewhere there is a reset(new ...) pattern.
| response.reset(new envoy::service::auth::v2::CheckResponse()); | ||
| ::google::rpc::Status* status = new ::google::rpc::Status(); | ||
| status->set_code(Grpc::Status::GrpcStatus::Ok); | ||
| response->set_allocated_status(status); |
There was a problem hiding this comment.
Prefer the mutable_foo pattern to raw allocations and set. Sorry if I'm being a pain here, it's just a reflection of the Envoy style where we prefer to always have smart pointers to simplify reasoning about correctness. This can be relaxed in tests when really needed, but I don't think this is one of those places.
There was a problem hiding this comment.
You are fine. It is more a reflection of my old-school C heritage/archaic behavior. The mutable_foo pattern and the smart pointers are much better.
| CreateCheckRequest::createTcpCheck(&net_callbacks_, request); | ||
| } | ||
|
|
||
| TEST_F(CreateCheckRequestTest, BasicHttp) { |
There was a problem hiding this comment.
https://23972-65214191-gh.circle-artifacts.com/0/build/envoy/generated/coverage/coverage.source_common_ext_authz_ext_authz_impl.cc.html tells me you have full coverage here, but I'm a bit surprised looking at just these tests, since they don't seem to be driving all the paths (at least directly). Could you say a few words on why we get full coverage here in this thread?
There was a problem hiding this comment.
@htuch
The underlying structures like Ssl::Connection context are mocks hence they are there but empty. The calls from it here thus invoks all the conditional code.
The other connditional is around local boolean which invokes with both true and false within the createTcpCheck and createHttpCheck it self.
The remaining conditional checks like protocol and service is invoked because those structures are getting initialized in the tests.
I've addressed the rest of the feedback as well. Thanks for the review...it has made the code much better.
| } | ||
| } | ||
| } | ||
| peer.set_principal(principal); |
There was a problem hiding this comment.
Nit: you're actually doing an additional string copy here that could be skipped if you did the set_principal directly in the if cases.
Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
| /** | ||
| * Cancel an inflight Check request. | ||
| */ | ||
| virtual void cancel() PURE; |
There was a problem hiding this comment.
Yeah, for Envoy gRPC, this will come for free from the cluster pool management. In theory, Google gRPC also does this, based on good authority, but I haven't validated. Let's work on your other PRs first and we can circle back to this later.
|
@saumoh Thanks for patience during the review process, this is a nice contribution, can you rebase your other changes and get them passing tests, I'll take them one at a time? Ta! |
| const RequestInfo::RequestInfo& request_info) { | ||
|
|
||
| static_assert(RequestInfo::ResponseFlag::LastFlag == 0x800, | ||
| static_assert(RequestInfo::ResponseFlag::LastFlag == 0x1000, |
There was a problem hiding this comment.
Seems like gRPC access logging protos should be enhanced for unauthorized? Can you add a TODO and take care of this in one of your follow ups?
There was a problem hiding this comment.
yes. I'll do it in the follow up pr; thanks.
In the proxy we've added an Unauthorized response flag. This PR adds the same to filter access logs. Once this PR is merged it will be possible to set the flag in source/common/access_log/grpc_access_log_impl.cc See also, comment in envoyproxy/envoy#2415 Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
And properly clean up reachability ref. Signed-off-by: JP Simard <jp@jpsim.com>
And properly clean up reachability ref. Signed-off-by: JP Simard <jp@jpsim.com>
In the proxy we've added an Unauthorized response flag. This PR adds the same to filter access logs. Once this PR is merged it will be possible to set the flag in source/common/access_log/grpc_access_log_impl.cc See also, comment in envoyproxy/envoy#2415 Signed-off-by: Saurabh Mohan <saurabh+github@tigera.io>
Title
Authorization TCP and HTTP filters using an external gRPC service.
Patch 1 of 3: gRPC client and common framework.
Description
As per the feedback on #2359 I am breaking it up into three PR's.
The patch in this PR adds support for gRPC client that will be used by TCP and HTTP filters to make authorization checks with an external service.
This PR implements the discussing we had in #2291.
Testing
I have tested this on my local setup with an external gRPC authorization server. I have also added UT's for the gRPC client side.
Risk
Low: Because only the users of this filter will be impacted. It should not impact general stability of Envoy it self.
Caveats
Signed-off-by: Saurabh Mohan saurabh+github@tigera.io