-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Support for external authorization http filter. #2417
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
Merged
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
7aee214
Support for external authroization http filter.
56c3431
Merge with gRPC singleton manager.
037b73c
Format fixes for http files.
fe6228f
Fix test http filter format.
d9cf7fd
Merge with data-plane-api for http.
bc2a433
Review feedback for the http files.
51bd7a9
Remove mock for proto generation: http changes.
79c6468
Review comments http.
1b34c85
Merge http changes with data-plane-api reorg #2495.
00994a9
Fix timeout and http branch build.
2fc04d5
Remove not required move.
144b63c
Merge and add test for http filter configuration.
1f27e36
Hold onto the check request sent via gRPC.
dcc17e1
Add test for assert on empty proto.
6845de2
Address review comments.
a1ed9e7
Revert "Add test for assert on empty proto."
d043b85
Add comments to the tests.
873728b
Address review feedback.
889ad54
Address review comments.
8201311
Add failure mode behavior when no route or cluster for the request.
2eb3637
Revert "Add failure mode behavior when no route or cluster for the re…
d9cb836
Fix ordering post rebase.
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| #include "common/http/filter/ext_authz.h" | ||
|
|
||
| #include <string> | ||
| #include <vector> | ||
|
|
||
| #include "envoy/http/codes.h" | ||
|
|
||
| #include "common/common/assert.h" | ||
| #include "common/common/enum_to_int.h" | ||
| #include "common/ext_authz/ext_authz_impl.h" | ||
| #include "common/http/codes.h" | ||
| #include "common/router/config_impl.h" | ||
|
|
||
| #include "fmt/format.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Http { | ||
| namespace ExtAuthz { | ||
|
|
||
| namespace { | ||
|
|
||
| const Http::HeaderMap* getDeniedHeader() { | ||
| static const Http::HeaderMap* header_map = new Http::HeaderMapImpl{ | ||
| {Http::Headers::get().Status, std::to_string(enumToInt(Code::Forbidden))}}; | ||
| return header_map; | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| void Filter::initiateCall(const HeaderMap& headers) { | ||
| Router::RouteConstSharedPtr route = callbacks_->route(); | ||
| if (route == nullptr || route->routeEntry() == nullptr) { | ||
| return; | ||
| } | ||
|
|
||
| const Router::RouteEntry* route_entry = route->routeEntry(); | ||
| Upstream::ThreadLocalCluster* cluster = config_->cm().get(route_entry->clusterName()); | ||
| if (cluster == nullptr) { | ||
| return; | ||
| } | ||
| cluster_ = cluster->info(); | ||
|
|
||
| Envoy::ExtAuthz::CheckRequestUtils::createHttpCheck(callbacks_, headers, check_request_); | ||
|
|
||
| state_ = State::Calling; | ||
| initiating_call_ = true; | ||
| client_->check(*this, check_request_, callbacks_->activeSpan()); | ||
| initiating_call_ = false; | ||
| } | ||
|
|
||
| FilterHeadersStatus Filter::decodeHeaders(HeaderMap& headers, bool) { | ||
| initiateCall(headers); | ||
| return state_ == State::Calling ? FilterHeadersStatus::StopIteration | ||
| : FilterHeadersStatus::Continue; | ||
| } | ||
|
|
||
| FilterDataStatus Filter::decodeData(Buffer::Instance&, bool) { | ||
| return state_ == State::Calling ? FilterDataStatus::StopIterationAndWatermark | ||
| : FilterDataStatus::Continue; | ||
| } | ||
|
|
||
| FilterTrailersStatus Filter::decodeTrailers(HeaderMap&) { | ||
| return state_ == State::Calling ? FilterTrailersStatus::StopIteration | ||
| : FilterTrailersStatus::Continue; | ||
| } | ||
|
|
||
| void Filter::setDecoderFilterCallbacks(StreamDecoderFilterCallbacks& callbacks) { | ||
| callbacks_ = &callbacks; | ||
| } | ||
|
|
||
| void Filter::onDestroy() { | ||
| if (state_ == State::Calling) { | ||
| state_ = State::Complete; | ||
| client_->cancel(); | ||
| } | ||
| } | ||
|
|
||
| void Filter::onComplete(Envoy::ExtAuthz::CheckStatus status) { | ||
| ASSERT(cluster_); | ||
|
|
||
| state_ = State::Complete; | ||
|
|
||
| using Envoy::ExtAuthz::CheckStatus; | ||
|
|
||
| switch (status) { | ||
| case CheckStatus::OK: | ||
| cluster_->statsScope().counter("ext_authz.ok").inc(); | ||
| break; | ||
| case CheckStatus::Error: | ||
| cluster_->statsScope().counter("ext_authz.error").inc(); | ||
| break; | ||
| case CheckStatus::Denied: | ||
| cluster_->statsScope().counter("ext_authz.denied").inc(); | ||
| Http::CodeUtility::ResponseStatInfo info{config_->scope(), | ||
| cluster_->statsScope(), | ||
| EMPTY_STRING, | ||
| enumToInt(Code::Forbidden), | ||
| true, | ||
| EMPTY_STRING, | ||
| EMPTY_STRING, | ||
| EMPTY_STRING, | ||
| EMPTY_STRING, | ||
| false}; | ||
| Http::CodeUtility::chargeResponseStat(info); | ||
| break; | ||
| } | ||
|
|
||
| // We fail open/fail close based of filter config | ||
| // if there is an error contacting the service. | ||
| if (status == CheckStatus::Denied || | ||
| (status == CheckStatus::Error && !config_->failureModeAllow())) { | ||
| Http::HeaderMapPtr response_headers{new HeaderMapImpl(*getDeniedHeader())}; | ||
| callbacks_->encodeHeaders(std::move(response_headers), true); | ||
| callbacks_->requestInfo().setResponseFlag(Envoy::RequestInfo::ResponseFlag::Unauthorized); | ||
| } else { | ||
| // We can get completion inline, so only call continue if that isn't happening. | ||
| if (!initiating_call_) { | ||
| callbacks_->continueDecoding(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| } // namespace ExtAuthz | ||
| } // namespace Http | ||
| } // namespace Envoy | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| #pragma once | ||
|
|
||
| #include <cstdint> | ||
| #include <memory> | ||
| #include <string> | ||
| #include <vector> | ||
|
|
||
| #include "envoy/config/filter/http/ext_authz/v2/ext_authz.pb.h" | ||
| #include "envoy/ext_authz/ext_authz.h" | ||
| #include "envoy/http/filter.h" | ||
| #include "envoy/local_info/local_info.h" | ||
| #include "envoy/runtime/runtime.h" | ||
| #include "envoy/upstream/cluster_manager.h" | ||
|
|
||
| #include "common/common/assert.h" | ||
| #include "common/ext_authz/ext_authz_impl.h" | ||
| #include "common/http/header_map_impl.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Http { | ||
| namespace ExtAuthz { | ||
|
|
||
| /** | ||
| * Type of requests the filter should apply to. | ||
| */ | ||
| enum class FilterRequestType { Internal, External, Both }; | ||
|
|
||
| /** | ||
| * Global configuration for the HTTP authorization (ext_authz) filter. | ||
| */ | ||
| class FilterConfig { | ||
| public: | ||
| FilterConfig(const envoy::config::filter::http::ext_authz::v2::ExtAuthz& config, | ||
| const LocalInfo::LocalInfo& local_info, Stats::Scope& scope, | ||
| Runtime::Loader& runtime, Upstream::ClusterManager& cm) | ||
| : local_info_(local_info), scope_(scope), runtime_(runtime), cm_(cm), | ||
| cluster_name_(config.grpc_service().envoy_grpc().cluster_name()), | ||
| failure_mode_allow_(config.failure_mode_allow()) {} | ||
|
|
||
| const LocalInfo::LocalInfo& localInfo() const { return local_info_; } | ||
| Runtime::Loader& runtime() { return runtime_; } | ||
| Stats::Scope& scope() { return scope_; } | ||
| std::string cluster() { return cluster_name_; } | ||
| Upstream::ClusterManager& cm() { return cm_; } | ||
| bool failureModeAllow() const { return failure_mode_allow_; } | ||
|
|
||
| private: | ||
| const LocalInfo::LocalInfo& local_info_; | ||
| Stats::Scope& scope_; | ||
| Runtime::Loader& runtime_; | ||
| Upstream::ClusterManager& cm_; | ||
| std::string cluster_name_; | ||
| bool failure_mode_allow_; | ||
| }; | ||
|
|
||
| typedef std::shared_ptr<FilterConfig> FilterConfigSharedPtr; | ||
|
|
||
| /** | ||
| * HTTP ext_authz filter. Depending on the route configuration, this filter calls the global | ||
| * ext_authz service before allowing further filter iteration. | ||
| */ | ||
| class Filter : public StreamDecoderFilter, public Envoy::ExtAuthz::RequestCallbacks { | ||
| public: | ||
| Filter(FilterConfigSharedPtr config, Envoy::ExtAuthz::ClientPtr&& client) | ||
| : config_(config), client_(std::move(client)) {} | ||
|
|
||
| // Http::StreamFilterBase | ||
| void onDestroy() override; | ||
|
|
||
| // Http::StreamDecoderFilter | ||
| FilterHeadersStatus decodeHeaders(HeaderMap& headers, bool end_stream) override; | ||
| FilterDataStatus decodeData(Buffer::Instance& data, bool end_stream) override; | ||
| FilterTrailersStatus decodeTrailers(HeaderMap& trailers) override; | ||
| void setDecoderFilterCallbacks(StreamDecoderFilterCallbacks& callbacks) override; | ||
|
|
||
| // ExtAuthz::RequestCallbacks | ||
| void onComplete(Envoy::ExtAuthz::CheckStatus status) override; | ||
|
|
||
| private: | ||
| enum class State { NotStarted, Calling, Complete }; | ||
| void initiateCall(const HeaderMap& headers); | ||
|
|
||
| FilterConfigSharedPtr config_; | ||
| Envoy::ExtAuthz::ClientPtr client_; | ||
| StreamDecoderFilterCallbacks* callbacks_{}; | ||
| State state_{State::NotStarted}; | ||
| Upstream::ClusterInfoConstSharedPtr cluster_; | ||
| bool initiating_call_{}; | ||
| envoy::service::auth::v2::CheckRequest check_request_{}; | ||
| }; | ||
|
|
||
| } // namespace ExtAuthz | ||
| } // namespace Http | ||
| } // namespace Envoy |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| #include "server/config/http/ext_authz.h" | ||
|
|
||
| #include <chrono> | ||
| #include <string> | ||
|
|
||
| #include "envoy/config/filter/http/ext_authz/v2/ext_authz.pb.validate.h" | ||
| #include "envoy/registry/registry.h" | ||
|
|
||
| #include "common/ext_authz/ext_authz_impl.h" | ||
| #include "common/http/filter/ext_authz.h" | ||
| #include "common/protobuf/utility.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Server { | ||
| namespace Configuration { | ||
|
|
||
| HttpFilterFactoryCb ExtAuthzFilterConfig::createFilter( | ||
| const envoy::config::filter::http::ext_authz::v2::ExtAuthz& proto_config, const std::string&, | ||
| FactoryContext& context) { | ||
| auto filter_config = std::make_shared<Http::ExtAuthz::FilterConfig>( | ||
| proto_config, context.localInfo(), context.scope(), context.runtime(), | ||
| context.clusterManager()); | ||
| const uint32_t timeout_ms = PROTOBUF_GET_MS_OR_DEFAULT(proto_config.grpc_service(), timeout, 200); | ||
|
|
||
| return [ grpc_service = proto_config.grpc_service(), &context, filter_config, | ||
| timeout_ms ](Http::FilterChainFactoryCallbacks & callbacks) { | ||
| auto async_client_factory = | ||
| context.clusterManager().grpcAsyncClientManager().factoryForGrpcService(grpc_service, | ||
| context.scope()); | ||
| auto client = std::make_unique<Envoy::ExtAuthz::GrpcClientImpl>( | ||
| async_client_factory->create(), std::chrono::milliseconds(timeout_ms)); | ||
| callbacks.addStreamDecoderFilter(Http::StreamDecoderFilterSharedPtr{ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: make_shared |
||
| std::make_shared<Http::ExtAuthz::Filter>(filter_config, std::move(client))}); | ||
| }; | ||
| } | ||
|
|
||
| HttpFilterFactoryCb ExtAuthzFilterConfig::createFilterFactory(const Json::Object&, | ||
| const std::string&, FactoryContext&) { | ||
| NOT_IMPLEMENTED; | ||
| } | ||
|
|
||
| HttpFilterFactoryCb | ||
| ExtAuthzFilterConfig::createFilterFactoryFromProto(const Protobuf::Message& proto_config, | ||
| const std::string& stats_prefix, | ||
| FactoryContext& context) { | ||
| return createFilter( | ||
| MessageUtil::downcastAndValidate<const envoy::config::filter::http::ext_authz::v2::ExtAuthz&>( | ||
| proto_config), | ||
| stats_prefix, context); | ||
| } | ||
|
|
||
| /** | ||
| * Static registration for the external authorization filter. @see RegisterFactory. | ||
| */ | ||
| static Registry::RegisterFactory<ExtAuthzFilterConfig, NamedHttpFilterConfigFactory> register_; | ||
|
|
||
| } // namespace Configuration | ||
| } // namespace Server | ||
| } // namespace Envoy | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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'm wondering if we want to charge to cluster or route? Why charge to cluster here but globally in the TCP ext_authz?
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.
@htuch To be honest I am not sure i fully understand the implications/scope of this choice. I was following the stats model from the
ratelimitfilter (but u know that already).From what I understood of the flow i thought that since the cluster was the routed destination hence we were accounting towards it.