Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions source/common/filter/ext_authz.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ InstanceStats Config::generateStats(const std::string& name, Stats::Scope& scope
}

void Instance::callCheck() {
CheckRequestUtils::createTcpCheck(filter_callbacks_, checkRequest_);
CheckRequestUtils::createTcpCheck(filter_callbacks_, check_request_);

status_ = Status::Calling;
config_->stats().active_.inc();
config_->stats().total_.inc();

calling_check_ = true;
client_->check(*this, checkRequest_, Tracing::NullSpan::instance());
client_->check(*this, check_request_, Tracing::NullSpan::instance());
calling_check_ = false;
}

Expand Down Expand Up @@ -74,7 +74,8 @@ void Instance::onComplete(CheckStatus status) {
}

// Fail open only if configured to do so and if the check status was a error.
if (status == CheckStatus::Denied || (status == CheckStatus::Error && !config_->failOpen())) {
if (status == CheckStatus::Denied ||
(status == CheckStatus::Error && !config_->failureModeAllow())) {
config_->stats().cx_closed_.inc();
filter_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush);
} else {
Expand Down
4 changes: 2 additions & 2 deletions source/common/filter/ext_authz.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class Config {
failure_mode_allow_(config.failure_mode_allow()) {}

const InstanceStats& stats() { return stats_; }
bool failOpen() const { return failure_mode_allow_; }
bool failureModeAllow() const { return failure_mode_allow_; }
void setFailModeAllow(bool value) { failure_mode_allow_ = value; }

private:
Expand Down Expand Up @@ -99,7 +99,7 @@ class Instance : public Network::ReadFilter,
Network::ReadFilterCallbacks* filter_callbacks_{};
Status status_{Status::NotStarted};
bool calling_check_{};
envoy::service::auth::v2::CheckRequest checkRequest_{};
envoy::service::auth::v2::CheckRequest check_request_{};
};

} // TcpFilter
Expand Down
34 changes: 34 additions & 0 deletions source/common/http/filter/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,37 @@ envoy_cc_library(
"@envoy_api//envoy/config/filter/http/rate_limit/v2:rate_limit_cc",
],
)

envoy_cc_library(
name = "ext_authz_lib",
srcs = ["ext_authz.cc"],
deps = [
":ext_authz_includes",
"//include/envoy/http:codes_interface",
"//source/common/common:assert_lib",
"//source/common/common:empty_string",
"//source/common/common:enum_to_int",
"//source/common/ext_authz:ext_authz_lib",
"//source/common/http:codes_lib",
"//source/common/router:config_lib",
],
)

envoy_cc_library(
name = "ext_authz_includes",
hdrs = ["ext_authz.h"],
deps = [
"//include/envoy/access_log:access_log_interface",
"//include/envoy/ext_authz:ext_authz_interface",
"//include/envoy/http:filter_interface",
"//include/envoy/local_info:local_info_interface",
"//include/envoy/runtime:runtime_interface",
"//include/envoy/upstream:cluster_manager_interface",
"//source/common/common:assert_lib",
"//source/common/http:header_map_lib",
"//source/common/json:config_schemas_lib",
"//source/common/json:json_loader_lib",
"//source/common/json:json_validator_lib",
"@envoy_api//envoy/config/filter/http/ext_authz/v2:ext_authz_cc",
],
)
125 changes: 125 additions & 0 deletions source/common/http/filter/ext_authz.cc
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();

Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Contributor Author

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 ratelimit filter (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.

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
94 changes: 94 additions & 0 deletions source/common/http/filter/ext_authz.h
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
1 change: 1 addition & 0 deletions source/exe/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ envoy_cc_library(
"//source/server/config/access_log:grpc_access_log_lib",
"//source/server/config/http:buffer_lib",
"//source/server/config/http:cors_lib",
"//source/server/config/http:ext_authz_lib",
"//source/server/config/http:fault_lib",
"//source/server/config/http:grpc_http1_bridge_lib",
"//source/server/config/http:grpc_json_transcoder_lib",
Expand Down
14 changes: 14 additions & 0 deletions source/server/config/http/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,17 @@ envoy_cc_library(
"//source/server:configuration_lib",
],
)

envoy_cc_library(
name = "ext_authz_lib",
srcs = ["ext_authz.cc"],
hdrs = ["ext_authz.h"],
deps = [
"//include/envoy/registry",
"//include/envoy/server:filter_config_interface",
"//source/common/config:well_known_names",
"//source/common/http/filter:ext_authz_lib",
"//source/common/protobuf:utility_lib",
"@envoy_api//envoy/config/filter/http/ext_authz/v2:ext_authz_cc",
],
)
59 changes: 59 additions & 0 deletions source/server/config/http/ext_authz.cc
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{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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
Loading