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

filter: add conditions to access control filter #7716

Merged
merged 43 commits into from
Aug 19, 2019
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
3d4f7aa
Initial ABAC filter
kyessenov Jul 24, 2019
beb197c
typos
kyessenov Jul 25, 2019
e914f46
Merge remote-tracking branch 'upstream/master' into abac_filter_needs…
kyessenov Jul 26, 2019
ec74571
review
kyessenov Jul 26, 2019
97efd6d
spelling
kyessenov Jul 26, 2019
f3668b2
undo watermark
kyessenov Jul 26, 2019
dca0933
Merge remote-tracking branch 'upstream/master' into abac_filter_needs…
kyessenov Jul 29, 2019
8e80999
review feedback
kyessenov Jul 30, 2019
970b361
review feedback
kyessenov Jul 30, 2019
ebf4c4a
review feedback
kyessenov Jul 30, 2019
d1fd462
add code owners
kyessenov Jul 30, 2019
1235c7d
Merge remote-tracking branch 'upstream/master' into abac_filter_needs…
kyessenov Jul 30, 2019
41220da
update cel-cpp
kyessenov Jul 30, 2019
982e3fd
combine engines
kyessenov Jul 30, 2019
c31b4a7
make arena explicit
kyessenov Jul 30, 2019
e309dd8
more attributes
kyessenov Jul 30, 2019
6cdbe8e
build fix
kyessenov Jul 31, 2019
f934dda
refactor
kyessenov Jul 31, 2019
bf99900
fix unit tests
kyessenov Jul 31, 2019
6861b98
Merge remote-tracking branch 'upstream/master' into abac_filter_needs…
kyessenov Jul 31, 2019
b27790a
unit tests
kyessenov Jul 31, 2019
9393ca9
fix api
kyessenov Jul 31, 2019
5d44fea
add metadata test
kyessenov Jul 31, 2019
784a970
release note
kyessenov Aug 1, 2019
c081695
typo
kyessenov Aug 1, 2019
9f812b5
Merge remote-tracking branch 'upstream/master' into abac_filter_needs…
kyessenov Aug 1, 2019
184fe6b
add constant folding; use unique_ptr to avoid copying the engine
kyessenov Aug 2, 2019
811fba6
merge fix
kyessenov Aug 2, 2019
64c9201
merge fix
kyessenov Aug 5, 2019
684d473
apply a patch for gcc
kyessenov Aug 6, 2019
08fe702
more specific patch
kyessenov Aug 6, 2019
580a79f
fix the macro specializer
kyessenov Aug 7, 2019
8f72a50
oops, reverse the patch
kyessenov Aug 8, 2019
a2a9a7f
Merge remote-tracking branch 'upstream/master' into abac_filter_needs…
kyessenov Aug 8, 2019
f86eadf
update re2 import
kyessenov Aug 8, 2019
b9c755c
Merge remote-tracking branch 'upstream/master' into abac_filter_needs…
kyessenov Aug 9, 2019
532ed67
align with ext_authz by using source and destination
kyessenov Aug 9, 2019
c1f1890
bump up coverage
kyessenov Aug 9, 2019
aed67b6
Merge remote-tracking branch 'upstream/master' into abac_filter_needs…
kyessenov Aug 15, 2019
76b6788
update cel-cpp
kyessenov Aug 15, 2019
a0daefa
Merge remote-tracking branch 'upstream/master' into abac_filter_needs…
kyessenov Aug 15, 2019
753b352
merge fix
kyessenov Aug 15, 2019
8df3414
bump up coverage
kyessenov Aug 15, 2019
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 CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ extensions/filters/common/original_src @snowp @klarose
/*/extensions/retry/host/omit_canary_hosts @sriduth @snowp
# http inspector
/*/extensions/filters/listener/http_inspector @crazyxy @PiotrSikora @lizan
# attribute context
/*/extensions/filters/common/expr @kyessenov @yangminzhu
1 change: 1 addition & 0 deletions api/envoy/config/filter/http/rbac/v2/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ licenses(["notice"]) # Apache 2
api_proto_library_internal(
name = "rbac",
srcs = ["rbac.proto"],
require_py = False,
deps = ["//envoy/config/rbac/v2:rbac"],
)
1 change: 1 addition & 0 deletions api/envoy/config/filter/network/rbac/v2/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ licenses(["notice"]) # Apache 2
api_proto_library_internal(
name = "rbac",
srcs = ["rbac.proto"],
require_py = False,
kyessenov marked this conversation as resolved.
Show resolved Hide resolved
deps = ["//envoy/config/rbac/v2:rbac"],
)
27 changes: 17 additions & 10 deletions api/envoy/config/rbac/v2/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_prot
api_proto_library_internal(
name = "rbac",
srcs = ["rbac.proto"],
external_cc_proto_deps = [
"@com_google_googleapis//google/api/expr/v1alpha1:syntax_cc_proto",
],
external_proto_deps = [
"@com_google_googleapis//google/api/expr/v1alpha1:syntax_proto",
],
require_py = False,
visibility = ["//visibility:public"],
deps = [
"//envoy/api/v2/core:address",
Expand All @@ -14,13 +21,13 @@ api_proto_library_internal(
],
)

api_go_proto_library(
name = "rbac",
proto = ":rbac",
deps = [
"//envoy/api/v2/core:address_go_proto",
"//envoy/api/v2/route:route_go_proto",
"//envoy/type/matcher:metadata_go_proto",
"//envoy/type/matcher:string_go_proto",
],
)
#api_go_proto_library(
kyessenov marked this conversation as resolved.
Show resolved Hide resolved
# name = "rbac",
# proto = ":rbac",
# deps = [
# "//envoy/api/v2/core:address_go_proto",
# "//envoy/api/v2/route:route_go_proto",
# "//envoy/type/matcher:metadata_go_proto",
# "//envoy/type/matcher:string_go_proto",
# ],
#)
5 changes: 5 additions & 0 deletions api/envoy/config/rbac/v2/rbac.proto
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import "envoy/api/v2/route/route.proto";
import "envoy/type/matcher/metadata.proto";
import "envoy/type/matcher/string.proto";

import "google/api/expr/v1alpha1/syntax.proto";

package envoy.config.rbac.v2;

option java_outer_classname = "RbacProto";
Expand Down Expand Up @@ -77,6 +79,9 @@ message RBAC {

// Maps from policy name to policy. A match occurs when at least one policy matches the request.
map<string, Policy> policies = 2;

// A symbolic expression specifying an access control condition.
kyessenov marked this conversation as resolved.
Show resolved Hide resolved
google.api.expr.v1alpha1.Expr condition = 3;
}

// Policy specifies a role and the principals that are assigned/denied the role. A policy matches if
Expand Down
2 changes: 2 additions & 0 deletions bazel/repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ def envoy_dependencies(skip_targets = []):
_com_lightstep_tracer_cpp()
_io_opentracing_cpp()
_net_zlib()
_repository_impl("com_google_re2")
_repository_impl("com_google_cel_cpp")
_repository_impl("bazel_toolchains")

_python_deps()
Expand Down
18 changes: 14 additions & 4 deletions bazel/repository_locations.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ REPOSITORY_LOCATIONS = dict(
urls = ["https://commondatastorage.googleapis.com/chromium-boringssl-docs/fips/boringssl-66005f41fbc3529ffe8d007708756720529da20d.tar.xz"],
),
com_google_absl = dict(
sha256 = "7ddf863ddced6fa5bf7304103f9c7aa619c20a2fcf84475512c8d3834b9d14fa",
strip_prefix = "abseil-cpp-61c9bf3e3e1c28a4aa6d7f1be4b37fd473bb5529",
# 2019-06-05
urls = ["https://github.com/abseil/abseil-cpp/archive/61c9bf3e3e1c28a4aa6d7f1be4b37fd473bb5529.tar.gz"],
sha256 = "05a97ad5bb123ee3f6d65c7b06f6d7de5ce62e4fb971cbe4b5e391dd69704bb7",
strip_prefix = "abseil-cpp-ad1485c8986246b2ae9105e512738d0e97aec887",
# 2019-07-24
urls = ["https://github.com/abseil/abseil-cpp/archive/ad1485c8986246b2ae9105e512738d0e97aec887.tar.gz"],
),
com_github_apache_thrift = dict(
sha256 = "7d59ac4fdcb2c58037ebd4a9da5f9a49e3e034bf75b3f26d9fe48ba3d8806e6b",
Expand Down Expand Up @@ -247,4 +247,14 @@ REPOSITORY_LOCATIONS = dict(
sha256 = "fcdebf54c89d839ffa7eefae166c8e4b551c765559db13ff15bff98047f344fb",
urls = ["https://storage.googleapis.com/quiche-envoy-integration/2a930469533c3b541443488a629fe25cd8ff53d0.tar.gz"],
),
com_google_cel_cpp = dict(
sha256 = "a0e6a6ccf25c1e57ba3c7a997edd9279f8e0b0112e0a3ac705a5568fa32792fc",
strip_prefix = "cel-cpp-d56f26adb53d0f41508a909d15e74d9ffb0e8a6c",
urls = ["https://github.com/google/cel-cpp/archive/d56f26adb53d0f41508a909d15e74d9ffb0e8a6c.tar.gz"],
),
com_google_re2 = dict(
kyessenov marked this conversation as resolved.
Show resolved Hide resolved
sha256 = "f31db9cd224d018a7e4fe88ef84aaa874b0b3ed91d4d98ee5a1531101d3fdc64",
strip_prefix = "re2-87e2ad45e7b18738e1551474f7ee5886ff572059",
urls = ["https://github.com/google/re2/archive/87e2ad45e7b18738e1551474f7ee5886ff572059.tar.gz"],
Copy link
Member

Choose a reason for hiding this comment

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

@kyessenov qq: does cel-cpp rely on specific commit of re2? Asking because it might conflict with #7878, or latest release (2019-08-01) is fine? cc @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.

There should be no difference between which version is used. I think I chose the latest version which I started this PR.

Copy link
Member

Choose a reason for hiding this comment

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

OK that's fine.

Copy link
Contributor

Choose a reason for hiding this comment

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

I was going to ask the same question. I'll switch this back a release version of re2 on a subsequent dependency PR.

Copy link
Member

Choose a reason for hiding this comment

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

I will just fix this when I merge master.

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. Happy to help if necessary. Google3 doesn't really have versions for its repositories, and the upstream cel-cpp is continuously tested against head.

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

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

envoy_package()

envoy_cc_library(
name = "evaluator_lib",
srcs = ["evaluator.cc"],
hdrs = ["evaluator.h"],
deps = [
":context_lib",
"//source/common/http:utility_lib",
"//source/common/protobuf",
"@com_google_cel_cpp//eval/public:builtin_func_registrar",
"@com_google_cel_cpp//eval/public:cel_expr_builder_factory",
"@com_google_cel_cpp//eval/public:cel_expression",
"@com_google_cel_cpp//eval/public:cel_value",
],
)

envoy_cc_library(
name = "context_lib",
srcs = ["context.cc"],
hdrs = ["context.h"],
deps = [
"//source/common/http:utility_lib",
"@com_google_cel_cpp//eval/public:cel_value",
],
)
135 changes: 135 additions & 0 deletions source/extensions/filters/common/expr/context.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#include "extensions/filters/common/expr/context.h"

#include "absl/strings/numbers.h"
#include "absl/time/time.h"

namespace Envoy {
namespace Extensions {
namespace Filters {
namespace Common {
namespace Expr {

namespace {

absl::optional<CelValue> convertHeaderEntry(const Http::HeaderEntry* header) {
if (header == nullptr) {
return {};
}
return CelValue::CreateString(header->value().getStringView());
}

} // namespace

absl::optional<CelValue> HeadersWrapper::operator[](CelValue key) const {
if (!key.IsString()) {
return {};
}
auto out = headers_.get(Http::LowerCaseString(std::string(key.StringOrDie().value())));
return convertHeaderEntry(out);
}

absl::optional<CelValue> RequestWrapper::operator[](CelValue key) const {
if (!key.IsString()) {
return {};
}
auto value = key.StringOrDie().value();
if (value == Path) {
return convertHeaderEntry(wrapper_.headers_.Path());
} else if (value == Host) {
return convertHeaderEntry(wrapper_.headers_.Host());
} else if (value == Scheme) {
return convertHeaderEntry(wrapper_.headers_.Scheme());
} else if (value == Method) {
return convertHeaderEntry(wrapper_.headers_.Method());
} else if (value == Referer) {
return convertHeaderEntry(wrapper_.headers_.Referer());
} else if (value == Headers) {
return CelValue::CreateMap(&wrapper_);
} else if (value == Time) {
return CelValue::CreateTimestamp(absl::FromChrono(info_.startTime()));
} else if (value == ID) {
return convertHeaderEntry(wrapper_.headers_.RequestId());
} else if (value == UserAgent) {
return convertHeaderEntry(wrapper_.headers_.UserAgent());
} else if (value == Size) {
kyessenov marked this conversation as resolved.
Show resolved Hide resolved
// it is important to make a choice whether to rely on content-length vs stream info
// (which is not available at the time of the request headers)
auto length_header = wrapper_.headers_.ContentLength();
if (length_header != nullptr) {
int64_t length;
if (absl::SimpleAtoi(length_header->value().getStringView(), &length)) {
return CelValue::CreateInt64(length);
}
}
} else if (value == TotalSize) {
return CelValue::CreateInt64(info_.bytesReceived() + wrapper_.headers_.byteSize());
} else if (value == Duration) {
auto duration = info_.requestComplete();
if (duration.has_value()) {
return CelValue::CreateDuration(absl::FromChrono(duration.value()));
}
}
return {};
}

absl::optional<CelValue> ResponseWrapper::operator[](CelValue key) const {
if (!key.IsString()) {
return {};
}
auto value = key.StringOrDie().value();
if (value == Code) {
auto code = info_.responseCode();
if (code.has_value()) {
return CelValue::CreateInt64(code.value());
}
} else if (value == Size) {
return CelValue::CreateInt64(info_.bytesSent());
}
return {};
}

absl::optional<CelValue> ConnectionWrapper::operator[](CelValue key) const {
if (!key.IsString()) {
return {};
}
auto value = key.StringOrDie().value();
if (value == LocalAddress) {
return CelValue::CreateString(info_.downstreamLocalAddress()->asStringView());
} else if (value == LocalPort) {
if (info_.downstreamLocalAddress()->ip() != nullptr) {
return CelValue::CreateInt64(info_.downstreamLocalAddress()->ip()->port());
}
} else if (value == RemoteAddress) {
return CelValue::CreateString(info_.downstreamRemoteAddress()->asStringView());
} else if (value == RemotePort) {
if (info_.downstreamRemoteAddress()->ip() != nullptr) {
return CelValue::CreateInt64(info_.downstreamRemoteAddress()->ip()->port());
}
} else if (value == UpstreamAddress) {
auto upstream_host = info_.upstreamHost();
if (upstream_host != nullptr && upstream_host->address() != nullptr) {
return CelValue::CreateString(upstream_host->address()->asStringView());
}
} else if (value == UpstreamPort) {
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());
}
}

auto downstream_ssl = info_.downstreamSslConnection();
if (downstream_ssl != nullptr) {
if (value == MTLS) {
return CelValue::CreateBool(downstream_ssl->peerCertificatePresented());
}
}

return {};
}

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

#include "envoy/stream_info/stream_info.h"

#include "common/http/headers.h"

#include "eval/public/cel_value.h"

namespace Envoy {
namespace Extensions {
namespace Filters {
namespace Common {
namespace Expr {

using CelValue = google::api::expr::runtime::CelValue;

// Symbols for traversing the request properties
constexpr absl::string_view Request = "request";
constexpr absl::string_view Path = "path";
constexpr absl::string_view Host = "host";
constexpr absl::string_view Scheme = "scheme";
constexpr absl::string_view Method = "method";
constexpr absl::string_view Referer = "referer";
constexpr absl::string_view Headers = "headers";
constexpr absl::string_view Time = "time";
constexpr absl::string_view ID = "id";
constexpr absl::string_view UserAgent = "useragent";
kyessenov marked this conversation as resolved.
Show resolved Hide resolved
constexpr absl::string_view Size = "size";
constexpr absl::string_view TotalSize = "total_size";
constexpr absl::string_view Duration = "duration";

// Symbols for traversing the response properties
constexpr absl::string_view Response = "response";
constexpr absl::string_view Code = "code";

// Per-request or per-connection metadata
constexpr absl::string_view Metadata = "metadata";

// Connection properties
constexpr absl::string_view Connection = "connection";
constexpr absl::string_view LocalAddress = "local_address";
constexpr absl::string_view LocalPort = "local_port";
constexpr absl::string_view RemoteAddress = "remote_address";
constexpr absl::string_view RemotePort = "remote_port";
constexpr absl::string_view UpstreamAddress = "upstream_address";
constexpr absl::string_view UpstreamPort = "upstream_port";
constexpr absl::string_view MTLS = "mtls";

kyessenov marked this conversation as resolved.
Show resolved Hide resolved
class RequestWrapper;

class HeadersWrapper : public google::api::expr::runtime::CelMap {
kyessenov marked this conversation as resolved.
Show resolved Hide resolved
public:
HeadersWrapper(const Http::HeaderMap& headers) : headers_(headers) {}
absl::optional<CelValue> operator[](CelValue key) const override;
int size() const override { return headers_.size(); }
bool empty() const override { return headers_.empty(); }
const google::api::expr::runtime::CelList* ListKeys() const override { return nullptr; }

private:
friend class RequestWrapper;
const Http::HeaderMap& headers_;
};

class BaseWrapper : public google::api::expr::runtime::CelMap {
public:
int size() const override { return 0; }
bool empty() const override { return false; }
kyessenov marked this conversation as resolved.
Show resolved Hide resolved
const google::api::expr::runtime::CelList* ListKeys() const override { return nullptr; }
};

class RequestWrapper : public BaseWrapper {
kyessenov marked this conversation as resolved.
Show resolved Hide resolved
public:
RequestWrapper(const Http::HeaderMap& headers, const StreamInfo::StreamInfo& info)
: wrapper_(headers), info_(info) {}
absl::optional<CelValue> operator[](CelValue key) const override;

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

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

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

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

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

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