diff options
Diffstat (limited to 'grpc/src/core/lib/security/authorization/rbac_translator.cc')
-rw-r--r-- | grpc/src/core/lib/security/authorization/rbac_translator.cc | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/grpc/src/core/lib/security/authorization/rbac_translator.cc b/grpc/src/core/lib/security/authorization/rbac_translator.cc new file mode 100644 index 00000000..ea5599dd --- /dev/null +++ b/grpc/src/core/lib/security/authorization/rbac_translator.cc @@ -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::kPrefix; + return ""; + } else if (absl::StartsWith(value, "*")) { + *type = StringMatcher::Type::kSuffix; + return absl::StripPrefix(value, "*"); + } else if (absl::EndsWith(value, "*")) { + *type = StringMatcher::Type::kPrefix; + return absl::StripSuffix(value, "*"); + } + *type = StringMatcher::Type::kExact; + 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::kPrincipalName, + std::move(matcher_or.value()))); + } + return Rbac::Principal(Rbac::Principal::RuleType::kOr, + 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::kAny); + } + return Rbac::Principal(Rbac::Principal::RuleType::kAnd, 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::kHeader, std::move(matcher_or.value()))); + } + return Rbac::Permission(Rbac::Permission::RuleType::kOr, 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::kAnd, 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::kPath, std::move(matcher_or.value()))); + } + return Rbac::Permission(Rbac::Permission::RuleType::kOr, 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::kAny); + } + return Rbac::Permission(Rbac::Permission::RuleType::kAnd, 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::kAny); + } + 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::kAny); + } + 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::kDeny, 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::kAllow, std::move(policies_or.value())); +} + +} // namespace + +absl::StatusOr<RbacPolicies> GenerateRbacPolicies( + absl::string_view authz_policy) { + grpc_error_handle 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_std_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::kDeny; + } + 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 |