Skip to content
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

rbac: add rbac network filter. #4083

Merged
merged 13 commits into from
Aug 14, 2018
Merged

Conversation

yangminzhu
Copy link
Contributor

@yangminzhu yangminzhu commented Aug 8, 2018

Description:

  • Added a new RBAC network filter using the same config proto.
  • Refactored the RBAC HTTP filter to share some common config code.
  • Updated the RBAC matcher construction to ignore header and metadata for RBAC network filter.

Risk Level: Medium.

Testing: Added unit test and integration test for the new network filter. Also tested manually with Envoy built with the RBAC network filter.

Docs Changes: Added network filter docs.

Release Notes: Added for the RBAC network filter.

Fixes #3993

/cc @quanjielin I add a TODO for collecting metrics for shadow rules in the RBAC network filter.
/cc @liminw @lizan @mattklein123

Signed-off-by: Yangmin Zhu ymzhu@google.com

Signed-off-by: Yangmin Zhu <ymzhu@google.com>
@lizan lizan self-assigned this Aug 8, 2018
Copy link
Member

@lizan lizan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a quick pass, will do another pass later today or tomorrow

// If absent, no enforcing RBAC policy will be applied.
config.rbac.v2alpha.RBAC rules = 1;

// Shadow rules are not enforced by the filter (i.e., returning a 403)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not 403 for network filter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

