Skip to content
Closed
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
17 changes: 16 additions & 1 deletion api/envoy/config/accesslog/v3/accesslog.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "envoy/config/core/v3/base.proto";
import "envoy/config/route/v3/route_components.proto";
import "envoy/data/accesslog/v3/accesslog.proto";
import "envoy/type/matcher/v3/metadata.proto";
import "envoy/type/matcher/v3/string.proto";
import "envoy/type/v3/percent.proto";

import "google/protobuf/any.proto";
Expand Down Expand Up @@ -44,7 +45,7 @@ message AccessLog {
}
}

// [#next-free-field: 14]
// [#next-free-field: 15]
message AccessLogFilter {
option (udpa.annotations.versioning).previous_message_type =
"envoy.config.filter.accesslog.v2.AccessLogFilter";
Expand Down Expand Up @@ -91,6 +92,9 @@ message AccessLogFilter {

// Log Type Filter
LogTypeFilter log_type_filter = 13;

// Response code details filter.
ResponseCodeDetailsFilter response_code_details_filter = 14;
}
}

Expand Down Expand Up @@ -266,6 +270,17 @@ message ResponseFlagFilter {
}];
}

// Filters requests that received responses with :ref:`response code details
// <config_access_log_format_response_code_details>` matching any of the configured
// string matchers.
message ResponseCodeDetailsFilter {
repeated type.matcher.v3.StringMatcher details = 1
[(validate.rules).repeated = {min_items: 1}];

// If set, the filter will instead block responses whose code details match the configured list.
bool exclude = 2;
}

