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
2 changes: 2 additions & 0 deletions api/docs/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ proto_library(
"//envoy/config/filter/http/ip_tagging/v2:ip_tagging",
"//envoy/config/filter/http/lua/v2:lua",
"//envoy/config/filter/http/rate_limit/v2:rate_limit",
"//envoy/config/filter/http/rbac/v2:rbac",
"//envoy/config/filter/http/router/v2:router",
"//envoy/config/filter/http/squash/v2:squash",
"//envoy/config/filter/http/transcoder/v2:transcoder",
Expand All @@ -48,6 +49,7 @@ proto_library(
"//envoy/config/metrics/v2:metrics_service",
"//envoy/config/metrics/v2:stats",
"//envoy/config/ratelimit/v2:rls",
"//envoy/config/rbac/v2alpha:rbac",
"//envoy/config/trace/v2:trace",
"//envoy/config/transport_socket/capture/v2alpha:capture",
"//envoy/extensions/common/tap/v2alpha:capture",
Expand Down
14 changes: 5 additions & 9 deletions api/envoy/config/rbac/v2alpha/rbac.proto
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,8 @@ package envoy.config.rbac.v2alpha;
option go_package = "v2alpha";

// Role Based Access Control (RBAC) provides service-level and method-level access control for a
// service. The RBAC engine authorizes a request by evaluating the request context (expressed in the
// form of :ref: `AttributeContext <envoy_api_msg_service.auth.v2alpha.AttributeContext>`) against
// the RBAC policies.
//
// RBAC policies are additive. The policies are examined in order. A request is allowed once a
// matching policy is found (suppose the `action` is ALLOW).
// service. RBAC policies are additive. The policies are examined in order. A request is allowed
// once a matching policy is found (suppose the `action` is ALLOW).
//
// Here is an example of RBAC configuration. It has two policies:
//
Expand Down Expand Up @@ -48,13 +44,13 @@ option go_package = "v2alpha";
// - any: true
//
message RBAC {
// Should we do white-list or black-list style access control?
// Should we do safe-list or block-list style access control?
enum Action {
// The policies grant access to principals. The rest is denied. This is white-list style
// The policies grant access to principals. The rest is denied. This is safe-list style
// access control. This is the default type.
ALLOW = 0;

// The policies deny access to principals. The rest is allowed. This is black-list style
// The policies deny access to principals. The rest is allowed. This is block-list style
// access control.
DENY = 1;
}
Expand Down
2 changes: 2 additions & 0 deletions docs/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ PROTO_RST="
/envoy/config/filter/http/ip_tagging/v2/ip_tagging/envoy/config/filter/http/ip_tagging/v2/ip_tagging.proto.rst
/envoy/config/filter/http/lua/v2/lua/envoy/config/filter/http/lua/v2/lua.proto.rst
/envoy/config/filter/http/rate_limit/v2/rate_limit/envoy/config/filter/http/rate_limit/v2/rate_limit.proto.rst
/envoy/config/filter/http/rbac/v2/rbac/envoy/config/filter/http/rbac/v2/rbac.proto.rst
/envoy/config/filter/http/router/v2/router/envoy/config/filter/http/router/v2/router.proto.rst
/envoy/config/filter/http/squash/v2/squash/envoy/config/filter/http/squash/v2/squash.proto.rst
/envoy/config/filter/http/transcoder/v2/transcoder/envoy/config/filter/http/transcoder/v2/transcoder.proto.rst
Expand All @@ -92,6 +93,7 @@ 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
/envoy/config/rbac/v2alpha/rbac/envoy/config/rbac/v2alpha/rbac.proto.rst
/envoy/config/transport_socket/capture/v2alpha/capture/envoy/config/transport_socket/capture/v2alpha/capture.proto.rst
/envoy/extensions/common/tap/v2alpha/capture/envoy/extensions/common/tap/v2alpha/capture.proto.rst
/envoy/type/percent/envoy/type/percent.proto.rst
Expand Down
1 change: 1 addition & 0 deletions docs/root/api-v2/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ v2 API reference
http_routes/http_routes
config/filter/filter
config/health_checker/health_checker
config/rbac/rbac
config/transport_socket/transport_socket
admin/admin
common_messages/common_messages
Expand Down
8 changes: 8 additions & 0 deletions docs/root/api-v2/config/rbac/rbac.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
RBAC
====

.. toctree::
:glob:
:maxdepth: 1

v2alpha/*
1 change: 1 addition & 0 deletions docs/root/configuration/http_filters/http_filters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ HTTP filters
ip_tagging_filter
lua_filter
rate_limit_filter
rbac_filter
router_filter
squash_filter
32 changes: 32 additions & 0 deletions docs/root/configuration/http_filters/rbac_filter.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.. _config_http_filters_rbac:

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

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

Choose a reason for hiding this comment

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

🎉

as well as the incoming request's HTTP headers.

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

Per-Route Configuration
-----------------------

The RBAC filter configuration can be overridden or disabled on a per-route basis by providing a
:ref:`RBACPerRoute <envoy_api_msg_config.filter.http.rbac.v2.RBACPerRoute>` configuration on
the virtual host, route, or weighted cluster.

Statistics
----------

The RBAC filter outputs statistics in the *http.<stat_prefix>.rbac.* namespace. The :ref:`stat
prefix <config_http_conn_man_stat_prefix>` comes from the owning HTTP connection manager.

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

allowed, Counter, Total requests that were allowed access by the filter
denied, Counter, Total requests that were denied access by the filter
1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ Version history
* logger: added the ability to optionally set the log format via the :option:`--log-format` option.
* logger: all :ref:`logging levels <operations_admin_interface_logging>` can be configured
at run-time: trace debug info warning error critical.
* rbac http filter: a :ref:`role-based access control http filter <config_http_filters_rbac>` has been added.
* router: The behavior of per-try timeouts have changed in the case where a portion of the response has
already been proxied downstream when the timeout occurs. Previously, the response would be reset
leading to either an HTTP/2 reset or an HTTP/1 closed connection and a partial response. Now, the
Expand Down
2 changes: 1 addition & 1 deletion include/envoy/ssl/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class Connection {
* @return std::string the URI in the SAN field of the peer certificate. Returns "" if there is no
* peer certificate, or no SAN field, or no URI.
**/
virtual std::string uriSanPeerCertificate() PURE;
virtual std::string uriSanPeerCertificate() const PURE;

/**
* @return std::string the URL-encoded PEM-encoded representation of the peer certificate. Returns
Expand Down
70 changes: 33 additions & 37 deletions source/common/http/header_utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,53 +65,49 @@ HeaderUtility::HeaderData::HeaderData(const Json::Object& config)

bool HeaderUtility::matchHeaders(const Http::HeaderMap& request_headers,
const std::vector<HeaderData>& config_headers) {
bool matches = true;

// TODO (rodaine): Should this really allow empty headers to always match?
if (!config_headers.empty()) {
for (const HeaderData& cfg_header_data : config_headers) {
const Http::HeaderEntry* header = request_headers.get(cfg_header_data.name_);

if (header == nullptr) {
matches = cfg_header_data.header_match_type_ == HeaderMatchType::Present
? cfg_header_data.invert_match_
: false;
break;
}

switch (cfg_header_data.header_match_type_) {
case HeaderMatchType::Value:
matches &=
(cfg_header_data.value_.empty() || header->value() == cfg_header_data.value_.c_str());
break;

case HeaderMatchType::Regex:
matches &= std::regex_match(header->value().c_str(), cfg_header_data.regex_pattern_);
break;

case HeaderMatchType::Range: {
int64_t header_value = 0;
matches &= StringUtil::atol(header->value().c_str(), header_value, 10) &&
header_value >= cfg_header_data.range_.start() &&
header_value < cfg_header_data.range_.end();
break;
if (!matchHeaders(request_headers, cfg_header_data)) {
return false;
}
}
}

case HeaderMatchType::Present:
break;
return true;
}

default:
NOT_REACHED;
}
bool HeaderUtility::matchHeaders(const Http::HeaderMap& request_headers,
const HeaderData& header_data) {
const Http::HeaderEntry* header = request_headers.get(header_data.name_);

matches ^= cfg_header_data.invert_match_;
if (header == nullptr) {
return header_data.invert_match_ && header_data.header_match_type_ == HeaderMatchType::Present;
}

if (!matches) {
break;
}
}
bool match;
switch (header_data.header_match_type_) {
case HeaderMatchType::Value:
match = header_data.value_.empty() || header->value() == header_data.value_.c_str();
break;
case HeaderMatchType::Regex:
match = std::regex_match(header->value().c_str(), header_data.regex_pattern_);
break;
case HeaderMatchType::Range: {
int64_t header_value = 0;
match = StringUtil::atol(header->value().c_str(), header_value, 10) &&
header_value >= header_data.range_.start() && header_value < header_data.range_.end();
break;
}
case HeaderMatchType::Present:
match = true;
break;
default:
NOT_REACHED;
}

return matches;
return match != header_data.invert_match_;
}

} // namespace Http
Expand Down
2 changes: 2 additions & 0 deletions source/common/http/header_utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class HeaderUtility {
*/
static bool matchHeaders(const Http::HeaderMap& request_headers,
const std::vector<HeaderData>& config_headers);

static bool matchHeaders(const Http::HeaderMap& request_headers, const HeaderData& config_header);
};
} // namespace Http
} // namespace Envoy
4 changes: 2 additions & 2 deletions source/common/ssl/ssl_socket.cc
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ const std::string& SslSocket::urlEncodedPemEncodedPeerCertificate() const {
return cached_url_encoded_pem_encoded_peer_certificate_;
}