const envoy::config::filter::http::rbac::v2::RBAC& proto_config,
const std::string& stats_prefix, Stats::Scope& scope)
: stats_(generateStats(stats_prefix, scope)),
INIT_RBAC_ENGINES(proto_config, false /* disable_http_rules */) {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need the INIT_RBAC_ENGINES macro? You can call the other constructors here. Or extract common part to other class, commented in header.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main reason is the type of proto_config are different.
For RoleBasedAccessControlRouteSpecificFilterConfig, the type is RBACPerRoute;
For RoleBasedAccessControlFilterConfig, the type is http::rbac::v2::RBAC and network::rbac::v2::RBAC respectively.
Do you prefer a template function instead of the INIT_RBAC_ENGINES macro?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I would prefer a template function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


enum class EnforcementMode { Enforced, Shadow };

class RoleBasedAccessControlRouteSpecificFilterConfig : public Router::RouteSpecificFilterConfig {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these two classes are pretty similar, you might want to combine them, or extract common part out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's copied directly from the existing implementation. I remember there had been some discussions in previous PR to combine them but later decided to not to do so. Also looked at the content, it seems there is not too much thing to share, do you have any specific suggestions here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, ok then I would not combine those config classes here, rather keep them separately.

callbacks_->connection().ssl()->subjectPeerCertificate()
: "none");

const Envoy::Http::HeaderMapImpl empty_header;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onData is called multiple times. we should only do rbac check on the first one.

Copy link
Contributor Author

@yangminzhu yangminzhu Aug 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can cache the check result, but just curious could it have any problems with the cache? I assume the cert, ip and port won't change on following onData()? Also whenever a newer RBAC config is pushed, envoy will create a new network filter, so it's also no problem to cache the result?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Envoy will call onData() for each buffer it receives. But you don't need to call rbac check on every buffer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const envoy::config::filter::network::rbac::v2::RBAC& proto_config,
Server::Configuration::FactoryContext& context) {
Filters::Common::RBAC::RoleBasedAccessControlFilterConfigSharedPtr config(
new Filters::Common::RBAC::RoleBasedAccessControlFilterConfig(proto_config, context.scope()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use make_shared

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Signed-off-by: Yangmin Zhu <ymzhu@google.com>
Signed-off-by: Yangmin Zhu <ymzhu@google.com>
@qiwzhang
Copy link
Contributor

qiwzhang commented Aug 8, 2018

LGTM

const envoy::config::filter::http::rbac::v2::RBAC& proto_config,
const std::string& stats_prefix, Stats::Scope& scope)
: stats_(generateStats(stats_prefix, scope)),
INIT_RBAC_ENGINES(proto_config, false /* disable_http_rules */) {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I would prefer a template function

const envoy::api::v2::core::Metadata& metadata,
std::string* effective_policy_id) const PURE;
virtual bool
allowed(const Network::Connection& connection,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://google.github.io/styleguide/cppguide.html#Default_Arguments

Default arguments are banned on virtual functions, where they don't work properly, and in cases where the specified default might not evaluate to the same value depending on when it was evaluated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks for the reference!


enum class EnforcementMode { Enforced, Shadow };

class RoleBasedAccessControlRouteSpecificFilterConfig : public Router::RouteSpecificFilterConfig {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, ok then I would not combine those config classes here, rather keep them separately.

RoleBasedAccessControlFilter::checkEngine(Filters::Common::RBAC::EnforcementMode mode) {
const auto& engine = config_->engine(nullptr, NetworkFilterNames::get().Rbac, mode);
if (engine.has_value()) {
if (engine->allowed(callbacks_->connection(), Envoy::Http::HeaderMapImpl(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a fan of leaking Http::HeaderMapImpl into network filters, can you add another allowed in to engine interface and call it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

case envoy::config::rbac::v2alpha::Permission::RuleCase::kHeader:
if (disable_http_rules) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this again, why do we need disable_http_rules and ignore HTTP headers? When a HeaderMatch specified in TCP filter, shouldn't it just reject the configuration?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense for me to treat this simply as a deny, @liminw WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. If a rules says "allow requests with header a=foo", the rules does not match in TCP filter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to reject invalid rules in config.cc

: absl::nullopt;
}

class RoleBasedAccessControlRouteSpecificFilterConfig : public Router::RouteSpecificFilterConfig {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you should move this to Common, it is HTTP specific.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved back to the http filer.

/**
* Configuration for the RBAC filter.
*/
class RoleBasedAccessControlFilterConfig {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still not convinced we should combine HTTP RBAC and TCP RBAC config in same class. You can have template functions and stats macros above in common/rbac/config_utility. having a filter_name in engine() below seems hacky.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, Moved back to http and network filter respectively, renamed config to utility.

Signed-off-by: Yangmin Zhu <ymzhu@google.com>
@@ -0,0 +1,31 @@
syntax = "proto3";

package envoy.config.filter.network.rbac.v2;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is version defined? Is "v2" the correct version? @lizan @mattklein123

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's defined the same to rbac http proto

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes v2 is correct, all envoy protos are under v2 (or v2alpha, v2beta)

@@ -159,7 +160,8 @@ message Principal {
// A header (or psuedo-header such as :path or :method) on the incoming HTTP request.
envoy.api.v2.route.HeaderMatcher header = 6;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"only available for HTTP requests" comments apply here as well.

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 off properties of the connection (IPs, ports, SSL subject).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

based off => based on?

(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 off properties of the connection (IPs, ports, SSL subject).
This filter also supports policy in both enforcement and shadow mode, shadow mode won't effect real
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mode => modes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shadow mode => Shadow mode (New sentence starts)

case envoy::config::rbac::v2alpha::Permission::RuleCase::kHeader:
if (disable_http_rules) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. If a rules says "allow requests with header a=foo", the rules does not match in TCP filter.

Signed-off-by: Yangmin Zhu <ymzhu@google.com>
Signed-off-by: Yangmin Zhu <ymzhu@google.com>
Signed-off-by: Yangmin Zhu <ymzhu@google.com>
Signed-off-by: Yangmin Zhu <ymzhu@google.com>
@yangminzhu
Copy link
Contributor Author

@lizan @liminw I have addressed comments so far, PTAL, thank you for the review.

@@ -0,0 +1,31 @@
syntax = "proto3";

package envoy.config.filter.network.rbac.v2;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes v2 is correct, all envoy protos are under v2 (or v2alpha, v2beta)

address:
socket_address:
address: 127.0.0.1
port_value: 8000
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not use fixed port number for tests (they couldn't be ran parallel, and fail for port conflict, which is hard to notice)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, we don't really need port in rbac config.

@@ -37,6 +39,11 @@ bool RoleBasedAccessControlEngineImpl::allowed(const Network::Connection& connec
return matched == allowed_if_matched_;
}

bool RoleBasedAccessControlEngineImpl::allowed(const Network::Connection& connection) const {
return allowed(connection, Envoy::Http::HeaderMapImpl(), envoy::api::v2::core::Metadata(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make the HeaderMapImpl and Metadata static variable? HeaderMapImpl does memory allocations for inline headers, constructing everytime is not good. Ideally we should make the parameter a pointer instead of a reference.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

namespace NetworkFilters {
namespace RBACFilter {

enum EngineResult { UNKNOWN, NONE, ALLOW, DENY };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When is NONE result used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NONE means no rbac policies, later we then don't increase metrics (allowed or denied).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Thanks for explaining.

namespace NetworkFilters {
namespace RBACFilter {

class RoleBasedAccessControlNetworkFilterTest : public testing::Test {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any tests on identity (SAN field in cert)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no such test in both http and tcp rbac filter so far.

namespace RBACFilter {

static void validateFail(const std::string& header, const std::string& metadata) {
throw EnvoyException(fmt::format("Found header({}) or metadata({}) rule,"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What will users see if validation fail? The request being rejected? With what error code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message should be returned to the entity trying to apply the config. In Istio, this should be Pilot. There is no request at this point, this happens in config stage.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Good point.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I'm not sure if it's really worth doing this, but up to you. It's going to be difficult to keep this in sync when new features are added to the HTTP filter and IMO it's fine to just ignore fields that are not supported at the network layer. But don't feel that strongly about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Matt, I'll keep the validation logic for now and also keep an eye on this if it becomes a big problem between the HTTP filter and the network filter.

Signed-off-by: Yangmin Zhu <ymzhu@google.com>
namespace NetworkFilters {
namespace RBACFilter {

enum EngineResult { UNKNOWN, NONE, ALLOW, DENY };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enum values using PascalCase (e.g., RoundRobin).

https://github.com/envoyproxy/envoy/blob/master/STYLE.md

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Signed-off-by: Yangmin Zhu <ymzhu@google.com>
lizan
lizan previously approved these changes Aug 10, 2018
@mattklein123 mattklein123 self-assigned this Aug 11, 2018
Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great. Few small comments. Please check that you have 100% code coverage on the new filter.

// RBAC network filter config.
//
// Header and Metadata should not be used in rules/shadow_rules in RBAC network filter as
// these information are only available in :ref:`RBAC http filter <config_http_filters_rbac>`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/these information are/this information is

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -69,6 +69,7 @@ EXTENSIONS = {
"envoy.filters.network.ratelimit": "//source/extensions/filters/network/ratelimit: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",
"envoy.filters.network.rbac": "//source/extensions/filters/network/rbac:config",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: please alpha order (can you check to see if anything else is not ordered)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also reordered HTTP filters.

namespace RBACFilter {

static void validateFail(const std::string& header, const std::string& metadata) {
throw EnvoyException(fmt::format("Found header({}) or metadata({}) rule,"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I'm not sure if it's really worth doing this, but up to you. It's going to be difficult to keep this in sync when new features are added to the HTTP filter and IMO it's fine to just ignore fields that are not supported at the network layer. But don't feel that strongly about it.

: "none");

if (shadow_engine_result_ == Unknown) {
// TODO(quanlin): Support metric collection for RBAC network filter.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you check metrics below? Is this comment still valid?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry the comment might be a bit misleading, I actually mean to pass the metrics to other filers. In HTTP filter this is done by using metadata. But there is no metadata for TCP filter so I created this TODO.

Signed-off-by: Yangmin Zhu <ymzhu@google.com>
@yangminzhu
Copy link
Contributor Author

@mattklein123
I added one more test to cover the case when there is no RBAC policy and checked the default behavior is to allow access. After this it's 100% covered except for the ENVOY_LOG statements.

@@ -49,6 +49,7 @@ Version history
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.
* rest-api: added ability to set the :ref:`request timeout <envoy_api_field_core.ApiConfigSource.request_timeout>` for REST API requests.
* rbac network filter: a :ref:`role-based access control network filter <config_network_filters_rbac>` has been added.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this should be one line above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Signed-off-by: Yangmin Zhu <ymzhu@google.com>
Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, very nice!

@mattklein123 mattklein123 merged commit c283439 into envoyproxy:master Aug 14, 2018
snowp added a commit to snowp/envoy that referenced this pull request Aug 14, 2018
* origin/master: (38 commits)
  test: add tests for corner-cases around sending requests before run() starts or after run() ends. (envoyproxy#4114)
  perf: reduce the memory usage of LC Trie construction (envoyproxy#4117)
  test: moving redundant code in websocket_integration_test to utilities (envoyproxy#4127)
  test: make YamlLoadFromStringFail less picky about error msg. (envoyproxy#4141)
  rbac: add rbac network filter. (envoyproxy#4083)
  fuzz: route lookup and header finalization fuzzer. (envoyproxy#4116)
  Set content-type and content-length (envoyproxy#4113)
  fault: use FractionalPercent for percent (envoyproxy#3978)
  test: Fix inverted exact match logic in IntegrationTcpClient::waitForData() (envoyproxy#4134)
  Added cluster_name to load assignment config for static cluster (envoyproxy#4123)
  ssl: refactor ContextConfig to use TlsCertificateConfig (envoyproxy#4115)
  syscall: refactor OsSysCalls for deeper errno latching (envoyproxy#4111)
  thrift_proxy: fix oneway bugs (envoyproxy#4025)
  Do not crash when converting YAML to JSON fails (envoyproxy#4110)
  config: allow unknown fields flag (take 2) (envoyproxy#4096)
  Use a jittered backoff strategy for handling HdsDelegate stream/connection failures (envoyproxy#4108)
  bazel: use GCS remote cache (envoyproxy#4050)
  Add thread local cache of overload action states (envoyproxy#4090)
  Added TCP healthcheck capabilities to the HdsDelegate (envoyproxy#4079)
  secret: add secret provider interface and use it for TlsCertificates (envoyproxy#4086)
  ...

Signed-off-by: Snow Pettersen <snowp@squareup.com>
@yangminzhu yangminzhu deleted the rbac_tcp branch August 14, 2018 18:08
@zuercher zuercher mentioned this pull request Aug 14, 2018
@liminw liminw mentioned this pull request Sep 17, 2018
8 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants