Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
98197b8
Support for external authroization tcp filter.
Jan 19, 2018
6c88bd1
Merge remote-tracking branch 'upstream/master' into ext-auth-review-tcp
Jan 23, 2018
981ae9d
Merge with gRPC singleton manager.
Jan 24, 2018
3952f68
Format fixes for tcp files.
Jan 25, 2018
9da8b85
Fix test tcp filter format.
Jan 25, 2018
4fe6572
Merge remote-tracking branch 'upstream/master' into ext-auth-review-tcp
Jan 26, 2018
9c02776
Merge with data-plane-api for tcp.
Jan 26, 2018
25d37c6
Review feedback for tcp files.
Jan 29, 2018
5dc5495
Fix up network config build.
Jan 30, 2018
38d8799
Merge remote-tracking branch 'upstream/master' into ext-auth-review-tcp
Jan 30, 2018
ce9c1e9
Remove mock for proto generation: tcp changes.
Jan 30, 2018
40746cd
Review comments tcp.
Jan 31, 2018
8823360
Merge remote-tracking branch 'upstream/master' into ext-auth-review-tcp
Feb 2, 2018
36053cd
Merge remote-tracking branch 'upstream/master' into ext-auth-review-tcp
Feb 5, 2018
a675ec2
Merge tcp changes with data-plane-api reorg #2495.
Feb 5, 2018
fa371c3
Merge remote-tracking branch 'upstream/master' into ext-auth-review-tcp
Feb 7, 2018
c8910ad
Fixup default timeout.
Feb 7, 2018
945ac1c
Include network filter config in build.
Feb 7, 2018
5b41b32
Address review comments.
Feb 12, 2018
a649490
Review feedback.
Feb 15, 2018
d1392ff
Fix tests class name.
Feb 15, 2018
8f5db76
Move callCheck to onData.
Feb 16, 2018
b3dc7bf
Add tcp filter server configuration tests.
Feb 17, 2018
4143a3c
Fix formatting issue.
Feb 17, 2018
a02d949
Formatting fix up with clang-5 not 3.8
Feb 17, 2018
4d13223
Remove createFilterFactor implementation.
Feb 20, 2018
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
22 changes: 11 additions & 11 deletions source/common/ext_authz/ext_authz_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ void GrpcClientImpl::onFailure(Grpc::Status::GrpcStatus status, const std::strin
callbacks_ = nullptr;
}

