| // 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 |