summaryrefslogtreecommitdiff
path: root/grpc/src/core/lib/security/authorization/rbac_translator.cc
diff options
context:
space:
mode:
Diffstat (limited to 'grpc/src/core/lib/security/authorization/rbac_translator.cc')
-rw-r--r--grpc/src/core/lib/security/authorization/rbac_translator.cc354
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