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

docs: document access control conditions and attributes #8230

Merged
merged 4 commits into from
Sep 13, 2019
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
5 changes: 3 additions & 2 deletions api/envoy/config/rbac/v2/rbac.proto
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,9 @@ message Policy {
// Principal with the `any` field set to true should be used.
repeated Principal principals = 2 [(validate.rules).repeated .min_items = 1];

// An optional symbolic expression specifying an access control condition.
// The condition is combined with AND semantics.
// An optional symbolic expression specifying an access control
// :ref:`condition <arch_overview_condition>`. The condition is combined
// with the permissions and the principals as a clause with AND semantics.
google.api.expr.v1alpha1.Expr condition = 3;
}

Expand Down
64 changes: 64 additions & 0 deletions docs/root/intro/arch_overview/security/rbac_filter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,67 @@ The filter can be configured with a
:ref:`shadow policy <envoy_api_field_config.filter.http.rbac.v2.RBAC.shadow_rules>` that doesn't
have any effect (i.e. not deny the request) but only emit stats and log the result. This is useful
for testing a rule before applying in production.

.. _arch_overview_condition:

Condition
---------

In addition to the pre-defined permissions and principals, a policy may optionally provide an
authorization condition written in the `Common Expression Language
<https://github.com/google/cel-spec/blob/master/doc/intro.md>`_. The condition specifies an extra
clause that must be satisfied for the policy to match. For example, the following condition checks
whether the request path starts with `/v1/`:

.. code-block:: yaml

call_expr:
function: startsWith
args:
- select_expr:
operand:
ident_expr:
name: request
field: path
- const_expr:
string_value: /v1/

The following attributes are exposed to the language runtime:

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

request.path, string, The path portion of the URL
request.url_path, string, The path portion of the URL without the query string
request.host, string, The host portion of the URL
request.scheme, string, The scheme portion of the URL
request.method, string, Request method
request.headers, string map, All request headers
request.referer, string, Referer request header
request.useragent, string, User agent request header
request.time, timestamp, Time of the first byte received
request.duration, duration, Total duration of the request
request.id, string, Request ID
request.size, int, Size of the request body
request.total_size, int, Total size of the request including the headers
response.code, int, Response HTTP status code
response.headers, string map, All response headers
response.trailers, string map, All response trailers
response.size, int, Size of the response body
source.address, string, Downstream connection remote address
source.port, int, Downstream connection remote port
destination.address, string, Downstream connection local address
destination.port, int, Downstream connection local port
metadata, :ref:`Metadata<envoy_api_msg_core.Metadata>`, Dynamic metadata
connection.mtls, bool, Indicates whether TLS is applied to the downstream connection and the peer ceritificate is presented
connection.requested_server_name, string, Requested server name in the downstream TLS connection
connection.tls_version, string, TLS version of the downstream TLS connection
upstream.address, string, Upstream connection remote address
upstream.port, int, Upstream connection remote port
upstream.mtls, bool, Indicates whether TLS is applied to the upstream connection and the peer ceritificate is presented


Most attributes are optional and provide the default value based on the type of the attribute.
CEL supports presence checks for attributes and maps using `has()` syntax, e.g.
`has(request.referer)`.
31 changes: 25 additions & 6 deletions source/extensions/filters/common/expr/context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -110,22 +110,41 @@ absl::optional<CelValue> ConnectionWrapper::operator[](CelValue key) const {
return {};
}
auto value = key.StringOrDie().value();
if (value == UpstreamAddress) {
if (value == MTLS) {
return CelValue::CreateBool(info_.downstreamSslConnection() != nullptr &&
info_.downstreamSslConnection()->peerCertificatePresented());
} else if (value == RequestedServerName) {
return CelValue::CreateString(info_.requestedServerName());
}

if (info_.downstreamSslConnection() != nullptr) {
if (value == TLSVersion) {
return CelValue::CreateString(info_.downstreamSslConnection()->tlsVersion());
}
}

return {};
}