std::string SslSocket::uriSanPeerCertificate() {
std::string SslSocket::uriSanPeerCertificate() const {
bssl::UniquePtr<X509> cert(SSL_get_peer_certificate(ssl_.get()));
if (!cert) {
return "";
Expand All @@ -289,7 +289,7 @@ std::vector<std::string> SslSocket::dnsSansPeerCertificate() {
return getDnsSansFromCertificate(cert.get());
}

std::string SslSocket::getUriSanFromCertificate(X509* cert) {
std::string SslSocket::getUriSanFromCertificate(X509* cert) const {
bssl::UniquePtr<GENERAL_NAMES> san_names(
static_cast<GENERAL_NAMES*>(X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr)));
if (san_names == nullptr) {
Expand Down
4 changes: 2 additions & 2 deletions source/common/ssl/ssl_socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class SslSocket : public Network::TransportSocket,
const std::string& sha256PeerCertificateDigest() const override;
std::string subjectPeerCertificate() const override;
std::string subjectLocalCertificate() const override;
std::string uriSanPeerCertificate() override;
std::string uriSanPeerCertificate() const override;
const std::string& urlEncodedPemEncodedPeerCertificate() const override;
std::vector<std::string> dnsSansPeerCertificate() override;
std::vector<std::string> dnsSansLocalCertificate() override;
Expand All @@ -50,7 +50,7 @@ class SslSocket : public Network::TransportSocket,
Network::PostIoAction doHandshake();
void drainErrorQueue();
void shutdownSsl();
std::string getUriSanFromCertificate(X509* cert);
std::string getUriSanFromCertificate(X509* cert) const;
std::string getSubjectFromCertificate(X509* cert) const;
std::vector<std::string> getDnsSansFromCertificate(X509* cert);

Expand Down
1 change: 1 addition & 0 deletions source/extensions/extensions_build_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ EXTENSIONS = {
"envoy.filters.http.ip_tagging": "//source/extensions/filters/http/ip_tagging:config",
"envoy.filters.http.lua": "//source/extensions/filters/http/lua:config",
"envoy.filters.http.ratelimit": "//source/extensions/filters/http/ratelimit:config",
"envoy.filters.http.rbac": "//source/extensions/filters/http/rbac:config",
"envoy.filters.http.router": "//source/extensions/filters/http/router:config",
"envoy.filters.http.squash": "//source/extensions/filters/http/squash:config",

Expand Down
44 changes: 44 additions & 0 deletions source/extensions/filters/common/rbac/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
licenses(["notice"]) # Apache 2

load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_library",
"envoy_package",
)

envoy_package()

envoy_cc_library(
name = "matchers_lib",
srcs = ["matchers.cc"],
hdrs = ["matchers.h"],
deps = [
"//include/envoy/http:header_map_interface",
"//include/envoy/network:connection_interface",
"//source/common/common:assert_lib",
"//source/common/http:header_utility_lib",
"//source/common/network:cidr_range_lib",
"@envoy_api//envoy/config/rbac/v2alpha:rbac_cc",
],
)

envoy_cc_library(
name = "engine_interface",
hdrs = ["engine.h"],
deps = [
"//include/envoy/http:filter_interface",
"//include/envoy/http:header_map_interface",
"//include/envoy/network:connection_interface",
],
)

envoy_cc_library(
name = "engine_lib",
srcs = ["engine_impl.cc"],
hdrs = ["engine_impl.h"],
deps = [
"//source/extensions/filters/common/rbac:engine_interface",
"//source/extensions/filters/common/rbac:matchers_lib",
"@envoy_api//envoy/config/filter/http/rbac/v2:rbac_cc",
],
)
35 changes: 35 additions & 0 deletions source/extensions/filters/common/rbac/engine.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#pragma once

#include "envoy/http/filter.h"
#include "envoy/http/header_map.h"
#include "envoy/network/connection.h"

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

/**
* Shared logic for evaluating RBAC policies.
*/
class RoleBasedAccessControlEngine : public Router::RouteSpecificFilterConfig {
public:
virtual ~RoleBasedAccessControlEngine() {}

/**
* Returns whether or not the current action is permitted.
*
* @param connection the downstream connection used to identify the action/principal.
* @param headers the headers of the incoming request used to identify the action/principal. An
* empty map should be used if there are no headers available.
*/
virtual bool allowed(const Network::Connection& connection,
const Envoy::Http::HeaderMap& headers) const PURE;
};

} // namespace RBAC
} // namespace Common
} // namespace Filters
} // namespace Extensions
} // namespace Envoy
Loading