// Filters gRPC requests based on their response status. If a gRPC status is not
// provided, the filter will infer the status from the HTTP status code.
message GrpcStatusFilter {
Expand Down
7 changes: 6 additions & 1 deletion api/envoy/extensions/filters/network/rbac/v3/rbac.proto
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
//
// Header should not be used in rules/shadow_rules in RBAC network filter as
// this information is only available in :ref:`RBAC http filter <config_http_filters_rbac>`.
// [#next-free-field: 9]
// [#next-free-field: 10]
message RBAC {
option (udpa.annotations.versioning).previous_message_type =
"envoy.config.filter.network.rbac.v2.RBAC";
Expand Down Expand Up @@ -95,4 +95,9 @@ message RBAC {
// This is useful to provide a better protection for Envoy against clients that retries
// aggressively when the connection is rejected by the RBAC filter.
google.protobuf.Duration delay_deny = 8;

// When set, RBAC policies are evaluated as soon as the connection is established, before any
// payload bytes are read from the peer. This is primarily useful for upstream network filters
// where Envoy must reject a CONNECT attempt prior to forwarding any data to the upstream host.
bool enforce_on_new_connection = 9;
}
8 changes: 8 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,14 @@ removed_config_or_runtime:
Removed runtime guard ``envoy.reloadable_features.proxy_104`` and legacy code paths.

new_features:
- area: rbac
change: |
Added :ref:`enforce_on_new_connection
<envoy_v3_api_field_extensions.filters.network.rbac.v3.RBAC.enforce_on_new_connection>` to the
RBAC network filter and registered it as an upstream network filter so that clusters can evaluate
RBAC policies as soon as an upstream connection is established. This enables egress CONNECT
proxies to attach RBAC filters directly to upstream clusters and block requests to specific CIDR
ranges before any bytes are forwarded to the destination.
- area: router
change: |
Added :ref:`use_hash_policy <envoy_v3_api_field_config.route.v3.WeightedCluster.use_hash_policy>`
Expand Down
12 changes: 12 additions & 0 deletions docs/root/configuration/listeners/network_filters/rbac_filter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ RBAC filter and the upstream backend.
* This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC``.
* :ref:`v3 API reference <envoy_v3_api_msg_extensions.filters.network.rbac.v3.RBAC>`

Upstream network usage
----------------------

The RBAC network filter can also be attached to upstream clusters via :ref:`cluster filters
<envoy_v3_api_field_config.cluster.v3.Cluster.filters>` to enforce policies before Envoy tunnels a
CONNECT request to a remote host. When used upstream, set
:ref:`enforce_on_new_connection <envoy_v3_api_field_extensions.filters.network.rbac.v3.RBAC.enforce_on_new_connection>`
to ``true`` so that the RBAC engine evaluates the destination IP information as soon as the
connection is established, before any payload bytes are proxied to the upstream host. This allows
operators to implement static blocklists for specific CIDR ranges in front of HTTP CONNECT egress
proxies.

Statistics
----------

Expand Down
6 changes: 6 additions & 0 deletions docs/root/intro/arch_overview/security/rbac_filter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ or as a :ref:`HTTP filter <config_http_filters_rbac>` or both. If the request is
by the network filter then the connection will be closed. If the request is deemed unauthorized by
the HTTP filter the request will be denied with 403 (Forbidden) response.

When the network filter is attached to an upstream cluster (for example, to block CONNECT tunnels to
specific CIDR ranges), set
:ref:`enforce_on_new_connection <envoy_v3_api_field_extensions.filters.network.rbac.v3.RBAC.enforce_on_new_connection>`
so that the RBAC engine evaluates the upstream destination metadata before Envoy forwards any bytes
to the remote host.

The RBAC filter's rules can be either configured with a list of
:ref:`policies <envoy_v3_api_field_config.rbac.v3.RBAC.policies>` or the
:ref:`matching API <envoy_v3_api_msg_.xds.type.matcher.v3.Matcher>`.
Expand Down
2 changes: 2 additions & 0 deletions source/common/access_log/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ envoy_cc_library(
"//envoy/runtime:runtime_interface",
"//envoy/upstream:upstream_interface",
"//source/common/common:assert_lib",
"//source/common/common:matchers_lib",
"//source/common/common:utility_lib",
"//source/common/config:utility_lib",
"//source/common/formatter:substitution_formatter_lib",
Expand All @@ -33,6 +34,7 @@ envoy_cc_library(
"//source/common/tracing:http_tracer_lib",
"@com_google_absl//absl/hash",
"@envoy_api//envoy/config/accesslog/v3:pkg_cc_proto",
"@envoy_api//envoy/type/matcher/v3:pkg_cc_proto",
"@envoy_api//envoy/type/v3:pkg_cc_proto",
],
)
Expand Down
24 changes: 24 additions & 0 deletions source/common/access_log/access_log_impl.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "source/common/access_log/access_log_impl.h"

#include <algorithm>
#include <cstdint>
#include <string>

Expand Down Expand Up @@ -78,6 +79,11 @@ FilterPtr FilterFactory::fromProto(const envoy::config::accesslog::v3::AccessLog
case envoy::config::accesslog::v3::AccessLogFilter::FilterSpecifierCase::kResponseFlagFilter:
MessageUtil::validate(config, validation_visitor);
return FilterPtr{new ResponseFlagFilter(config.response_flag_filter())};
case envoy::config::accesslog::v3::AccessLogFilter::FilterSpecifierCase::
kResponseCodeDetailsFilter:
MessageUtil::validate(config, validation_visitor);
return FilterPtr{new ResponseCodeDetailsFilter(config.response_code_details_filter(),
context.serverFactoryContext())};
case envoy::config::accesslog::v3::AccessLogFilter::FilterSpecifierCase::kGrpcStatusFilter:
MessageUtil::validate(config, validation_visitor);
return FilterPtr{new GrpcStatusFilter(config.grpc_status_filter())};
Expand Down Expand Up @@ -249,6 +255,24 @@ bool ResponseFlagFilter::evaluate(const Formatter::HttpFormatterContext&,
return info.hasAnyResponseFlag();
}

ResponseCodeDetailsFilter::ResponseCodeDetailsFilter(
const envoy::config::accesslog::v3::ResponseCodeDetailsFilter& config,
Server::Configuration::CommonFactoryContext& context)
: exclude_(config.exclude()) {
for (const auto& matcher_config : config.details()) {
matchers_.emplace_back(std::make_unique<Matchers::StringMatcherImpl>(matcher_config, context));
}
}

bool ResponseCodeDetailsFilter::evaluate(const Formatter::HttpFormatterContext&,
const StreamInfo::StreamInfo& info) const {
const bool matched =
info.responseCodeDetails().has_value() &&
std::any_of(matchers_.begin(), matchers_.end(),
[&](const auto& matcher) { return matcher->match(info.responseCodeDetails().value()); });
return exclude_ ? !matched : matched;
}

GrpcStatusFilter::GrpcStatusFilter(const envoy::config::accesslog::v3::GrpcStatusFilter& config) {
for (int i = 0; i < config.statuses_size(); i++) {
statuses_.insert(protoToGrpcStatus(config.statuses(i)));
Expand Down
17 changes: 17 additions & 0 deletions source/common/access_log/access_log_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,23 @@ class ResponseFlagFilter : public Filter {
std::vector<bool> configured_flags_{};
};

/**
* Filters requests that have response code details matching any configured string matcher.
*/
class ResponseCodeDetailsFilter : public Filter {
public:
ResponseCodeDetailsFilter(
const envoy::config::accesslog::v3::ResponseCodeDetailsFilter& config,
Server::Configuration::CommonFactoryContext& context);

bool evaluate(const Formatter::HttpFormatterContext& context,
const StreamInfo::StreamInfo& info) const override;

private:
std::vector<Matchers::StringMatcherPtr> matchers_;
bool exclude_{false};
};

/**
* Filters requests that have a response with a gRPC status. Because the gRPC protocol does not
* guarantee a gRPC status code, if a gRPC status code is not available, then the filter will infer
Expand Down
1 change: 1 addition & 0 deletions source/extensions/extensions_metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,7 @@ envoy.filters.network.rbac:
- envoy.filters.network
security_posture: robust_to_untrusted_downstream
status: stable
status_upstream: alpha
type_urls:
- envoy.extensions.filters.network.rbac.v3.RBAC
envoy.filters.network.redis_proxy:
Expand Down
1 change: 1 addition & 0 deletions source/extensions/filters/network/rbac/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ envoy_cc_extension(
":rbac_filter",
"//envoy/registry",
"//envoy/server:filter_config_interface",
"//source/common/protobuf:utility_lib",
"//source/extensions/filters/network:well_known_names",
"//source/extensions/filters/network/common:factory_base_lib",
"@envoy_api//envoy/config/rbac/v3:pkg_cc_proto",
Expand Down
46 changes: 38 additions & 8 deletions source/extensions/filters/network/rbac/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "envoy/network/connection.h"
#include "envoy/registry/registry.h"

#include "source/common/protobuf/utility.h"
#include "source/extensions/filters/network/rbac/rbac_filter.h"
#include "source/extensions/filters/network/well_known_names.h"

Expand Down Expand Up @@ -74,30 +75,59 @@ static void validateRbacRules(const envoy::config::rbac::v3::RBAC& rules) {
}
}

Network::FilterFactoryCb
RoleBasedAccessControlNetworkFilterConfigFactory::createFilterFactoryFromProtoTyped(
const envoy::extensions::filters::network::rbac::v3::RBAC& proto_config,
Server::Configuration::FactoryContext& context) {
static RoleBasedAccessControlFilterConfigSharedPtr
createFilterConfig(const envoy::extensions::filters::network::rbac::v3::RBAC& proto_config,
Stats::Scope& scope, Server::Configuration::ServerFactoryContext& context,
ProtobufMessage::ValidationVisitor& validation_visitor) {
if (proto_config.has_rules()) {
validateRbacRules(proto_config.rules());
}
if (proto_config.has_shadow_rules()) {
validateRbacRules(proto_config.shadow_rules());
}
RoleBasedAccessControlFilterConfigSharedPtr config(
std::make_shared<RoleBasedAccessControlFilterConfig>(proto_config, context.scope(),
context.serverFactoryContext(),
context.messageValidationVisitor()));
return std::make_shared<RoleBasedAccessControlFilterConfig>(proto_config, scope, context,
validation_visitor);
}

Network::FilterFactoryCb
RoleBasedAccessControlNetworkFilterConfigFactory::createFilterFactoryFromProtoTyped(
const envoy::extensions::filters::network::rbac::v3::RBAC& proto_config,
Server::Configuration::FactoryContext& context) {
RoleBasedAccessControlFilterConfigSharedPtr config =
createFilterConfig(proto_config, context.scope(), context.serverFactoryContext(),
context.messageValidationVisitor());
return [config](Network::FilterManager& filter_manager) -> void {
filter_manager.addReadFilter(std::make_shared<RoleBasedAccessControlFilter>(config));
};
}

Network::FilterFactoryCb
RoleBasedAccessControlUpstreamNetworkFilterConfigFactory::createFilterFactoryFromProto(
const Protobuf::Message& config,
Server::Configuration::UpstreamFactoryContext& context) {
const auto& proto_config = MessageUtil::downcastAndValidate<
const envoy::extensions::filters::network::rbac::v3::RBAC&>(
config, context.serverFactoryContext().messageValidationVisitor());
RoleBasedAccessControlFilterConfigSharedPtr filter_config =
createFilterConfig(proto_config, context.scope(), context.serverFactoryContext(),
context.serverFactoryContext().messageValidationVisitor());
return [filter_config](Network::FilterManager& filter_manager) -> void {
filter_manager.addReadFilter(std::make_shared<RoleBasedAccessControlFilter>(filter_config));
};
}

ProtobufTypes::MessagePtr
RoleBasedAccessControlUpstreamNetworkFilterConfigFactory::createEmptyConfigProto() {
return std::make_unique<envoy::extensions::filters::network::rbac::v3::RBAC>();
}

/**
* Static registration for the RBAC network filter. @see RegisterFactory.
*/
REGISTER_FACTORY(RoleBasedAccessControlNetworkFilterConfigFactory,
Server::Configuration::NamedNetworkFilterConfigFactory);
REGISTER_FACTORY(RoleBasedAccessControlUpstreamNetworkFilterConfigFactory,
Server::Configuration::NamedUpstreamNetworkFilterConfigFactory);

} // namespace RBACFilter
} // namespace NetworkFilters
Expand Down
13 changes: 13 additions & 0 deletions source/extensions/filters/network/rbac/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "source/extensions/filters/network/common/factory_base.h"
#include "source/extensions/filters/network/well_known_names.h"
#include "envoy/server/filter_config.h"

namespace Envoy {
namespace Extensions {
Expand All @@ -27,6 +28,18 @@ class RoleBasedAccessControlNetworkFilterConfigFactory
Server::Configuration::FactoryContext& context) override;
};

class RoleBasedAccessControlUpstreamNetworkFilterConfigFactory
: public Server::Configuration::NamedUpstreamNetworkFilterConfigFactory {
public:
RoleBasedAccessControlUpstreamNetworkFilterConfigFactory() = default;

Network::FilterFactoryCb createFilterFactoryFromProto(
const Protobuf::Message& config,
Server::Configuration::UpstreamFactoryContext& context) override;
ProtobufTypes::MessagePtr createEmptyConfigProto() override;
std::string name() const override { return NetworkFilterNames::get().Rbac; }
};

} // namespace RBACFilter
} // namespace NetworkFilters
} // namespace Extensions
Expand Down
20 changes: 19 additions & 1 deletion source/extensions/filters/network/rbac/rbac_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "envoy/buffer/buffer.h"
#include "envoy/extensions/filters/network/rbac/v3/rbac.pb.h"
#include "envoy/network/connection.h"
#include "envoy/stream_info/stream_info.h"

