mirror of https://github.com/grpc/grpc.git
SDK authorization policy translator. (#25361)
* SDK authorization policy translator.pull/25637/head
parent
34c8a1f87a
commit
bd86187f19
26 changed files with 1924 additions and 175 deletions
@ -0,0 +1,328 @@ |
||||
// Copyright 2021 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/security/authorization/rbac_policy.h" |
||||
|
||||
#include "absl/strings/str_format.h" |
||||
#include "absl/strings/str_join.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
//
|
||||
// Rbac
|
||||
//
|
||||
|
||||
Rbac::Rbac(Rbac::Action action, std::map<std::string, Policy> policies) |
||||
: action(action), policies(std::move(policies)) {} |
||||
|
||||
Rbac::Rbac(Rbac&& other) noexcept |
||||
: action(other.action), policies(std::move(other.policies)) {} |
||||
|
||||
Rbac& Rbac::operator=(Rbac&& other) noexcept { |
||||
action = other.action; |
||||
policies = std::move(other.policies); |
||||
return *this; |
||||
} |
||||
|
||||
std::string Rbac::ToString() const { |
||||
std::vector<std::string> contents; |
||||
contents.push_back(absl::StrFormat( |
||||
"Rbac action=%s{", action == Rbac::Action::ALLOW ? "Allow" : "Deny")); |
||||
for (const auto& p : policies) { |
||||
contents.push_back(absl::StrFormat("{\n policy_name=%s\n%s\n}", p.first, |
||||
p.second.ToString())); |
||||
} |
||||
contents.push_back("}"); |
||||
return absl::StrJoin(contents, "\n"); |
||||
} |
||||
|
||||
//
|
||||
// CidrRange
|
||||
//
|
||||
|
||||
Rbac::CidrRange::CidrRange(std::string address_prefix, uint32_t prefix_len) |
||||
: address_prefix(std::move(address_prefix)), prefix_len(prefix_len) {} |
||||
|
||||
Rbac::CidrRange::CidrRange(Rbac::CidrRange&& other) noexcept |
||||
: address_prefix(std::move(other.address_prefix)), |
||||
prefix_len(other.prefix_len) {} |
||||
|
||||
Rbac::CidrRange& Rbac::CidrRange::operator=(Rbac::CidrRange&& other) noexcept { |
||||
address_prefix = std::move(other.address_prefix); |
||||
prefix_len = other.prefix_len; |
||||
return *this; |
||||
} |
||||
|
||||
std::string Rbac::CidrRange::ToString() const { |
||||
return absl::StrFormat("CidrRange{address_prefix=%s,prefix_len=%d}", |
||||
address_prefix, prefix_len); |
||||
} |
||||
|
||||
//
|
||||
// Permission
|
||||
//
|
||||
|
||||
Rbac::Permission::Permission( |
||||
Permission::RuleType type, |
||||
std::vector<std::unique_ptr<Permission>> permissions, bool not_rule) |
||||
: type(type), permissions(std::move(permissions)), not_rule(not_rule) {} |
||||
Rbac::Permission::Permission(Permission::RuleType type, bool not_rule) |
||||
: type(type), not_rule(not_rule) {} |
||||
Rbac::Permission::Permission(Permission::RuleType type, |
||||
HeaderMatcher header_matcher, bool not_rule) |
||||
: type(type), |
||||
header_matcher(std::move(header_matcher)), |
||||
not_rule(not_rule) {} |
||||
Rbac::Permission::Permission(Permission::RuleType type, |
||||
StringMatcher string_matcher, bool not_rule) |
||||
: type(type), |
||||
string_matcher(std::move(string_matcher)), |
||||
not_rule(not_rule) {} |
||||
Rbac::Permission::Permission(Permission::RuleType type, CidrRange ip, |
||||
bool not_rule) |
||||
: type(type), ip(std::move(ip)), not_rule(not_rule) {} |
||||
Rbac::Permission::Permission(Permission::RuleType type, int port, bool not_rule) |
||||
: type(type), port(port), not_rule(not_rule) {} |
||||
|
||||
Rbac::Permission::Permission(Rbac::Permission&& other) noexcept |
||||
: type(other.type), not_rule(other.not_rule) { |
||||
switch (type) { |
||||
case RuleType::AND: |
||||
case RuleType::OR: |
||||
permissions = std::move(other.permissions); |
||||
break; |
||||
case RuleType::ANY: |
||||
break; |
||||
case RuleType::HEADER: |
||||
header_matcher = std::move(other.header_matcher); |
||||
break; |
||||
case RuleType::PATH: |
||||
case RuleType::REQ_SERVER_NAME: |
||||
string_matcher = std::move(other.string_matcher); |
||||
break; |
||||
case RuleType::DEST_IP: |
||||
ip = std::move(other.ip); |
||||
break; |
||||
default: |
||||
port = other.port; |
||||
} |
||||
} |
||||
|
||||
Rbac::Permission& Rbac::Permission::operator=( |
||||
Rbac::Permission&& other) noexcept { |
||||
type = other.type; |
||||
not_rule = other.not_rule; |
||||
switch (type) { |
||||
case RuleType::AND: |
||||
case RuleType::OR: |
||||
permissions = std::move(other.permissions); |
||||
break; |
||||
case RuleType::ANY: |
||||
break; |
||||
case RuleType::HEADER: |
||||
header_matcher = std::move(other.header_matcher); |
||||
break; |
||||
case RuleType::PATH: |
||||
case RuleType::REQ_SERVER_NAME: |
||||
string_matcher = std::move(other.string_matcher); |
||||
break; |
||||
case RuleType::DEST_IP: |
||||
ip = std::move(other.ip); |
||||
break; |
||||
default: |
||||
port = other.port; |
||||
} |
||||
return *this; |
||||
} |
||||
|
||||
std::string Rbac::Permission::ToString() const { |
||||
switch (type) { |
||||
case RuleType::AND: { |
||||
std::vector<std::string> contents; |
||||
contents.reserve(permissions.size()); |
||||
for (const auto& permission : permissions) { |
||||
contents.push_back(permission->ToString()); |
||||
} |
||||
return absl::StrFormat("%sand=[%s]", not_rule ? "not " : "", |
||||
absl::StrJoin(contents, ",")); |
||||
} |
||||
case RuleType::OR: { |
||||
std::vector<std::string> contents; |
||||
contents.reserve(permissions.size()); |
||||
for (const auto& permission : permissions) { |
||||
contents.push_back(permission->ToString()); |
||||
} |
||||
return absl::StrFormat("%sor=[%s]", not_rule ? "not " : "", |
||||
absl::StrJoin(contents, ",")); |
||||
} |
||||
case RuleType::ANY: |
||||
return absl::StrFormat("%sany", not_rule ? "not " : ""); |
||||
case RuleType::HEADER: |
||||
return absl::StrFormat("%sheader=%s", not_rule ? "not " : "", |
||||
header_matcher.ToString()); |
||||
case RuleType::PATH: |
||||
return absl::StrFormat("%spath=%s", not_rule ? "not " : "", |
||||
string_matcher.ToString()); |
||||
case RuleType::DEST_IP: |
||||
return absl::StrFormat("%sdest_ip=%s", not_rule ? "not " : "", |
||||
ip.ToString()); |
||||
case RuleType::DEST_PORT: |
||||
return absl::StrFormat("%sdest_port=%d", not_rule ? "not " : "", port); |
||||
case RuleType::REQ_SERVER_NAME: |
||||
return absl::StrFormat("%srequested_server_name=%s", |
||||
not_rule ? "not " : "", string_matcher.ToString()); |
||||
default: |
||||
return ""; |
||||
} |
||||
} |
||||
|
||||
//
|
||||
// Principal
|
||||
//
|
||||
|
||||
Rbac::Principal::Principal(Principal::RuleType type, |
||||
std::vector<std::unique_ptr<Principal>> principals, |
||||
bool not_rule) |
||||
: type(type), principals(std::move(principals)), not_rule(not_rule) {} |
||||
Rbac::Principal::Principal(Principal::RuleType type, bool not_rule) |
||||
: type(type), not_rule(not_rule) {} |
||||
Rbac::Principal::Principal(Principal::RuleType type, |
||||
StringMatcher string_matcher, bool not_rule) |
||||
: type(type), |
||||
string_matcher(std::move(string_matcher)), |
||||
not_rule(not_rule) {} |
||||
Rbac::Principal::Principal(Principal::RuleType type, CidrRange ip, |
||||
bool not_rule) |
||||
: type(type), ip(std::move(ip)), not_rule(not_rule) {} |
||||
Rbac::Principal::Principal(Principal::RuleType type, |
||||
HeaderMatcher header_matcher, bool not_rule) |
||||
: type(type), |
||||
header_matcher(std::move(header_matcher)), |
||||
not_rule(not_rule) {} |
||||
|
||||
Rbac::Principal::Principal(Rbac::Principal&& other) noexcept |
||||
: type(other.type), not_rule(other.not_rule) { |
||||
switch (type) { |
||||
case RuleType::AND: |
||||
case RuleType::OR: |
||||
principals = std::move(other.principals); |
||||
break; |
||||
case RuleType::ANY: |
||||
break; |
||||
case RuleType::HEADER: |
||||
header_matcher = std::move(other.header_matcher); |
||||
break; |
||||
case RuleType::PRINCIPAL_NAME: |
||||
case RuleType::PATH: |
||||
string_matcher = std::move(other.string_matcher); |
||||
break; |
||||
default: |
||||
ip = std::move(other.ip); |
||||
} |
||||
} |
||||
|
||||
Rbac::Principal& Rbac::Principal::operator=(Rbac::Principal&& other) noexcept { |
||||
type = other.type; |
||||
not_rule = other.not_rule; |
||||
switch (type) { |
||||
case RuleType::AND: |
||||
case RuleType::OR: |
||||
principals = std::move(other.principals); |
||||
break; |
||||
case RuleType::ANY: |
||||
break; |
||||
case RuleType::HEADER: |
||||
header_matcher = std::move(other.header_matcher); |
||||
break; |
||||
case RuleType::PRINCIPAL_NAME: |
||||
case RuleType::PATH: |
||||
string_matcher = std::move(other.string_matcher); |
||||
break; |
||||
default: |
||||
ip = std::move(other.ip); |
||||
} |
||||
return *this; |
||||
} |
||||
|
||||
std::string Rbac::Principal::ToString() const { |
||||
switch (type) { |
||||
case RuleType::AND: { |
||||
std::vector<std::string> contents; |
||||
contents.reserve(principals.size()); |
||||
for (const auto& principal : principals) { |
||||
contents.push_back(principal->ToString()); |
||||
} |
||||
return absl::StrFormat("%sand=[%s]", not_rule ? "not " : "", |
||||
absl::StrJoin(contents, ",")); |
||||
} |
||||
case RuleType::OR: { |
||||
std::vector<std::string> contents; |
||||
contents.reserve(principals.size()); |
||||
for (const auto& principal : principals) { |
||||
contents.push_back(principal->ToString()); |
||||
} |
||||
return absl::StrFormat("%sor=[%s]", not_rule ? "not " : "", |
||||
absl::StrJoin(contents, ",")); |
||||
} |
||||
case RuleType::ANY: |
||||
return absl::StrFormat("%sany", not_rule ? "not " : ""); |
||||
case RuleType::PRINCIPAL_NAME: |
||||
return absl::StrFormat("%sprincipal_name=%s", not_rule ? "not " : "", |
||||
string_matcher.ToString()); |
||||
case RuleType::SOURCE_IP: |
||||
return absl::StrFormat("%ssource_ip=%s", not_rule ? "not " : "", |
||||
ip.ToString()); |
||||
case RuleType::DIRECT_REMOTE_IP: |
||||
return absl::StrFormat("%sdirect_remote_ip=%s", not_rule ? "not " : "", |
||||
ip.ToString()); |
||||
case RuleType::REMOTE_IP: |
||||
return absl::StrFormat("%sremote_ip=%s", not_rule ? "not " : "", |
||||
ip.ToString()); |
||||
case RuleType::HEADER: |
||||
return absl::StrFormat("%sheader=%s", not_rule ? "not " : "", |
||||
header_matcher.ToString()); |
||||
case RuleType::PATH: |
||||
return absl::StrFormat("%spath=%s", not_rule ? "not " : "", |
||||
string_matcher.ToString()); |
||||
default: |
||||
return ""; |
||||
} |
||||
} |
||||
|
||||
//
|
||||
// Policy
|
||||
//
|
||||
|
||||
Rbac::Policy::Policy(Permission permissions, Principal principals) |
||||
: permissions(std::move(permissions)), principals(std::move(principals)) {} |
||||
|
||||
Rbac::Policy::Policy(Rbac::Policy&& other) noexcept |
||||
: permissions(std::move(other.permissions)), |
||||
principals(std::move(other.principals)) {} |
||||
|
||||
Rbac::Policy& Rbac::Policy::operator=(Rbac::Policy&& other) noexcept { |
||||
permissions = std::move(other.permissions); |
||||
principals = std::move(other.principals); |
||||
return *this; |
||||
} |
||||
|
||||
std::string Rbac::Policy::ToString() const { |
||||
return absl::StrFormat( |
||||
" Policy {\n Permissions{%s}\n Principals{%s}\n }", |
||||
permissions.ToString(), principals.ToString()); |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,163 @@ |
||||
// Copyright 2021 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef GRPC_CORE_LIB_SECURITY_AUTHORIZATION_RBAC_POLICY_H |
||||
#define GRPC_CORE_LIB_SECURITY_AUTHORIZATION_RBAC_POLICY_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <memory> |
||||
|
||||
#include "src/core/lib/matchers/matchers.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
// Represents Envoy RBAC Proto. [See
|
||||
// https://github.com/envoyproxy/envoy/blob/release/v1.17/api/envoy/config/rbac/v3/rbac.proto]
|
||||
struct Rbac { |
||||
enum class Action { |
||||
ALLOW, |
||||
DENY, |
||||
}; |
||||
|
||||
struct CidrRange { |
||||
CidrRange() = default; |
||||
CidrRange(std::string address_prefix, uint32_t prefix_len); |
||||
|
||||
CidrRange(CidrRange&& other) noexcept; |
||||
CidrRange& operator=(CidrRange&& other) noexcept; |
||||
|
||||
std::string ToString() const; |
||||
|
||||
std::string address_prefix; |
||||
uint32_t prefix_len; |
||||
}; |
||||
|
||||
// TODO(ashithasantosh): Add metadata field to Permission and Principal.
|
||||
struct Permission { |
||||
enum class RuleType { |
||||
AND, |
||||
OR, |
||||
ANY, |
||||
HEADER, |
||||
PATH, |
||||
DEST_IP, |
||||
DEST_PORT, |
||||
REQ_SERVER_NAME, |
||||
}; |
||||
|
||||
Permission() = default; |
||||
// For AND/OR RuleType.
|
||||
Permission(Permission::RuleType type, |
||||
std::vector<std::unique_ptr<Permission>> permissions, |
||||
bool not_rule = false); |
||||
// For ANY RuleType.
|
||||
explicit Permission(Permission::RuleType type, bool not_rule = false); |
||||
// For HEADER RuleType.
|
||||
Permission(Permission::RuleType type, HeaderMatcher header_matcher, |
||||
bool not_rule = false); |
||||
// For PATH/REQ_SERVER_NAME RuleType.
|
||||
Permission(Permission::RuleType type, StringMatcher string_matcher, |
||||
bool not_rule = false); |
||||
// For DEST_IP RuleType.
|
||||
Permission(Permission::RuleType type, CidrRange ip, bool not_rule = false); |
||||
// For DEST_PORT RuleType.
|
||||
Permission(Permission::RuleType type, int port, bool not_rule = false); |
||||
|
||||
Permission(Permission&& other) noexcept; |
||||
Permission& operator=(Permission&& other) noexcept; |
||||
|
||||
std::string ToString() const; |
||||
|
||||
RuleType type; |
||||
HeaderMatcher header_matcher; |
||||
StringMatcher string_matcher; |
||||
CidrRange ip; |
||||
int port; |
||||
// For type AND/OR.
|
||||
std::vector<std::unique_ptr<Permission>> permissions; |
||||
bool not_rule = false; |
||||
}; |
||||
|
||||
struct Principal { |
||||
enum class RuleType { |
||||
AND, |
||||
OR, |
||||
ANY, |
||||
PRINCIPAL_NAME, |
||||
SOURCE_IP, |
||||
DIRECT_REMOTE_IP, |
||||
REMOTE_IP, |
||||
HEADER, |
||||
PATH, |
||||
}; |
||||
|
||||
Principal() = default; |
||||
// For AND/OR RuleType.
|
||||
Principal(Principal::RuleType type, |
||||
std::vector<std::unique_ptr<Principal>> principals, |
||||
bool not_rule = false); |
||||
// For ANY RuleType.
|
||||
explicit Principal(Principal::RuleType type, bool not_rule = false); |
||||
// For PRINCIPAL_NAME/PATH RuleType.
|
||||
Principal(Principal::RuleType type, StringMatcher string_matcher, |
||||
bool not_rule = false); |
||||
// For SOURCE_IP/DIRECT_REMOTE_IP/REMOTE_IP RuleType.
|
||||
Principal(Principal::RuleType type, CidrRange ip, bool not_rule = false); |
||||
// For HEADER RuleType.
|
||||
Principal(Principal::RuleType type, HeaderMatcher header_matcher, |
||||
bool not_rule = false); |
||||
|
||||
Principal(Principal&& other) noexcept; |
||||
Principal& operator=(Principal&& other) noexcept; |
||||
|
||||
std::string ToString() const; |
||||
|
||||
RuleType type; |
||||
HeaderMatcher header_matcher; |
||||
StringMatcher string_matcher; |
||||
CidrRange ip; |
||||
// For type AND/OR.
|
||||
std::vector<std::unique_ptr<Principal>> principals; |
||||
bool not_rule = false; |
||||
}; |
||||
|
||||
struct Policy { |
||||
Policy() = default; |
||||
Policy(Permission permissions, Principal principals); |
||||
|
||||
Policy(Policy&& other) noexcept; |
||||
Policy& operator=(Policy&& other) noexcept; |
||||
|
||||
std::string ToString() const; |
||||
|
||||
Permission permissions; |
||||
Principal principals; |
||||
}; |
||||
|
||||
Rbac() = default; |
||||
Rbac(Rbac::Action action, std::map<std::string, Policy> policies); |
||||
|
||||
Rbac(Rbac&& other) noexcept; |
||||
Rbac& operator=(Rbac&& other) noexcept; |
||||
|
||||
std::string ToString() const; |
||||
|
||||
Action action; |
||||
std::map<std::string, Policy> policies; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif /* GRPC_CORE_LIB_SECURITY_AUTHORIZATION_RBAC_POLICY_H */ |
@ -0,0 +1,354 @@ |
||||
// Copyright 2021 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/security/authorization/rbac_translator.h" |
||||
|
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/strings/str_format.h" |
||||
#include "absl/strings/strip.h" |
||||
|
||||
#include "src/core/lib/matchers/matchers.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
namespace { |
||||
|
||||
absl::string_view GetMatcherType(absl::string_view value, |
||||
StringMatcher::Type* type) { |
||||
if (value == "*") { |
||||
*type = StringMatcher::Type::PREFIX; |
||||
return ""; |
||||
} else if (absl::StartsWith(value, "*")) { |
||||
*type = StringMatcher::Type::SUFFIX; |
||||
return absl::StripPrefix(value, "*"); |
||||
} else if (absl::EndsWith(value, "*")) { |
||||
*type = StringMatcher::Type::PREFIX; |
||||
return absl::StripSuffix(value, "*"); |
||||
} |
||||
*type = StringMatcher::Type::EXACT; |
||||
return value; |
||||
} |
||||
|
||||
absl::StatusOr<StringMatcher> GetStringMatcher(absl::string_view value) { |
||||
StringMatcher::Type type; |
||||
absl::string_view matcher = GetMatcherType(value, &type); |
||||
return StringMatcher::Create(type, matcher); |
||||
} |
||||
|
||||
absl::StatusOr<HeaderMatcher> GetHeaderMatcher(absl::string_view name, |
||||
absl::string_view value) { |
||||
StringMatcher::Type type; |
||||
absl::string_view matcher = GetMatcherType(value, &type); |
||||
return HeaderMatcher::Create(name, static_cast<HeaderMatcher::Type>(type), |
||||
matcher); |
||||
} |
||||
|
||||
absl::StatusOr<Rbac::Principal> ParsePrincipalsArray(const Json& json) { |
||||
std::vector<std::unique_ptr<Rbac::Principal>> principal_names; |
||||
for (size_t i = 0; i < json.array_value().size(); ++i) { |
||||
const Json& child = json.array_value().at(i); |
||||
if (child.type() != Json::Type::STRING) { |
||||
return absl::InvalidArgumentError( |
||||
absl::StrCat("\"principals\" ", i, ": is not a string.")); |
||||
} |
||||
auto matcher_or = GetStringMatcher(child.string_value()); |
||||
if (!matcher_or.ok()) { |
||||
return absl::Status(matcher_or.status().code(), |
||||
absl::StrCat("\"principals\" ", i, ": ", |
||||
matcher_or.status().message())); |
||||
} |
||||
principal_names.push_back(absl::make_unique<Rbac::Principal>( |
||||
Rbac::Principal::RuleType::PRINCIPAL_NAME, |
||||
std::move(matcher_or.value()))); |
||||
} |
||||
return Rbac::Principal(Rbac::Principal::RuleType::OR, |
||||
std::move(principal_names)); |
||||
} |
||||
|
||||
absl::StatusOr<Rbac::Principal> ParsePeer(const Json& json) { |
||||
std::vector<std::unique_ptr<Rbac::Principal>> peer; |
||||
auto it = json.object_value().find("principals"); |
||||
if (it != json.object_value().end()) { |
||||
if (it->second.type() != Json::Type::ARRAY) { |
||||
return absl::InvalidArgumentError("\"principals\" is not an array."); |
||||
} |
||||
auto principal_names_or = ParsePrincipalsArray(it->second); |
||||
if (!principal_names_or.ok()) return principal_names_or.status(); |
||||
if (!principal_names_or.value().principals.empty()) { |
||||
peer.push_back(absl::make_unique<Rbac::Principal>( |
||||
std::move(principal_names_or.value()))); |
||||
} |
||||
} |
||||
if (peer.empty()) { |
||||
return Rbac::Principal(Rbac::Principal::RuleType::ANY); |
||||
} |
||||
return Rbac::Principal(Rbac::Principal::RuleType::AND, std::move(peer)); |
||||
} |
||||
|
||||
absl::StatusOr<Rbac::Permission> ParseHeaderValues( |
||||
const Json& json, absl::string_view header_name) { |
||||
if (json.array_value().empty()) { |
||||
return absl::InvalidArgumentError("\"values\" list is empty."); |
||||
} |
||||
std::vector<std::unique_ptr<Rbac::Permission>> values; |
||||
for (size_t i = 0; i < json.array_value().size(); ++i) { |
||||
const Json& child = json.array_value().at(i); |
||||
if (child.type() != Json::Type::STRING) { |
||||
return absl::InvalidArgumentError( |
||||
absl::StrCat("\"values\" ", i, ": is not a string.")); |
||||
} |
||||
auto matcher_or = GetHeaderMatcher(header_name, child.string_value()); |
||||
if (!matcher_or.ok()) { |
||||
return absl::Status( |
||||
matcher_or.status().code(), |
||||
absl::StrCat("\"values\" ", i, ": ", matcher_or.status().message())); |
||||
} |
||||
values.push_back(absl::make_unique<Rbac::Permission>( |
||||
Rbac::Permission::RuleType::HEADER, std::move(matcher_or.value()))); |
||||
} |
||||
return Rbac::Permission(Rbac::Permission::RuleType::OR, std::move(values)); |
||||
} |
||||
|
||||
absl::StatusOr<Rbac::Permission> ParseHeaders(const Json& json) { |
||||
auto it = json.object_value().find("key"); |
||||
if (it == json.object_value().end()) { |
||||
return absl::InvalidArgumentError("\"key\" is not present."); |
||||
} |
||||
if (it->second.type() != Json::Type::STRING) { |
||||
return absl::InvalidArgumentError("\"key\" is not a string."); |
||||
} |
||||
absl::string_view header_name = it->second.string_value(); |
||||
// TODO(ashithasantosh): Add connection headers below.
|
||||
if (absl::StartsWith(header_name, ":") || |
||||
absl::StartsWith(header_name, "grpc-") || header_name == "host" || |
||||
header_name == "Host") { |
||||
return absl::InvalidArgumentError( |
||||
absl::StrFormat("Unsupported \"key\" %s.", header_name)); |
||||
} |
||||
it = json.object_value().find("values"); |
||||
if (it == json.object_value().end()) { |
||||
return absl::InvalidArgumentError("\"values\" is not present."); |
||||
} |
||||
if (it->second.type() != Json::Type::ARRAY) { |
||||
return absl::InvalidArgumentError("\"values\" is not an array."); |
||||
} |
||||
return ParseHeaderValues(it->second, header_name); |
||||
} |
||||
|
||||
absl::StatusOr<Rbac::Permission> ParseHeadersArray(const Json& json) { |
||||
std::vector<std::unique_ptr<Rbac::Permission>> headers; |
||||
for (size_t i = 0; i < json.array_value().size(); ++i) { |
||||
const Json& child = json.array_value().at(i); |
||||
if (child.type() != Json::Type::OBJECT) { |
||||
return absl::InvalidArgumentError( |
||||
absl::StrCat("\"headers\" ", i, ": is not an object.")); |
||||
} |
||||
auto headers_or = ParseHeaders(child); |
||||
if (!headers_or.ok()) { |
||||
return absl::Status( |
||||
headers_or.status().code(), |
||||
absl::StrCat("\"headers\" ", i, ": ", headers_or.status().message())); |
||||
} |
||||
headers.push_back( |
||||
absl::make_unique<Rbac::Permission>(std::move(headers_or.value()))); |
||||
} |
||||
return Rbac::Permission(Rbac::Permission::RuleType::AND, std::move(headers)); |
||||
} |
||||
|
||||
absl::StatusOr<Rbac::Permission> ParsePathsArray(const Json& json) { |
||||
std::vector<std::unique_ptr<Rbac::Permission>> paths; |
||||
for (size_t i = 0; i < json.array_value().size(); ++i) { |
||||
const Json& child = json.array_value().at(i); |
||||
if (child.type() != Json::Type::STRING) { |
||||
return absl::InvalidArgumentError( |
||||
absl::StrCat("\"paths\" ", i, ": is not a string.")); |
||||
} |
||||
auto matcher_or = GetStringMatcher(child.string_value()); |
||||
if (!matcher_or.ok()) { |
||||
return absl::Status( |
||||
matcher_or.status().code(), |
||||
absl::StrCat("\"paths\" ", i, ": ", matcher_or.status().message())); |
||||
} |
||||
paths.push_back(absl::make_unique<Rbac::Permission>( |
||||
Rbac::Permission::RuleType::PATH, std::move(matcher_or.value()))); |
||||
} |
||||
return Rbac::Permission(Rbac::Permission::RuleType::OR, std::move(paths)); |
||||
} |
||||
|
||||
absl::StatusOr<Rbac::Permission> ParseRequest(const Json& json) { |
||||
std::vector<std::unique_ptr<Rbac::Permission>> request; |
||||
auto it = json.object_value().find("paths"); |
||||
if (it != json.object_value().end()) { |
||||
if (it->second.type() != Json::Type::ARRAY) { |
||||
return absl::InvalidArgumentError("\"paths\" is not an array."); |
||||
} |
||||
auto paths_or = ParsePathsArray(it->second); |
||||
if (!paths_or.ok()) return paths_or.status(); |
||||
if (!paths_or.value().permissions.empty()) { |
||||
request.push_back( |
||||
absl::make_unique<Rbac::Permission>(std::move(paths_or.value()))); |
||||
} |
||||
} |
||||
it = json.object_value().find("headers"); |
||||
if (it != json.object_value().end()) { |
||||
if (it->second.type() != Json::Type::ARRAY) { |
||||
return absl::InvalidArgumentError("\"headers\" is not an array."); |
||||
} |
||||
auto headers_or = ParseHeadersArray(it->second); |
||||
if (!headers_or.ok()) return headers_or.status(); |
||||
if (!headers_or.value().permissions.empty()) { |
||||
request.push_back( |
||||
absl::make_unique<Rbac::Permission>(std::move(headers_or.value()))); |
||||
} |
||||
} |
||||
if (request.empty()) { |
||||
return Rbac::Permission(Rbac::Permission::RuleType::ANY); |
||||
} |
||||
return Rbac::Permission(Rbac::Permission::RuleType::AND, std::move(request)); |
||||
} |
||||
|
||||
absl::StatusOr<Rbac::Policy> ParseRules(const Json& json) { |
||||
Rbac::Principal principals; |
||||
auto it = json.object_value().find("source"); |
||||
if (it != json.object_value().end()) { |
||||
if (it->second.type() != Json::Type::OBJECT) { |
||||
return absl::InvalidArgumentError("\"source\" is not an object."); |
||||
} |
||||
auto peer_or = ParsePeer(it->second); |
||||
if (!peer_or.ok()) return peer_or.status(); |
||||
principals = std::move(peer_or.value()); |
||||
} else { |
||||
principals = Rbac::Principal(Rbac::Principal::RuleType::ANY); |
||||
} |
||||
Rbac::Permission permissions; |
||||
it = json.object_value().find("request"); |
||||
if (it != json.object_value().end()) { |
||||
if (it->second.type() != Json::Type::OBJECT) { |
||||
return absl::InvalidArgumentError("\"request\" is not an object."); |
||||
} |
||||
auto request_or = ParseRequest(it->second); |
||||
if (!request_or.ok()) return request_or.status(); |
||||
permissions = std::move(request_or.value()); |
||||
} else { |
||||
permissions = Rbac::Permission(Rbac::Permission::RuleType::ANY); |
||||
} |
||||
return Rbac::Policy(std::move(permissions), std::move(principals)); |
||||
} |
||||
|
||||
absl::StatusOr<std::map<std::string, Rbac::Policy>> ParseRulesArray( |
||||
const Json& json, absl::string_view name) { |
||||
std::map<std::string, Rbac::Policy> policies; |
||||
for (size_t i = 0; i < json.array_value().size(); ++i) { |
||||
const Json& child = json.array_value().at(i); |
||||
if (child.type() != Json::Type::OBJECT) { |
||||
return absl::InvalidArgumentError( |
||||
absl::StrCat("rules ", i, ": is not an object.")); |
||||
} |
||||
auto it = child.object_value().find("name"); |
||||
if (it == child.object_value().end()) { |
||||
return absl::InvalidArgumentError( |
||||
absl::StrCat("rules ", i, ": \"name\" is not present.")); |
||||
} |
||||
if (it->second.type() != Json::Type::STRING) { |
||||
return absl::InvalidArgumentError( |
||||
absl::StrCat("rules ", i, ": \"name\" is not a string.")); |
||||
} |
||||
std::string policy_name = |
||||
std::string(name) + "_" + it->second.string_value(); |
||||
auto policy_or = ParseRules(child); |
||||
if (!policy_or.ok()) { |
||||
return absl::Status( |
||||
policy_or.status().code(), |
||||
absl::StrCat("rules ", i, ": ", policy_or.status().message())); |
||||
} |
||||
policies[policy_name] = std::move(policy_or.value()); |
||||
} |
||||
return std::move(policies); |
||||
} |
||||
|
||||
absl::StatusOr<Rbac> ParseDenyRulesArray(const Json& json, |
||||
absl::string_view name) { |
||||
auto policies_or = ParseRulesArray(json, name); |
||||
if (!policies_or.ok()) return policies_or.status(); |
||||
return Rbac(Rbac::Action::DENY, std::move(policies_or.value())); |
||||
} |
||||
|
||||
absl::StatusOr<Rbac> ParseAllowRulesArray(const Json& json, |
||||
absl::string_view name) { |
||||
auto policies_or = ParseRulesArray(json, name); |
||||
if (!policies_or.ok()) return policies_or.status(); |
||||
return Rbac(Rbac::Action::ALLOW, std::move(policies_or.value())); |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
absl::StatusOr<RbacPolicies> GenerateRbacPolicies( |
||||
absl::string_view authz_policy) { |
||||
grpc_error* error = GRPC_ERROR_NONE; |
||||
Json json = Json::Parse(authz_policy, &error); |
||||
if (error != GRPC_ERROR_NONE) { |
||||
absl::Status status = absl::InvalidArgumentError( |
||||
absl::StrCat("Failed to parse SDK authorization policy. Error: ", |
||||
grpc_error_string(error))); |
||||
GRPC_ERROR_UNREF(error); |
||||
return status; |
||||
} |
||||
if (json.type() != Json::Type::OBJECT) { |
||||
return absl::InvalidArgumentError( |
||||
"SDK authorization policy is not an object."); |
||||
} |
||||
auto it = json.mutable_object()->find("name"); |
||||
if (it == json.mutable_object()->end()) { |
||||
return absl::InvalidArgumentError("\"name\" field is not present."); |
||||
} |
||||
if (it->second.type() != Json::Type::STRING) { |
||||
return absl::InvalidArgumentError("\"name\" is not a string."); |
||||
} |
||||
absl::string_view name = it->second.string_value(); |
||||
RbacPolicies rbac_policies; |
||||
it = json.mutable_object()->find("deny_rules"); |
||||
if (it != json.mutable_object()->end()) { |
||||
if (it->second.type() != Json::Type::ARRAY) { |
||||
return absl::InvalidArgumentError("\"deny_rules\" is not an array."); |
||||
} |
||||
auto deny_policy_or = ParseDenyRulesArray(it->second, name); |
||||
if (!deny_policy_or.ok()) { |
||||
return absl::Status( |
||||
deny_policy_or.status().code(), |
||||
absl::StrCat("deny_", deny_policy_or.status().message())); |
||||
} |
||||
rbac_policies.deny_policy = std::move(deny_policy_or.value()); |
||||
} else { |
||||
rbac_policies.deny_policy.action = Rbac::Action::DENY; |
||||
} |
||||
it = json.mutable_object()->find("allow_rules"); |
||||
if (it == json.mutable_object()->end()) { |
||||
return absl::InvalidArgumentError("\"allow_rules\" is not present."); |
||||
} |
||||
if (it->second.type() != Json::Type::ARRAY) { |
||||
return absl::InvalidArgumentError("\"allow_rules\" is not an array."); |
||||
} |
||||
auto allow_policy_or = ParseAllowRulesArray(it->second, name); |
||||
if (!allow_policy_or.ok()) { |
||||
return absl::Status( |
||||
allow_policy_or.status().code(), |
||||
absl::StrCat("allow_", allow_policy_or.status().message())); |
||||
} |
||||
rbac_policies.allow_policy = std::move(allow_policy_or.value()); |
||||
return std::move(rbac_policies); |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,39 @@ |
||||
// Copyright 2021 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef GRPC_CORE_LIB_SECURITY_AUTHORIZATION_RBAC_TRANSLATOR_H |
||||
#define GRPC_CORE_LIB_SECURITY_AUTHORIZATION_RBAC_TRANSLATOR_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "absl/status/statusor.h" |
||||
#include "src/core/lib/json/json.h" |
||||
#include "src/core/lib/security/authorization/rbac_policy.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
struct RbacPolicies { |
||||
Rbac deny_policy; |
||||
Rbac allow_policy; |
||||
}; |
||||
|
||||
// Translates SDK authorization policy to Envoy RBAC policies. Returns error on
|
||||
// failure.
|
||||
// authz_policy: Authorization Policy string in JSON format.
|
||||
absl::StatusOr<RbacPolicies> GenerateRbacPolicies( |
||||
absl::string_view authz_policy); |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif /* GRPC_CORE_LIB_SECURITY_AUTHORIZATION_RBAC_TRANSLATOR_H */ |
@ -0,0 +1,804 @@ |
||||
// Copyright 2021 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/core/lib/security/authorization/rbac_translator.h" |
||||
|
||||
#include <gmock/gmock.h> |
||||
#include <gtest/gtest.h> |
||||
|
||||
namespace grpc_core { |
||||
|
||||
namespace { |
||||
|
||||
MATCHER_P2(EqualsPrincipalName, expected_matcher_type, expected_matcher_value, |
||||
"") { |
||||
return arg->type == Rbac::Principal::RuleType::PRINCIPAL_NAME && |
||||
arg->string_matcher.type() == expected_matcher_type && |
||||
arg->string_matcher.string_matcher() == expected_matcher_value; |
||||
} |
||||
|
||||
MATCHER_P2(EqualsPath, expected_matcher_type, expected_matcher_value, "") { |
||||
return arg->type == Rbac::Permission::RuleType::PATH && |
||||
arg->string_matcher.type() == expected_matcher_type && |
||||
arg->string_matcher.string_matcher() == expected_matcher_value; |
||||
} |
||||
|
||||
MATCHER_P3(EqualsHeader, expected_name, expected_matcher_type, |
||||
expected_matcher_value, "") { |
||||
return arg->type == Rbac::Permission::RuleType::HEADER && |
||||
arg->header_matcher.name() == expected_name && |
||||
arg->header_matcher.type() == expected_matcher_type && |
||||
arg->header_matcher.string_matcher() == expected_matcher_value; |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
TEST(GenerateRbacPoliciesTest, InvalidPolicy) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz-policy\",," |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_THAT( |
||||
std::string(rbac_policies.status().message()), |
||||
::testing::StartsWith("Failed to parse SDK authorization policy.")); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, MissingAuthorizationPolicyName) { |
||||
const char* authz_policy = "{}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(rbac_policies.status().message(), "\"name\" field is not present."); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, IncorrectAuthorizationPolicyNameType) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": [\"authz_policy\"]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(rbac_policies.status().message(), "\"name\" is not a string."); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, MissingAllowRules) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz_policy\"" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(rbac_policies.status().message(), |
||||
"\"allow_rules\" is not present."); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, MissingDenyRules) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": \"allow_policy\"" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
ASSERT_TRUE(rbac_policies.ok()); |
||||
EXPECT_EQ(rbac_policies.value().deny_policy.action, Rbac::Action::DENY); |
||||
EXPECT_TRUE(rbac_policies.value().deny_policy.policies.empty()); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, IncorrectAllowRulesType) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": {}" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(rbac_policies.status().message(), |
||||
"\"allow_rules\" is not an array."); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, IncorrectDenyRulesType) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"deny_rules\": 123" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(rbac_policies.status().message(), |
||||
"\"deny_rules\" is not an array."); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, IncorrectRuleType) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [\"rule-a\"]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(rbac_policies.status().message(), |
||||
"allow_rules 0: is not an object."); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, MissingRuleNameField) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [{}]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(rbac_policies.status().message(), |
||||
"allow_rules 0: \"name\" is not present."); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, IncorrectRuleNameType) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": 123" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(rbac_policies.status().message(), |
||||
"allow_rules 0: \"name\" is not a string."); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, MissingSourceAndRequest) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": \"allow_policy\"" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
ASSERT_TRUE(rbac_policies.ok()); |
||||
EXPECT_EQ(rbac_policies.value().allow_policy.action, Rbac::Action::ALLOW); |
||||
EXPECT_THAT(rbac_policies.value().allow_policy.policies, |
||||
::testing::ElementsAre(::testing::Pair( |
||||
"authz_allow_policy", |
||||
::testing::AllOf( |
||||
::testing::Field( |
||||
&Rbac::Policy::permissions, |
||||
::testing::Field(&Rbac::Permission::type, |
||||
Rbac::Permission::RuleType::ANY)), |
||||
::testing::Field( |
||||
&Rbac::Policy::principals, |
||||
::testing::Field(&Rbac::Principal::type, |
||||
Rbac::Principal::RuleType::ANY)))))); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, EmptySourceAndRequest) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": \"allow_policy\"," |
||||
" \"source\": {}," |
||||
" \"request\": {}" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
ASSERT_TRUE(rbac_policies.ok()); |
||||
EXPECT_EQ(rbac_policies.value().allow_policy.action, Rbac::Action::ALLOW); |
||||
EXPECT_THAT(rbac_policies.value().allow_policy.policies, |
||||
::testing::ElementsAre(::testing::Pair( |
||||
"authz_allow_policy", |
||||
::testing::AllOf( |
||||
::testing::Field( |
||||
&Rbac::Policy::permissions, |
||||
::testing::Field(&Rbac::Permission::type, |
||||
Rbac::Permission::RuleType::ANY)), |
||||
::testing::Field( |
||||
&Rbac::Policy::principals, |
||||
::testing::Field(&Rbac::Principal::type, |
||||
Rbac::Principal::RuleType::ANY)))))); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, IncorrectSourceType) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": \"allow_policy\"," |
||||
" \"source\": 111" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(rbac_policies.status().message(), |
||||
"allow_rules 0: \"source\" is not an object."); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, IncorrectPrincipalsType) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": \"allow_policy\"," |
||||
" \"source\": {" |
||||
" \"principals\": [" |
||||
" \"*\"," |
||||
" 123" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(rbac_policies.status().message(), |
||||
"allow_rules 0: \"principals\" 1: is not a string."); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, ParseSourceSuccess) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": \"allow_policy\"," |
||||
" \"source\": {" |
||||
" \"principals\": [" |
||||
" \"spiffe://foo.abc\"," |
||||
" \"spiffe://bar*\"," |
||||
" \"*baz\"," |
||||
" \"spiffe://abc.*.com\"" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]," |
||||
" \"deny_rules\": [" |
||||
" {" |
||||
" \"name\": \"deny_policy\"," |
||||
" \"source\": {" |
||||
" \"principals\": [" |
||||
" \"*\"" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
ASSERT_TRUE(rbac_policies.ok()); |
||||
EXPECT_EQ(rbac_policies.value().allow_policy.action, Rbac::Action::ALLOW); |
||||
EXPECT_THAT( |
||||
rbac_policies.value().allow_policy.policies, |
||||
::testing::ElementsAre(::testing::Pair( |
||||
"authz_allow_policy", |
||||
::testing::AllOf( |
||||
::testing::Field( |
||||
&Rbac::Policy::permissions, |
||||
::testing::Field(&Rbac::Permission::type, |
||||
Rbac::Permission::RuleType::ANY)), |
||||
::testing::Field( |
||||
&Rbac::Policy::principals, |
||||
::testing::AllOf( |
||||
::testing::Field(&Rbac::Principal::type, |
||||
Rbac::Principal::RuleType::AND), |
||||
::testing::Field( |
||||
&Rbac::Principal::principals, |
||||
::testing::ElementsAre(::testing::AllOf( |
||||
::testing::Pointee(::testing::Field( |
||||
&Rbac::Principal::type, |
||||
Rbac::Principal::RuleType::OR)), |
||||
::testing::Pointee(::testing::Field( |
||||
&Rbac::Principal::principals, |
||||
::testing::ElementsAre( |
||||
EqualsPrincipalName( |
||||
StringMatcher::Type::EXACT, |
||||
"spiffe://foo.abc"), |
||||
EqualsPrincipalName( |
||||
StringMatcher::Type::PREFIX, |
||||
"spiffe://bar"), |
||||
EqualsPrincipalName( |
||||
StringMatcher::Type::SUFFIX, "baz"), |
||||
EqualsPrincipalName( |
||||
StringMatcher::Type::EXACT, |
||||
"spiffe://abc.*.com"))))))))))))); |
||||
EXPECT_EQ(rbac_policies.value().deny_policy.action, Rbac::Action::DENY); |
||||
EXPECT_THAT( |
||||
rbac_policies.value().deny_policy.policies, |
||||
::testing::ElementsAre(::testing::Pair( |
||||
"authz_deny_policy", |
||||
::testing::AllOf( |
||||
::testing::Field( |
||||
&Rbac::Policy::permissions, |
||||
::testing::Field(&Rbac::Permission::type, |
||||
Rbac::Permission::RuleType::ANY)), |
||||
::testing::Field( |
||||
&Rbac::Policy::principals, |
||||
::testing::AllOf( |
||||
::testing::Field(&Rbac::Principal::type, |
||||
Rbac::Principal::RuleType::AND), |
||||
::testing::Field( |
||||
&Rbac::Principal::principals, |
||||
::testing::ElementsAre(::testing::AllOf( |
||||
::testing::Pointee(::testing::Field( |
||||
&Rbac::Principal::type, |
||||
Rbac::Principal::RuleType::OR)), |
||||
::testing::Pointee(::testing::Field( |
||||
&Rbac::Principal::principals, |
||||
::testing::ElementsAre(EqualsPrincipalName( |
||||
StringMatcher::Type::PREFIX, |
||||
""))))))))))))); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, IncorrectRequestType) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"deny_rules\": [" |
||||
" {" |
||||
" \"name\": \"deny_policy\"," |
||||
" \"request\": 111" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(rbac_policies.status().message(), |
||||
"deny_rules 0: \"request\" is not an object."); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, IncorrectPathType) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"deny_rules\": [" |
||||
" {" |
||||
" \"name\": \"allow_policy\"," |
||||
" \"request\": {" |
||||
" \"paths\": [" |
||||
" \"path-a\"," |
||||
" 123" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(rbac_policies.status().message(), |
||||
"deny_rules 0: \"paths\" 1: is not a string."); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, ParseRequestPathsSuccess) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": \"allow_policy\"," |
||||
" \"request\": {" |
||||
" \"paths\": [" |
||||
" \"*\"" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]," |
||||
" \"deny_rules\": [" |
||||
" {" |
||||
" \"name\": \"deny_policy\"," |
||||
" \"request\": {" |
||||
" \"paths\": [" |
||||
" \"path-foo\"," |
||||
" \"path-bar*\"," |
||||
" \"*baz\"" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
ASSERT_TRUE(rbac_policies.ok()); |
||||
EXPECT_EQ(rbac_policies.value().deny_policy.action, Rbac::Action::DENY); |
||||
EXPECT_THAT( |
||||
rbac_policies.value().deny_policy.policies, |
||||
::testing::ElementsAre(::testing::Pair( |
||||
"authz_deny_policy", |
||||
::testing::AllOf( |
||||
::testing::Field( |
||||
&Rbac::Policy::principals, |
||||
::testing::Field(&Rbac::Principal::type, |
||||
Rbac::Principal::RuleType::ANY)), |
||||
::testing::Field( |
||||
&Rbac::Policy::permissions, |
||||
::testing::AllOf( |
||||
::testing::Field(&Rbac::Permission::type, |
||||
Rbac::Permission::RuleType::AND), |
||||
::testing::Field( |
||||
&Rbac::Permission::permissions, |
||||
::testing::ElementsAre(::testing::AllOf( |
||||
::testing::Pointee(::testing::Field( |
||||
&Rbac::Permission::type, |
||||
Rbac::Permission::RuleType::OR)), |
||||
::testing::Pointee(::testing::Field( |
||||
&Rbac::Permission::permissions, |
||||
::testing::ElementsAre( |
||||
EqualsPath(StringMatcher::Type::EXACT, |
||||
"path-foo"), |
||||
EqualsPath(StringMatcher::Type::PREFIX, |
||||
"path-bar"), |
||||
EqualsPath(StringMatcher::Type::SUFFIX, |
||||
"baz"))))))))))))); |
||||
EXPECT_EQ(rbac_policies.value().allow_policy.action, Rbac::Action::ALLOW); |
||||
EXPECT_THAT(rbac_policies.value().allow_policy.policies, |
||||
::testing::ElementsAre(::testing::Pair( |
||||
"authz_allow_policy", |
||||
::testing::AllOf( |
||||
::testing::Field( |
||||
&Rbac::Policy::principals, |
||||
::testing::Field(&Rbac::Principal::type, |
||||
Rbac::Principal::RuleType::ANY)), |
||||
::testing::Field( |
||||
&Rbac::Policy::permissions, |
||||
::testing::AllOf( |
||||
::testing::Field(&Rbac::Permission::type, |
||||
Rbac::Permission::RuleType::AND), |
||||
::testing::Field( |
||||
&Rbac::Permission::permissions, |
||||
::testing::ElementsAre(::testing::AllOf( |
||||
::testing::Pointee(::testing::Field( |
||||
&Rbac::Permission::type, |
||||
Rbac::Permission::RuleType::OR)), |
||||
::testing::Pointee(::testing::Field( |
||||
&Rbac::Permission::permissions, |
||||
::testing::ElementsAre(EqualsPath( |
||||
StringMatcher::Type::PREFIX, |
||||
""))))))))))))); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, IncorrectHeaderType) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"deny_rules\": [" |
||||
" {" |
||||
" \"name\": \"allow_policy\"," |
||||
" \"request\": {" |
||||
" \"headers\": [" |
||||
" \"header-a\"" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(rbac_policies.status().message(), |
||||
"deny_rules 0: \"headers\" 0: is not an object."); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, UnsupportedGrpcHeaders) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"deny_rules\": [" |
||||
" {" |
||||
" \"name\": \"policy\"," |
||||
" \"request\": {" |
||||
" \"headers\": [" |
||||
" {" |
||||
" \"key\": \"grpc-xxx\"," |
||||
" \"values\": [" |
||||
" \"*\"" |
||||
" ]" |
||||
" }" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(rbac_policies.status().message(), |
||||
"deny_rules 0: \"headers\" 0: Unsupported \"key\" grpc-xxx."); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, UnsupportedPseudoHeaders) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": \"policy\"," |
||||
" \"request\": {" |
||||
" \"headers\": [" |
||||
" {" |
||||
" \"key\": \":method\"," |
||||
" \"values\": [" |
||||
" \"*\"" |
||||
" ]" |
||||
" }" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(rbac_policies.status().message(), |
||||
"allow_rules 0: \"headers\" 0: Unsupported \"key\" :method."); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, UnsupportedhostHeader) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"deny_rules\": [" |
||||
" {" |
||||
" \"name\": \"policy\"," |
||||
" \"request\": {" |
||||
" \"headers\": [" |
||||
" {" |
||||
" \"key\": \"host\"," |
||||
" \"values\": [" |
||||
" \"*\"" |
||||
" ]" |
||||
" }" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(rbac_policies.status().message(), |
||||
"deny_rules 0: \"headers\" 0: Unsupported \"key\" host."); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, UnsupportedHostHeader) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": \"policy\"," |
||||
" \"request\": {" |
||||
" \"headers\": [" |
||||
" {" |
||||
" \"key\": \"Host\"," |
||||
" \"values\": [" |
||||
" \"*\"" |
||||
" ]" |
||||
" }" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(rbac_policies.status().message(), |
||||
"allow_rules 0: \"headers\" 0: Unsupported \"key\" Host."); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, EmptyHeaderValuesList) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": \"allow_policy_1\"," |
||||
" \"request\": {" |
||||
" \"headers\": [" |
||||
" {" |
||||
" \"key\": \"key-a\"," |
||||
" \"values\": [" |
||||
" ]" |
||||
" }" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(rbac_policies.status().message(), |
||||
"allow_rules 0: \"headers\" 0: \"values\" list is empty."); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, ParseRequestHeadersSuccess) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": \"allow_policy\"," |
||||
" \"request\": {" |
||||
" \"headers\": [" |
||||
" {" |
||||
" \"key\": \"key-1\"," |
||||
" \"values\": [" |
||||
" \"*\"" |
||||
" ]" |
||||
" }," |
||||
" {" |
||||
" \"key\": \"key-2\"," |
||||
" \"values\": [" |
||||
" \"foo\"," |
||||
" \"bar*\"," |
||||
" \"*baz\"" |
||||
" ]" |
||||
" }" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
ASSERT_TRUE(rbac_policies.ok()); |
||||
EXPECT_EQ(rbac_policies.value().deny_policy.action, Rbac::Action::DENY); |
||||
EXPECT_TRUE(rbac_policies.value().deny_policy.policies.empty()); |
||||
EXPECT_EQ(rbac_policies.value().allow_policy.action, Rbac::Action::ALLOW); |
||||
EXPECT_THAT( |
||||
rbac_policies.value().allow_policy.policies, |
||||
::testing::ElementsAre(::testing::Pair( |
||||
"authz_allow_policy", |
||||
::testing::AllOf( |
||||
::testing::Field( |
||||
&Rbac::Policy::principals, |
||||
::testing::Field(&Rbac::Principal::type, |
||||
Rbac::Principal::RuleType::ANY)), |
||||
::testing::Field( |
||||
&Rbac::Policy::permissions, |
||||
::testing::AllOf( |
||||
::testing::Field(&Rbac::Permission::type, |
||||
Rbac::Permission::RuleType::AND), |
||||
::testing::Field( |
||||
&Rbac::Permission::permissions, |
||||
::testing::ElementsAre(::testing::AllOf( |
||||
::testing::Pointee(::testing::Field( |
||||
&Rbac::Permission::type, |
||||
Rbac::Permission::RuleType::AND)), |
||||
::testing::Pointee(::testing::Field( |
||||
&Rbac::Permission::permissions, |
||||
::testing::ElementsAre( |
||||
::testing::AllOf( |
||||
::testing::Pointee(::testing::Field( |
||||
&Rbac::Permission::type, |
||||
Rbac::Permission::RuleType::OR)), |
||||
::testing::Pointee(::testing::Field( |
||||
&Rbac::Permission::permissions, |
||||
::testing::ElementsAre( |
||||
EqualsHeader("key-1", |
||||
HeaderMatcher:: |
||||
Type::PREFIX, |
||||
""))))), |
||||
::testing::AllOf( |
||||
::testing::Pointee(::testing::Field( |
||||
&Rbac::Permission::type, |
||||
Rbac::Permission::RuleType::OR)), |
||||
::testing::Pointee(::testing::Field( |
||||
&Rbac::Permission::permissions, |
||||
::testing::ElementsAre( |
||||
EqualsHeader("key-2", |
||||
HeaderMatcher:: |
||||
Type::EXACT, |
||||
"foo"), |
||||
EqualsHeader("key-2", |
||||
HeaderMatcher:: |
||||
Type::PREFIX, |
||||
"bar"), |
||||
EqualsHeader( |
||||
"key-2", |
||||
HeaderMatcher::Type:: |
||||
SUFFIX, |
||||
"baz"))))))))))))))))); |
||||
} |
||||
|
||||
TEST(GenerateRbacPoliciesTest, ParseRulesArraySuccess) { |
||||
const char* authz_policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": \"allow_policy_1\"," |
||||
" \"source\": {" |
||||
" \"principals\": [" |
||||
" \"spiffe://foo.abc\"" |
||||
" ]" |
||||
" }," |
||||
" \"request\": {" |
||||
" \"paths\": [" |
||||
" \"foo\"" |
||||
" ]" |
||||
" }" |
||||
" }," |
||||
" {" |
||||
" \"name\": \"allow_policy_2\"" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
auto rbac_policies = GenerateRbacPolicies(authz_policy); |
||||
ASSERT_TRUE(rbac_policies.ok()); |
||||
EXPECT_EQ(rbac_policies.value().deny_policy.action, Rbac::Action::DENY); |
||||
EXPECT_TRUE(rbac_policies.value().deny_policy.policies.empty()); |
||||
EXPECT_EQ(rbac_policies.value().allow_policy.action, Rbac::Action::ALLOW); |
||||
EXPECT_THAT( |
||||
rbac_policies.value().allow_policy.policies, |
||||
::testing::ElementsAre( |
||||
::testing::Pair( |
||||
"authz_allow_policy_1", |
||||
::testing::AllOf( |
||||
::testing::Field( |
||||
&Rbac::Policy::permissions, |
||||
::testing::AllOf( |
||||
::testing::Field(&Rbac::Permission::type, |
||||
Rbac::Permission::RuleType::AND), |
||||
::testing::Field( |
||||
&Rbac::Permission::permissions, |
||||
::testing::ElementsAre(::testing::AllOf( |
||||
::testing::Pointee(::testing::Field( |
||||
&Rbac::Permission::type, |
||||
Rbac::Permission::RuleType::OR)), |
||||
::testing::Pointee(::testing::Field( |
||||
&Rbac::Permission::permissions, |
||||
::testing::ElementsAre( |
||||
EqualsPath(StringMatcher::Type::EXACT, |
||||
"foo"))))))))), |
||||
::testing::Field( |
||||
&Rbac::Policy::principals, |
||||
::testing::AllOf( |
||||
::testing::Field(&Rbac::Principal::type, |
||||
Rbac::Principal::RuleType::AND), |
||||
::testing::Field( |
||||
&Rbac::Principal::principals, |
||||
::testing::ElementsAre(::testing::AllOf( |
||||
::testing::Pointee(::testing::Field( |
||||
&Rbac::Principal::type, |
||||
Rbac::Principal::RuleType::OR)), |
||||
::testing::Pointee(::testing::Field( |
||||
&Rbac::Principal::principals, |
||||
::testing::ElementsAre( |
||||
EqualsPrincipalName( |
||||
StringMatcher::Type::EXACT, |
||||
"spiffe://foo.abc"))))))))))), |
||||
::testing::Pair( |
||||
"authz_allow_policy_2", |
||||
::testing::AllOf( |
||||
::testing::Field( |
||||
&Rbac::Policy::permissions, |
||||
::testing::Field(&Rbac::Permission::type, |
||||
Rbac::Permission::RuleType::ANY)), |
||||
::testing::Field( |
||||
&Rbac::Policy::principals, |
||||
::testing::Field(&Rbac::Principal::type, |
||||
Rbac::Principal::RuleType::ANY)))))); |
||||
} |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
return RUN_ALL_TESTS(); |
||||
} |
Loading…
Reference in new issue