Skip to content

Commit

Permalink
rbac: add rbac network filter. (#4083)
Browse files Browse the repository at this point in the history
Signed-off-by: Yangmin Zhu <ymzhu@google.com>
  • Loading branch information
yangminzhu authored and mattklein123 committed Aug 14, 2018
1 parent 5a7152d commit c283439
Show file tree
Hide file tree
Showing 33 changed files with 963 additions and 70 deletions.
1 change: 1 addition & 0 deletions api/docs/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ proto_library(
"//envoy/config/filter/network/http_connection_manager/v2:http_connection_manager",
"//envoy/config/filter/network/mongo_proxy/v2:mongo_proxy",
"//envoy/config/filter/network/rate_limit/v2:rate_limit",
"//envoy/config/filter/network/rbac/v2:rbac",
"//envoy/config/filter/network/redis_proxy/v2:redis_proxy",
"//envoy/config/filter/network/tcp_proxy/v2:tcp_proxy",
"//envoy/config/grpc_credential/v2alpha:file_based_metadata",
Expand Down
2 changes: 1 addition & 1 deletion api/envoy/config/filter/http/rbac/v2/rbac.proto
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ message RBAC {

// Shadow rules are not enforced by the filter (i.e., returning a 403)
// but will emit stats and logs and can be used for rule testing.
// If absent, no shadow RBAC policy with be applied.
// If absent, no shadow RBAC policy will be applied.
config.rbac.v2alpha.RBAC shadow_rules = 2;
}

Expand Down
9 changes: 9 additions & 0 deletions api/envoy/config/filter/network/rbac/v2/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
load("//bazel:api_build_system.bzl", "api_proto_library_internal")

licenses(["notice"]) # Apache 2

api_proto_library_internal(
name = "rbac",
srcs = ["rbac.proto"],
deps = ["//envoy/config/rbac/v2alpha:rbac"],
)
30 changes: 30 additions & 0 deletions api/envoy/config/filter/network/rbac/v2/rbac.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
syntax = "proto3";

package envoy.config.filter.network.rbac.v2;
option go_package = "v2";

import "envoy/config/rbac/v2alpha/rbac.proto";

import "validate/validate.proto";
import "gogoproto/gogo.proto";

// [#protodoc-title: RBAC]
// Role-Based Access Control :ref:`configuration overview <config_network_filters_rbac>`.

// RBAC network filter config.
//
// Header and Metadata 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>`.
message RBAC {
// Specify the RBAC rules to be applied globally.
// If absent, no enforcing RBAC policy will be applied.
config.rbac.v2alpha.RBAC rules = 1;

// Shadow rules are not enforced by the filter but will emit stats and logs
// and can be used for rule testing.
// If absent, no shadow RBAC policy will be applied.
config.rbac.v2alpha.RBAC shadow_rules = 2;

// The prefix to use when emitting statistics.
string stat_prefix = 3 [(validate.rules).string.min_bytes = 1];
}
12 changes: 8 additions & 4 deletions api/envoy/config/rbac/v2alpha/rbac.proto
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ message Permission {
// When any is set, it matches any action.
bool any = 3 [(validate.rules).bool.const = true];

// A header (or psuedo-header such as :path or :method) on the incoming HTTP request.
// A header (or psuedo-header such as :path or :method) on the incoming HTTP request. Only
// available for HTTP request.
envoy.api.v2.route.HeaderMatcher header = 4;

// A CIDR block that describes the destination IP.
Expand All @@ -112,7 +113,8 @@ message Permission {
// A port number that describes the destination port connecting to.
uint32 destination_port = 6 [(validate.rules).uint32.lte = 65535];

// Metadata that describes additional information about the action.
// Metadata that describes additional information about the action. Only available for HTTP
// request.
envoy.type.matcher.MetadataMatcher metadata = 7;

// Negates matching the provided permission. For instance, if the value of `not_rule` would
Expand Down Expand Up @@ -156,10 +158,12 @@ message Principal {
// A CIDR block that describes the downstream IP.
envoy.api.v2.core.CidrRange source_ip = 5;

// A header (or psuedo-header such as :path or :method) on the incoming HTTP request.
// A header (or psuedo-header such as :path or :method) on the incoming HTTP request. Only
// available for HTTP request.
envoy.api.v2.route.HeaderMatcher header = 6;

// Metadata that describes additional information about the principal.
// Metadata that describes additional information about the principal. Only available for HTTP
// request.
envoy.type.matcher.MetadataMatcher metadata = 7;

// Negates matching the provided principal. For instance, if the value of `not_id` would match,
Expand Down
1 change: 1 addition & 0 deletions docs/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ PROTO_RST="
/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto.rst
/envoy/config/filter/network/mongo_proxy/v2/mongo_proxy/envoy/config/filter/network/mongo_proxy/v2/mongo_proxy.proto.rst
/envoy/config/filter/network/rate_limit/v2/rate_limit/envoy/config/filter/network/rate_limit/v2/rate_limit.proto.rst
/envoy/config/filter/network/rbac/v2/rbac/envoy/config/filter/network/rbac/v2/rbac.proto.rst
/envoy/config/filter/network/redis_proxy/v2/redis_proxy/envoy/config/filter/network/redis_proxy/v2/redis_proxy.proto.rst
/envoy/config/filter/network/tcp_proxy/v2/tcp_proxy/envoy/config/filter/network/tcp_proxy/v2/tcp_proxy.proto.rst
/envoy/config/health_checker/redis/v2/redis/envoy/config/health_checker/redis/v2/redis.proto.rst
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ filters.
ext_authz_filter
mongo_proxy_filter
rate_limit_filter
rbac_filter
redis_proxy_filter
tcp_proxy_filter
27 changes: 27 additions & 0 deletions docs/root/configuration/network_filters/rbac_filter.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.. _config_network_filters_rbac:

Role Based Access Control (RBAC) Network Filter
===============================================

The RBAC network filter is used to authorize actions (permissions) by identified downstream clients
(principals). This is useful to explicitly manage callers to an application and protect it from
unexpected or forbidden agents. The filter supports configuration with either a safe-list (ALLOW) or
block-list (DENY) set of policies based on properties of the connection (IPs, ports, SSL subject).
This filter also supports policy in both enforcement and shadow modes. Shadow mode won't effect real
users, it is used to test that a new set of policies work before rolling out to production.

* :ref:`v2 API reference <envoy_api_msg_config.filter.network.rbac.v2.RBAC>`

Statistics
----------

The RBAC network filter outputs statistics in the *<stat_prefix>.rbac.* namespace.

.. csv-table::
:header: Name, Type, Description
:widths: 1, 1, 2

allowed, Counter, Total requests that were allowed access
denied, Counter, Total requests that were denied access
shadow_allowed, Counter, Total requests that would be allowed access by the filter's shadow rules
shadow_denied, Counter, Total requests that would be denied access by the filter's shadow rules
1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Version history
:ref:`use_data_plane_proto<envoy_api_field_config.ratelimit.v2.RateLimitServiceConfig.use_data_plane_proto>`
boolean flag in the ratelimit configuration.
Support for the legacy proto :repo:`source/common/ratelimit/ratelimit.proto` is deprecated and will be removed at the start of the 1.9.0 release cycle.
* rbac network filter: a :ref:`role-based access control network filter <config_network_filters_rbac>` has been added.
* rest-api: added ability to set the :ref:`request timeout <envoy_api_field_core.ApiConfigSource.request_timeout>` for REST API requests.
* router: added ability to set request/response headers at the :ref:`envoy_api_msg_route.Route` level.
* tracing: added support for configuration of :ref:`tracing sampling
Expand Down
5 changes: 3 additions & 2 deletions source/extensions/extensions_build_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ EXTENSIONS = {
"envoy.filters.http.grpc_json_transcoder": "//source/extensions/filters/http/grpc_json_transcoder:config",
"envoy.filters.http.grpc_web": "//source/extensions/filters/http/grpc_web:config",
"envoy.filters.http.gzip": "//source/extensions/filters/http/gzip:config",
"envoy.filters.http.health_check": "//source/extensions/filters/http/health_check:config",
"envoy.filters.http.header_to_metadata": "//source/extensions/filters/http/header_to_metadata:config",
"envoy.filters.http.health_check": "//source/extensions/filters/http/health_check:config",
"envoy.filters.http.ip_tagging": "//source/extensions/filters/http/ip_tagging:config",
"envoy.filters.http.jwt_authn": "//source/extensions/filters/http/jwt_authn:config",
"envoy.filters.http.lua": "//source/extensions/filters/http/lua:config",
Expand Down Expand Up @@ -65,8 +65,9 @@ EXTENSIONS = {
"envoy.filters.network.ext_authz": "//source/extensions/filters/network/ext_authz:config",
"envoy.filters.network.http_connection_manager": "//source/extensions/filters/network/http_connection_manager:config",
"envoy.filters.network.mongo_proxy": "//source/extensions/filters/network/mongo_proxy:config",
"envoy.filters.network.redis_proxy": "//source/extensions/filters/network/redis_proxy:config",
"envoy.filters.network.ratelimit": "//source/extensions/filters/network/ratelimit:config",
"envoy.filters.network.rbac": "//source/extensions/filters/network/rbac:config",
"envoy.filters.network.redis_proxy": "//source/extensions/filters/network/redis_proxy:config",
"envoy.filters.network.tcp_proxy": "//source/extensions/filters/network/tcp_proxy:config",
"envoy.filters.network.thrift_proxy": "//source/extensions/filters/network/thrift_proxy:config",

Expand Down
12 changes: 12 additions & 0 deletions source/extensions/filters/common/rbac/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ load(

envoy_package()

envoy_cc_library(
name = "utility_lib",
srcs = ["utility.cc"],
hdrs = ["utility.h"],
deps = [
":engine_lib",
"//include/envoy/stats:stats_macros",
"@envoy_api//envoy/config/filter/http/rbac/v2:rbac_cc",
"@envoy_api//envoy/config/filter/network/rbac/v2:rbac_cc",
],
)

envoy_cc_library(
name = "matchers_lib",
srcs = ["matchers.cc"],
Expand Down
7 changes: 7 additions & 0 deletions source/extensions/filters/common/rbac/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ class RoleBasedAccessControlEngine {
virtual bool allowed(const Network::Connection& connection, const Envoy::Http::HeaderMap& headers,
const envoy::api::v2::core::Metadata& metadata,
std::string* effective_policy_id) const PURE;

/**
* Returns whether or not the current action is permitted.
*
* @param connection the downstream connection used to identify the action/principal.
*/
virtual bool allowed(const Network::Connection& connection) const PURE;
};

} // namespace RBAC
Expand Down
11 changes: 11 additions & 0 deletions source/extensions/filters/common/rbac/engine_impl.cc
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
#include "extensions/filters/common/rbac/engine_impl.h"

#include "common/http/header_map_impl.h"

namespace Envoy {
namespace Extensions {
namespace Filters {
namespace Common {
namespace RBAC {

namespace {
const Envoy::Http::HeaderMapImpl empty_header = Envoy::Http::HeaderMapImpl();
const envoy::api::v2::core::Metadata empty_metadata = envoy::api::v2::core::Metadata();
} // namespace

RoleBasedAccessControlEngineImpl::RoleBasedAccessControlEngineImpl(
const envoy::config::rbac::v2alpha::RBAC& rules)
: allowed_if_matched_(rules.action() ==
Expand Down Expand Up @@ -37,6 +44,10 @@ bool RoleBasedAccessControlEngineImpl::allowed(const Network::Connection& connec
return matched == allowed_if_matched_;
}

bool RoleBasedAccessControlEngineImpl::allowed(const Network::Connection& connection) const {
return allowed(connection, empty_header, empty_metadata, nullptr);
}

} // namespace RBAC
} // namespace Common
} // namespace Filters
Expand Down
2 changes: 2 additions & 0 deletions source/extensions/filters/common/rbac/engine_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class RoleBasedAccessControlEngineImpl : public RoleBasedAccessControlEngine {
const envoy::api::v2::core::Metadata& metadata,
std::string* effective_policy_id) const override;

bool allowed(const Network::Connection& connection) const override;

private:
const bool allowed_if_matched_;

Expand Down
18 changes: 18 additions & 0 deletions source/extensions/filters/common/rbac/utility.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include "extensions/filters/common/rbac/utility.h"

namespace Envoy {
namespace Extensions {
namespace Filters {
namespace Common {
namespace RBAC {

RoleBasedAccessControlFilterStats generateStats(const std::string& prefix, Stats::Scope& scope) {
const std::string final_prefix = prefix + "rbac.";
return {ALL_RBAC_FILTER_STATS(POOL_COUNTER_PREFIX(scope, final_prefix))};
}

} // namespace RBAC
} // namespace Common
} // namespace Filters
} // namespace Extensions
} // namespace Envoy
54 changes: 54 additions & 0 deletions source/extensions/filters/common/rbac/utility.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#pragma once

#include "envoy/config/filter/http/rbac/v2/rbac.pb.h"
#include "envoy/config/filter/network/rbac/v2/rbac.pb.h"
#include "envoy/stats/stats_macros.h"

#include "extensions/filters/common/rbac/engine_impl.h"

namespace Envoy {
namespace Extensions {
namespace Filters {
namespace Common {
namespace RBAC {

/**
* All stats for the RBAC filter. @see stats_macros.h
*/
// clang-format off
#define ALL_RBAC_FILTER_STATS(COUNTER) \
COUNTER(allowed) \
COUNTER(denied) \
COUNTER(shadow_allowed) \
COUNTER(shadow_denied)
// clang-format on

/**
* Wrapper struct for RBAC filter stats. @see stats_macros.h
*/
struct RoleBasedAccessControlFilterStats {
ALL_RBAC_FILTER_STATS(GENERATE_COUNTER_STRUCT)
};

RoleBasedAccessControlFilterStats generateStats(const std::string& prefix, Stats::Scope& scope);

enum class EnforcementMode { Enforced, Shadow };

template <class ConfigType>
absl::optional<RoleBasedAccessControlEngineImpl> createEngine(const ConfigType& config) {
return config.has_rules() ? absl::make_optional<RoleBasedAccessControlEngineImpl>(config.rules())
: absl::nullopt;
}

template <class ConfigType>
absl::optional<RoleBasedAccessControlEngineImpl> createShadowEngine(const ConfigType& config) {
return config.has_shadow_rules()
? absl::make_optional<RoleBasedAccessControlEngineImpl>(config.shadow_rules())
: absl::nullopt;
}

} // namespace RBAC
} // namespace Common
} // namespace Filters
} // namespace Extensions
} // namespace Envoy
1 change: 1 addition & 0 deletions source/extensions/filters/http/rbac/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ envoy_cc_library(
"//include/envoy/stats:stats_macros",
"//source/common/http:utility_lib",
"//source/extensions/filters/common/rbac:engine_lib",
"//source/extensions/filters/common/rbac:utility_lib",
"//source/extensions/filters/http:well_known_names",
"@envoy_api//envoy/config/filter/http/rbac/v2:rbac_cc",
],
Expand Down
43 changes: 12 additions & 31 deletions source/extensions/filters/http/rbac/rbac_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,19 @@ static const std::string shadow_resp_code_field = "shadow_response_code";
RoleBasedAccessControlFilterConfig::RoleBasedAccessControlFilterConfig(
const envoy::config::filter::http::rbac::v2::RBAC& proto_config,
const std::string& stats_prefix, Stats::Scope& scope)
: stats_(RoleBasedAccessControlFilter::generateStats(stats_prefix, scope)),
engine_(proto_config.has_rules()
? absl::make_optional<Filters::Common::RBAC::RoleBasedAccessControlEngineImpl>(
proto_config.rules())
: absl::nullopt),
shadow_engine_(
proto_config.has_shadow_rules()
? absl::make_optional<Filters::Common::RBAC::RoleBasedAccessControlEngineImpl>(
proto_config.shadow_rules())
: absl::nullopt) {}

RoleBasedAccessControlFilterStats
RoleBasedAccessControlFilter::generateStats(const std::string& prefix, Stats::Scope& scope) {
const std::string final_prefix = prefix + "rbac.";
return {ALL_RBAC_FILTER_STATS(POOL_COUNTER_PREFIX(scope, final_prefix))};
}
: stats_(Filters::Common::RBAC::generateStats(stats_prefix, scope)),
engine_(Filters::Common::RBAC::createEngine(proto_config)),
shadow_engine_(Filters::Common::RBAC::createShadowEngine(proto_config)) {}

const absl::optional<Filters::Common::RBAC::RoleBasedAccessControlEngineImpl>&
RoleBasedAccessControlFilterConfig::engine(const Router::RouteConstSharedPtr route,
EnforcementMode mode) const {
Filters::Common::RBAC::EnforcementMode mode) const {
if (!route || !route->routeEntry()) {
return engine(mode);
}

const std::string& name = HttpFilterNames::get().Rbac;
const auto* entry = route->routeEntry();

const auto* route_local =
entry->perFilterConfigTyped<RoleBasedAccessControlRouteSpecificFilterConfig>(name)
?: entry->virtualHost()
Expand All @@ -60,15 +46,8 @@ RoleBasedAccessControlFilterConfig::engine(const Router::RouteConstSharedPtr rou

RoleBasedAccessControlRouteSpecificFilterConfig::RoleBasedAccessControlRouteSpecificFilterConfig(
const envoy::config::filter::http::rbac::v2::RBACPerRoute& per_route_config)
: engine_(per_route_config.rbac().has_rules()
? absl::make_optional<Filters::Common::RBAC::RoleBasedAccessControlEngineImpl>(
per_route_config.rbac().rules())
: absl::nullopt),
shadow_engine_(
per_route_config.rbac().has_shadow_rules()
? absl::make_optional<Filters::Common::RBAC::RoleBasedAccessControlEngineImpl>(
per_route_config.rbac().shadow_rules())
: absl::nullopt) {}
: engine_(Filters::Common::RBAC::createEngine(per_route_config.rbac())),
shadow_engine_(Filters::Common::RBAC::createShadowEngine(per_route_config.rbac())) {}

Http::FilterHeadersStatus RoleBasedAccessControlFilter::decodeHeaders(Http::HeaderMap& headers,
bool) {
Expand All @@ -84,9 +63,11 @@ Http::FilterHeadersStatus RoleBasedAccessControlFilter::decodeHeaders(Http::Head
callbacks_->connection()->ssl()->subjectPeerCertificate()
: "none",
headers, callbacks_->requestInfo().dynamicMetadata().DebugString());

std::string effective_policy_id;
const absl::optional<Filters::Common::RBAC::RoleBasedAccessControlEngineImpl>& shadow_engine =
config_->engine(callbacks_->route(), EnforcementMode::Shadow);
const auto& shadow_engine =
config_->engine(callbacks_->route(), Filters::Common::RBAC::EnforcementMode::Shadow);

if (shadow_engine.has_value()) {
std::string shadow_resp_code = resp_code_200;
if (shadow_engine->allowed(*callbacks_->connection(), headers,
Expand Down Expand Up @@ -119,8 +100,8 @@ Http::FilterHeadersStatus RoleBasedAccessControlFilter::decodeHeaders(Http::Head
}
}

const absl::optional<Filters::Common::RBAC::RoleBasedAccessControlEngineImpl>& engine =
config_->engine(callbacks_->route(), EnforcementMode::Enforced);
const auto& engine =
config_->engine(callbacks_->route(), Filters::Common::RBAC::EnforcementMode::Enforced);
if (engine.has_value()) {
if (engine->allowed(*callbacks_->connection(), headers,
callbacks_->requestInfo().dynamicMetadata(), nullptr)) {
Expand Down
Loading

0 comments on commit c283439

Please sign in to comment.