#include "source/common/network/matching/inputs.h"
#include "source/common/ssl/matching/inputs.h"
Expand Down Expand Up @@ -70,9 +71,21 @@ RoleBasedAccessControlFilterConfig::RoleBasedAccessControlFilterConfig(
shadow_engine_(Filters::Common::RBAC::createShadowEngine(
proto_config, context, validation_visitor, action_validation_visitor_)),
enforcement_type_(proto_config.enforcement_type()),
delay_deny_ms_(PROTOBUF_GET_MS_OR_DEFAULT(proto_config, delay_deny, 0)) {}
delay_deny_ms_(PROTOBUF_GET_MS_OR_DEFAULT(proto_config, delay_deny, 0)),
enforce_on_new_connection_(proto_config.enforce_on_new_connection()) {}

Network::FilterStatus RoleBasedAccessControlFilter::onData(Buffer::Instance&, bool) {
return evaluatePolicies();
}

Network::FilterStatus RoleBasedAccessControlFilter::onNewConnection() {
if (!config_->enforceOnNewConnection()) {
return Network::FilterStatus::Continue;
}
return evaluatePolicies();
}

Network::FilterStatus RoleBasedAccessControlFilter::evaluatePolicies() {
if (is_delay_denied_) {
return Network::FilterStatus::StopIteration;
}
Expand Down Expand Up @@ -133,6 +146,11 @@ Network::FilterStatus RoleBasedAccessControlFilter::onData(Buffer::Instance&, bo
} else if (engine_result_ == Deny) {
callbacks_->connection().streamInfo().setConnectionTerminationDetails(
Filters::Common::RBAC::responseDetail(log_policy_id));
if (!response_flag_set_) {
callbacks_->connection().streamInfo().setResponseFlag(
StreamInfo::CoreResponseFlag::UnauthorizedExternalService);
response_flag_set_ = true;
}

if (const std::chrono::milliseconds duration = config_->delayDenyMs();
duration > std::chrono::milliseconds(0)) {
Expand Down
6 changes: 5 additions & 1 deletion source/extensions/filters/network/rbac/rbac_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class RoleBasedAccessControlFilterConfig {
}

std::chrono::milliseconds delayDenyMs() const { return delay_deny_ms_; }
bool enforceOnNewConnection() const { return enforce_on_new_connection_; }

private:
Filters::Common::RBAC::RoleBasedAccessControlFilterStats stats_;
Expand All @@ -70,6 +71,7 @@ class RoleBasedAccessControlFilterConfig {
std::unique_ptr<const Filters::Common::RBAC::RoleBasedAccessControlEngine> shadow_engine_;
const envoy::extensions::filters::network::rbac::v3::RBAC::EnforcementType enforcement_type_;
std::chrono::milliseconds delay_deny_ms_;
const bool enforce_on_new_connection_;
};

using RoleBasedAccessControlFilterConfigSharedPtr =
Expand All @@ -89,7 +91,7 @@ class RoleBasedAccessControlFilter : public Network::ReadFilter,

// Network::ReadFilter
Network::FilterStatus onData(Buffer::Instance& data, bool end_stream) override;
Network::FilterStatus onNewConnection() override { return Network::FilterStatus::Continue; };
Network::FilterStatus onNewConnection() override;
void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override {
callbacks_ = &callbacks;
callbacks_->connection().addConnectionCallbacks(*this);
Expand All @@ -112,8 +114,10 @@ class RoleBasedAccessControlFilter : public Network::ReadFilter,
Result checkEngine(Filters::Common::RBAC::EnforcementMode mode) const;
void closeConnection() const;
void resetTimerState();
Network::FilterStatus evaluatePolicies();
Event::TimerPtr delay_timer_{nullptr};
bool is_delay_denied_{false};
bool response_flag_set_{false};
};

} // namespace RBACFilter
Expand Down
Loading