void CreateCheckRequest::setAttrContextPeer(envoy::service::auth::v2::AttributeContext_Peer& peer,
const Network::Connection& connection,
const std::string& service, const bool local) {
void CheckRequestUtils::setAttrContextPeer(envoy::service::auth::v2::AttributeContext_Peer& peer,
const Network::Connection& connection,
const std::string& service, const bool local) {

// Set the address
auto addr = peer.mutable_address();
Expand Down Expand Up @@ -103,14 +103,14 @@ void CreateCheckRequest::setAttrContextPeer(envoy::service::auth::v2::AttributeC
}
}

std::string CreateCheckRequest::getHeaderStr(const Envoy::Http::HeaderEntry* entry) {
std::string CheckRequestUtils::getHeaderStr(const Envoy::Http::HeaderEntry* entry) {
if (entry) {
return entry->value().getString();
}
return "";
}

void CreateCheckRequest::setHttpRequest(
void CheckRequestUtils::setHttpRequest(
::envoy::service::auth::v2::AttributeContext_HttpRequest& httpreq,
const Envoy::Http::StreamDecoderFilterCallbacks* callbacks,
const Envoy::Http::HeaderMap& headers) {
Expand Down Expand Up @@ -152,16 +152,16 @@ void CreateCheckRequest::setHttpRequest(
mutable_headers);
}

void CreateCheckRequest::setAttrContextRequest(
void CheckRequestUtils::setAttrContextRequest(
::envoy::service::auth::v2::AttributeContext_Request& req,
const Envoy::Http::StreamDecoderFilterCallbacks* callbacks,
const Envoy::Http::HeaderMap& headers) {
setHttpRequest(*req.mutable_http(), callbacks, headers);
}

void CreateCheckRequest::createHttpCheck(const Envoy::Http::StreamDecoderFilterCallbacks* callbacks,
const Envoy::Http::HeaderMap& headers,
envoy::service::auth::v2::CheckRequest& request) {
void CheckRequestUtils::createHttpCheck(const Envoy::Http::StreamDecoderFilterCallbacks* callbacks,
const Envoy::Http::HeaderMap& headers,
envoy::service::auth::v2::CheckRequest& request) {

auto attrs = request.mutable_attributes();

Expand All @@ -175,8 +175,8 @@ void CreateCheckRequest::createHttpCheck(const Envoy::Http::StreamDecoderFilterC
setAttrContextRequest(*attrs->mutable_request(), callbacks, headers);
}

void CreateCheckRequest::createTcpCheck(const Network::ReadFilterCallbacks* callbacks,
envoy::service::auth::v2::CheckRequest& request) {
void CheckRequestUtils::createTcpCheck(const Network::ReadFilterCallbacks* callbacks,
envoy::service::auth::v2::CheckRequest& request) {

auto attrs = request.mutable_attributes();

Expand Down
4 changes: 2 additions & 2 deletions source/common/ext_authz/ext_authz_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,13 @@ class GrpcClientImpl : public Client, public ExtAuthzAsyncCallbacks {

/**
* For creating ext_authz.proto (authorization) request.
* CreateCheckRequest is used to extract attributes from the TCP/HTTP request
* CheckRequestUtils is used to extract attributes from the TCP/HTTP request
* and fill out the details in the authorization protobuf that is sent to authorization
* service.
* The specific information in the request is as per the specification in the
* data-plane-api.
*/
class CreateCheckRequest {
class CheckRequestUtils {
public:
/**
* createHttpCheck is used to extract the attributes from the stream and the http headers
Expand Down
18 changes: 18 additions & 0 deletions source/common/filter/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,21 @@ envoy_cc_library(
"@envoy_api//envoy/config/filter/network/tcp_proxy/v2:tcp_proxy_cc",
],
)

envoy_cc_library(
name = "ext_authz_lib",
srcs = ["ext_authz.cc"],
hdrs = ["ext_authz.h"],
deps = [
"//include/envoy/ext_authz:ext_authz_interface",
"//include/envoy/network:connection_interface",
"//include/envoy/network:filter_interface",
"//include/envoy/runtime:runtime_interface",
"//include/envoy/stats:stats_macros",
"//include/envoy/upstream:cluster_manager_interface",
"//source/common/common:assert_lib",
"//source/common/ext_authz:ext_authz_lib",
"//source/common/tracing:http_tracer_lib",
"@envoy_api//envoy/config/filter/network/ext_authz/v2:ext_authz_cc",
],
)
90 changes: 90 additions & 0 deletions source/common/filter/ext_authz.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#include "common/filter/ext_authz.h"

#include <cstdint>
#include <string>

#include "common/common/assert.h"
#include "common/tracing/http_tracer_impl.h"

#include "fmt/format.h"

namespace Envoy {
namespace ExtAuthz {
namespace TcpFilter {

InstanceStats Config::generateStats(const std::string& name, Stats::Scope& scope) {
const std::string final_prefix = fmt::format("ext_authz.{}.", name);
return {ALL_TCP_EXT_AUTHZ_STATS(POOL_COUNTER_PREFIX(scope, final_prefix),
POOL_GAUGE_PREFIX(scope, final_prefix))};
}

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

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

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

Network::FilterStatus Instance::onData(Buffer::Instance&) {
if (status_ == Status::NotStarted) {
// By waiting to invoke the check at onData() the call to authorization service will have
// sufficient information to fillout the checkRequest_.
callCheck();
}
return status_ == Status::Calling ? Network::FilterStatus::StopIteration
: Network::FilterStatus::Continue;
}

Network::FilterStatus Instance::onNewConnection() {
// Wait till onData() happens.
return Network::FilterStatus::Continue;
}

void Instance::onEvent(Network::ConnectionEvent event) {
if (event == Network::ConnectionEvent::RemoteClose ||
event == Network::ConnectionEvent::LocalClose) {
if (status_ == Status::Calling) {
// Make sure that any pending request in the client is cancelled. This will be NOP if the
// request already completed.
client_->cancel();
config_->stats().active_.dec();
}
}
}

void Instance::onComplete(CheckStatus status) {
status_ = Status::Complete;
config_->stats().active_.dec();

switch (status) {
case CheckStatus::OK:
config_->stats().ok_.inc();
break;
case CheckStatus::Error:
config_->stats().error_.inc();
break;
case CheckStatus::Denied:
config_->stats().denied_.inc();
break;
}

// 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())) {
config_->stats().cx_closed_.inc();
filter_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush);
} else {
// We can get completion inline, so only call continue if that isn't happening.
if (!calling_check_) {
filter_callbacks_->continueReading();
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 wonder if readDisable(false) above is sufficient, and you don't need to do this; it's docs say it will restart the read pipeline, and redispatch pending data. Does this work for this filter? That could eliminate the need to maintain calling_check_.

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.

Moving readDisable(false) here -which is what u were asking about at the top of this function-makes sense.
From what I understood-which may be totally wrong-is that readDisable(false) was to get the socket buffer pipeline moving and the filter_callbacks_->continueReading() is to get the filter chain iteration moving forward. The filter iteration may have been stopped iff the callCheck()and the onComplete() callback happened asynchronously.
But if they both are invoked in the same call stack then the filter chain was never stopped, hence it doesn't need to be kick started again by invoking filter_callbacks_->continueReading().

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.

I think that filter_callback->connection().readDisable(false|true) will no longer be necessary since now we will invoke the external authz service from onData() which clearly returns either a StopIteration or Continue and NOT from onEvent().

}
}
}

} // namespace TcpFilter
} // namespace ExtAuthz
} // namespace Envoy
107 changes: 107 additions & 0 deletions source/common/filter/ext_authz.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#pragma once

#include <cstdint>
#include <memory>
#include <string>
#include <vector>

#include "envoy/config/filter/network/ext_authz/v2/ext_authz.pb.h"
#include "envoy/ext_authz/ext_authz.h"
#include "envoy/network/connection.h"
#include "envoy/network/filter.h"
#include "envoy/runtime/runtime.h"
#include "envoy/stats/stats_macros.h"
#include "envoy/upstream/cluster_manager.h"

#include "common/ext_authz/ext_authz_impl.h"

namespace Envoy {
namespace ExtAuthz {
namespace TcpFilter {

/**
* All tcp external authorization stats. @see stats_macros.h
*/
// clang-format off
#define ALL_TCP_EXT_AUTHZ_STATS(COUNTER, GAUGE) \
COUNTER(total) \
COUNTER(error) \
COUNTER(denied) \
COUNTER(ok) \
COUNTER(cx_closed) \
GAUGE (active)
// clang-format on

/**
* Struct definition for all external authorization stats. @see stats_macros.h
*/
struct InstanceStats {
ALL_TCP_EXT_AUTHZ_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT)
};

/**
* Global configuration for ExtAuthz filter.
*/
class Config {
public:
Config(const envoy::config::filter::network::ext_authz::v2::ExtAuthz& config, Stats::Scope& scope)
: stats_(generateStats(config.stat_prefix(), scope)),
failure_mode_allow_(config.failure_mode_allow()) {}

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

private:
static InstanceStats generateStats(const std::string& name, Stats::Scope& scope);
const InstanceStats stats_;
bool failure_mode_allow_;
};

typedef std::shared_ptr<Config> ConfigSharedPtr;

/**
* ExtAuthz filter instance. This filter will call the Authorization service with the given
* configuration parameters. If the authorization service returns an error or a deny the
* connection will be closed without any further filters being called. Otherwise all buffered
* data will be released to further filters.
*/
class Instance : public Network::ReadFilter,
public Network::ConnectionCallbacks,
public RequestCallbacks {
public:
Instance(ConfigSharedPtr config, ClientPtr&& client)
: config_(config), client_(std::move(client)) {}
~Instance() {}

// Network::ReadFilter
Network::FilterStatus onData(Buffer::Instance& data) override;
Network::FilterStatus onNewConnection() override;
void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override {
filter_callbacks_ = &callbacks;
filter_callbacks_->connection().addConnectionCallbacks(*this);
}

// Network::ConnectionCallbacks
void onEvent(Network::ConnectionEvent event) override;
void onAboveWriteBufferHighWatermark() override {}
void onBelowWriteBufferLowWatermark() override {}

// ExtAuthz::RequestCallbacks
void onComplete(CheckStatus status) override;

private:
enum class Status { NotStarted, Calling, Complete };
void callCheck();

ConfigSharedPtr config_;
ClientPtr client_;
Network::ReadFilterCallbacks* filter_callbacks_{};
Status status_{Status::NotStarted};
bool calling_check_{};
envoy::service::auth::v2::CheckRequest checkRequest_{};
};

} // TcpFilter
} // namespace ExtAuthz
} // namespace Envoy
1 change: 1 addition & 0 deletions source/exe/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ envoy_cc_library(
"//source/server/config/listener:proxy_protocol_lib",
"//source/server/config/network:client_ssl_auth_lib",
"//source/server/config/network:echo_lib",
"//source/server/config/network:ext_authz_lib",
"//source/server/config/network:http_connection_manager_lib",
"//source/server/config/network:ratelimit_lib",
"//source/server/config/network:raw_buffer_socket_lib",
Expand Down
14 changes: 14 additions & 0 deletions source/server/config/network/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,17 @@ envoy_cc_library(
"@envoy_api//envoy/api/v2/auth:cert_cc",
],
)

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/filter:ext_authz_lib",
"//source/common/protobuf:utility_lib",
"@envoy_api//envoy/config/filter/network/ext_authz/v2:ext_authz_cc",
],
)
68 changes: 68 additions & 0 deletions source/server/config/network/ext_authz.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include "server/config/network/ext_authz.h"

#include <chrono>
#include <string>

#include "envoy/config/filter/network/ext_authz/v2/ext_authz.pb.validate.h"
#include "envoy/ext_authz/ext_authz.h"
#include "envoy/network/connection.h"
#include "envoy/registry/registry.h"

#include "common/ext_authz/ext_authz_impl.h"
#include "common/filter/ext_authz.h"
#include "common/protobuf/utility.h"

namespace Envoy {
namespace Server {
namespace Configuration {

NetworkFilterFactoryCb ExtAuthzConfigFactory::createFilter(
const envoy::config::filter::network::ext_authz::v2::ExtAuthz& proto_config,
FactoryContext& context) {

ASSERT(!proto_config.stat_prefix().empty());
ASSERT(!proto_config.grpc_service().envoy_grpc().cluster_name().empty() ||
!proto_config.grpc_service().google_grpc().target_uri().empty());

ExtAuthz::TcpFilter::ConfigSharedPtr ext_authz_config(
new ExtAuthz::TcpFilter::Config(proto_config, context.scope()));
const uint32_t timeout_ms = PROTOBUF_GET_MS_OR_DEFAULT(proto_config.grpc_service(), timeout, 200);

return [ grpc_service = proto_config.grpc_service(), &context, ext_authz_config,
timeout_ms ](Network::FilterManager & filter_manager)
->void {

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));
filter_manager.addReadFilter(Network::ReadFilterSharedPtr{
new ExtAuthz::TcpFilter::Instance(ext_authz_config, std::move(client))});
};
}

NetworkFilterFactoryCb ExtAuthzConfigFactory::createFilterFactory(const Json::Object&,
FactoryContext&) {
NOT_IMPLEMENTED;
}

NetworkFilterFactoryCb
ExtAuthzConfigFactory::createFilterFactoryFromProto(const Protobuf::Message& proto_config,
FactoryContext& context) {
return createFilter(
MessageUtil::downcastAndValidate<
const envoy::config::filter::network::ext_authz::v2::ExtAuthz&>(proto_config),
context);
}

/**
* Static registration for the external authorization filter. @see RegisterFactory.
*/
static Registry::RegisterFactory<ExtAuthzConfigFactory, NamedNetworkFilterConfigFactory>
registered_;

} // namespace Configuration
} // namespace Server
} // namespace Envoy
Loading