absl::optional<CelValue> UpstreamWrapper::operator[](CelValue key) const {
if (!key.IsString()) {
return {};
}
auto value = key.StringOrDie().value();
if (value == Address) {
auto upstream_host = info_.upstreamHost();
if (upstream_host != nullptr && upstream_host->address() != nullptr) {
return CelValue::CreateString(upstream_host->address()->asStringView());
}
} else if (value == UpstreamPort) {
} else if (value == Port) {
auto upstream_host = info_.upstreamHost();
if (upstream_host != nullptr && upstream_host->address() != nullptr &&
upstream_host->address()->ip() != nullptr) {
return CelValue::CreateInt64(upstream_host->address()->ip()->port());
}
} else if (value == MTLS) {
return CelValue::CreateBool(info_.downstreamSslConnection() != nullptr &&
info_.downstreamSslConnection()->peerCertificatePresented());
} else if (value == RequestedServerName) {
return CelValue::CreateString(info_.requestedServerName());
return CelValue::CreateBool(info_.upstreamSslConnection() != nullptr &&
info_.upstreamSslConnection()->peerCertificatePresented());
}

return {};
Expand Down
15 changes: 13 additions & 2 deletions source/extensions/filters/common/expr/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,9 @@ constexpr absl::string_view Metadata = "metadata";

// Connection properties
constexpr absl::string_view Connection = "connection";
constexpr absl::string_view UpstreamAddress = "upstream_address";
constexpr absl::string_view UpstreamPort = "upstream_port";
constexpr absl::string_view MTLS = "mtls";
constexpr absl::string_view RequestedServerName = "requested_server_name";
constexpr absl::string_view TLSVersion = "tls_version";

// Source properties
constexpr absl::string_view Source = "source";
Expand All @@ -53,6 +52,9 @@ constexpr absl::string_view Port = "port";
// Destination properties
constexpr absl::string_view Destination = "destination";

// Upstream properties
constexpr absl::string_view Upstream = "upstream";

class RequestWrapper;

class HeadersWrapper : public google::api::expr::runtime::CelMap {
Expand Down Expand Up @@ -112,6 +114,15 @@ class ConnectionWrapper : public BaseWrapper {
const StreamInfo::StreamInfo& info_;
};

class UpstreamWrapper : public BaseWrapper {
public:
UpstreamWrapper(const StreamInfo::StreamInfo& info) : info_(info) {}
absl::optional<CelValue> operator[](CelValue key) const override;

private:
const StreamInfo::StreamInfo& info_;
};

class PeerWrapper : public BaseWrapper {
public:
PeerWrapper(const StreamInfo::StreamInfo& info, bool local) : info_(info), local_(local) {}
Expand Down
2 changes: 2 additions & 0 deletions source/extensions/filters/common/expr/evaluator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,14 @@ absl::optional<CelValue> evaluate(const Expression& expr, Protobuf::Arena* arena
const RequestWrapper request(request_headers, info);
const ResponseWrapper response(response_headers, response_trailers, info);
const ConnectionWrapper connection(info);
const UpstreamWrapper upstream(info);
const PeerWrapper source(info, false);
const PeerWrapper destination(info, true);
activation.InsertValue(Request, CelValue::CreateMap(&request));
activation.InsertValue(Response, CelValue::CreateMap(&response));
activation.InsertValue(Metadata, CelValue::CreateMessage(&info.dynamicMetadata(), arena));
activation.InsertValue(Connection, CelValue::CreateMap(&connection));
activation.InsertValue(Upstream, CelValue::CreateMap(&upstream));
activation.InsertValue(Source, CelValue::CreateMap(&source));
activation.InsertValue(Destination, CelValue::CreateMap(&destination));

Expand Down
38 changes: 29 additions & 9 deletions test/extensions/filters/common/expr/context_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -254,27 +254,33 @@ TEST(Context, ResponseAttributes) {

TEST(Context, ConnectionAttributes) {
NiceMock<StreamInfo::MockStreamInfo> info;
std::shared_ptr<NiceMock<Envoy::Upstream::MockHostDescription>> host(
std::shared_ptr<NiceMock<Envoy::Upstream::MockHostDescription>> upstream_host(
new NiceMock<Envoy::Upstream::MockHostDescription>());
auto connection_info = std::make_shared<NiceMock<Ssl::MockConnectionInfo>>();
auto downstream_ssl_info = std::make_shared<NiceMock<Ssl::MockConnectionInfo>>();
auto upstream_ssl_info = std::make_shared<NiceMock<Ssl::MockConnectionInfo>>();
ConnectionWrapper connection(info);
UpstreamWrapper upstream(info);
PeerWrapper source(info, false);
PeerWrapper destination(info, true);

Network::Address::InstanceConstSharedPtr local =
Network::Utility::parseInternetAddress("1.2.3.4", 123, false);
Network::Address::InstanceConstSharedPtr remote =
Network::Utility::parseInternetAddress("10.20.30.40", 456, false);
Network::Address::InstanceConstSharedPtr upstream =
Network::Address::InstanceConstSharedPtr upstream_address =
Network::Utility::parseInternetAddress("10.1.2.3", 679, false);
const std::string sni_name = "kittens.com";
EXPECT_CALL(info, downstreamLocalAddress()).WillRepeatedly(ReturnRef(local));
EXPECT_CALL(info, downstreamRemoteAddress()).WillRepeatedly(ReturnRef(remote));
EXPECT_CALL(info, downstreamSslConnection()).WillRepeatedly(Return(connection_info));
EXPECT_CALL(info, upstreamHost()).WillRepeatedly(Return(host));
EXPECT_CALL(info, downstreamSslConnection()).WillRepeatedly(Return(downstream_ssl_info));
EXPECT_CALL(info, upstreamSslConnection()).WillRepeatedly(Return(upstream_ssl_info));
EXPECT_CALL(info, upstreamHost()).WillRepeatedly(Return(upstream_host));
EXPECT_CALL(info, requestedServerName()).WillRepeatedly(ReturnRef(sni_name));
EXPECT_CALL(*connection_info, peerCertificatePresented()).WillRepeatedly(Return(true));
EXPECT_CALL(*host, address()).WillRepeatedly(Return(upstream));
EXPECT_CALL(*downstream_ssl_info, peerCertificatePresented()).WillRepeatedly(Return(true));
EXPECT_CALL(*upstream_ssl_info, peerCertificatePresented()).WillRepeatedly(Return(true));
const std::string tls_version = "TLSv1";
EXPECT_CALL(*downstream_ssl_info, tlsVersion()).WillRepeatedly(ReturnRef(tls_version));
EXPECT_CALL(*upstream_host, address()).WillRepeatedly(Return(upstream_address));

{
auto value = connection[CelValue::CreateString(Undefined)];
Expand Down Expand Up @@ -325,19 +331,26 @@ TEST(Context, ConnectionAttributes) {
}

{
auto value = connection[CelValue::CreateString(UpstreamAddress)];
auto value = upstream[CelValue::CreateString(Address)];
EXPECT_TRUE(value.has_value());
ASSERT_TRUE(value.value().IsString());
EXPECT_EQ("10.1.2.3:679", value.value().StringOrDie().value());
}

{
auto value = connection[CelValue::CreateString(UpstreamPort)];
auto value = upstream[CelValue::CreateString(Port)];
EXPECT_TRUE(value.has_value());
ASSERT_TRUE(value.value().IsInt64());
EXPECT_EQ(679, value.value().Int64OrDie());
}

{
auto value = upstream[CelValue::CreateString(MTLS)];
EXPECT_TRUE(value.has_value());
ASSERT_TRUE(value.value().IsBool());
EXPECT_TRUE(value.value().BoolOrDie());
}

{
auto value = connection[CelValue::CreateString(MTLS)];
EXPECT_TRUE(value.has_value());
Expand All @@ -351,6 +364,13 @@ TEST(Context, ConnectionAttributes) {
ASSERT_TRUE(value.value().IsString());
EXPECT_EQ(sni_name, value.value().StringOrDie().value());
}

{
auto value = connection[CelValue::CreateString(TLSVersion)];
EXPECT_TRUE(value.has_value());
ASSERT_TRUE(value.value().IsString());
EXPECT_EQ(tls_version, value.value().StringOrDie().value());
}
}

} // namespace
Expand Down
1 change: 1 addition & 0 deletions tools/spelling_dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ BSON
CAS
CB
CBs
CEL
CDS
CHACHA
CHLO
Expand Down