• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 gRPC authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "src/core/lib/security/authorization/rbac_translator.h"
16 
17 #include <grpc/grpc_audit_logging.h>
18 #include <grpc/support/json.h>
19 #include <grpc/support/port_platform.h>
20 #include <stddef.h>
21 
22 #include <algorithm>
23 #include <map>
24 #include <memory>
25 #include <string>
26 #include <utility>
27 #include <vector>
28 
29 #include "absl/log/check.h"
30 #include "absl/status/status.h"
31 #include "absl/status/statusor.h"
32 #include "absl/strings/match.h"
33 #include "absl/strings/str_cat.h"
34 #include "absl/strings/str_format.h"
35 #include "absl/strings/string_view.h"
36 #include "absl/strings/strip.h"
37 #include "src/core/lib/security/authorization/audit_logging.h"
38 #include "src/core/util/json/json.h"
39 #include "src/core/util/json/json_reader.h"
40 #include "src/core/util/matchers.h"
41 #include "src/core/util/useful.h"
42 
43 namespace grpc_core {
44 
45 namespace {
46 
47 using experimental::AuditLoggerRegistry;
48 
GetMatcherType(absl::string_view value,StringMatcher::Type * type)49 absl::string_view GetMatcherType(absl::string_view value,
50                                  StringMatcher::Type* type) {
51   if (value == "*") {
52     *type = StringMatcher::Type::kSafeRegex;
53     // Presence match checks for non empty strings.
54     return ".+";
55   } else if (absl::StartsWith(value, "*")) {
56     *type = StringMatcher::Type::kSuffix;
57     return absl::StripPrefix(value, "*");
58   } else if (absl::EndsWith(value, "*")) {
59     *type = StringMatcher::Type::kPrefix;
60     return absl::StripSuffix(value, "*");
61   }
62   *type = StringMatcher::Type::kExact;
63   return value;
64 }
65 
GetStringMatcher(absl::string_view value)66 absl::StatusOr<StringMatcher> GetStringMatcher(absl::string_view value) {
67   StringMatcher::Type type;
68   absl::string_view matcher = GetMatcherType(value, &type);
69   return StringMatcher::Create(type, matcher);
70 }
71 
GetHeaderMatcher(absl::string_view name,absl::string_view value)72 absl::StatusOr<HeaderMatcher> GetHeaderMatcher(absl::string_view name,
73                                                absl::string_view value) {
74   StringMatcher::Type type;
75   absl::string_view matcher = GetMatcherType(value, &type);
76   return HeaderMatcher::Create(name, static_cast<HeaderMatcher::Type>(type),
77                                matcher);
78 }
79 
IsUnsupportedHeader(absl::string_view header_name)80 bool IsUnsupportedHeader(absl::string_view header_name) {
81   static const char* const kUnsupportedHeaders[] = {"host",
82                                                     "connection",
83                                                     "keep-alive",
84                                                     "proxy-authenticate",
85                                                     "proxy-authorization",
86                                                     "te",
87                                                     "trailer",
88                                                     "transfer-encoding",
89                                                     "upgrade"};
90   for (size_t i = 0; i < GPR_ARRAY_SIZE(kUnsupportedHeaders); ++i) {
91     if (absl::EqualsIgnoreCase(header_name, kUnsupportedHeaders[i])) {
92       return true;
93     }
94   }
95   return false;
96 }
97 
ParsePrincipalsArray(const Json & json)98 absl::StatusOr<Rbac::Principal> ParsePrincipalsArray(const Json& json) {
99   std::vector<std::unique_ptr<Rbac::Principal>> principal_names;
100   for (size_t i = 0; i < json.array().size(); ++i) {
101     const Json& child = json.array().at(i);
102     if (child.type() != Json::Type::kString) {
103       return absl::InvalidArgumentError(
104           absl::StrCat("\"principals\" ", i, ": is not a string."));
105     }
106     auto matcher_or = GetStringMatcher(child.string());
107     if (!matcher_or.ok()) {
108       return absl::Status(matcher_or.status().code(),
109                           absl::StrCat("\"principals\" ", i, ": ",
110                                        matcher_or.status().message()));
111     }
112     principal_names.push_back(std::make_unique<Rbac::Principal>(
113         Rbac::Principal::MakeAuthenticatedPrincipal(
114             std::move(matcher_or.value()))));
115   }
116   return Rbac::Principal::MakeOrPrincipal(std::move(principal_names));
117 }
118 
ParsePeer(const Json & json)119 absl::StatusOr<Rbac::Principal> ParsePeer(const Json& json) {
120   std::vector<std::unique_ptr<Rbac::Principal>> peer;
121   for (const auto& object : json.object()) {
122     if (object.first == "principals") {
123       if (object.second.type() != Json::Type::kArray) {
124         return absl::InvalidArgumentError("\"principals\" is not an array.");
125       }
126       auto principal_names_or = ParsePrincipalsArray(object.second);
127       if (!principal_names_or.ok()) return principal_names_or.status();
128       if (!principal_names_or.value().principals.empty()) {
129         peer.push_back(std::make_unique<Rbac::Principal>(
130             std::move(principal_names_or.value())));
131       }
132     } else {
133       return absl::InvalidArgumentError(absl::StrFormat(
134           "policy contains unknown field \"%s\" in \"source\".", object.first));
135     }
136   }
137   if (peer.empty()) {
138     return Rbac::Principal::MakeAnyPrincipal();
139   }
140   return Rbac::Principal::MakeAndPrincipal(std::move(peer));
141 }
142 
ParseHeaderValues(const Json & json,absl::string_view header_name)143 absl::StatusOr<Rbac::Permission> ParseHeaderValues(
144     const Json& json, absl::string_view header_name) {
145   if (json.array().empty()) {
146     return absl::InvalidArgumentError("\"values\" list is empty.");
147   }
148   std::vector<std::unique_ptr<Rbac::Permission>> values;
149   for (size_t i = 0; i < json.array().size(); ++i) {
150     const Json& child = json.array().at(i);
151     if (child.type() != Json::Type::kString) {
152       return absl::InvalidArgumentError(
153           absl::StrCat("\"values\" ", i, ": is not a string."));
154     }
155     auto matcher_or = GetHeaderMatcher(header_name, child.string());
156     if (!matcher_or.ok()) {
157       return absl::Status(
158           matcher_or.status().code(),
159           absl::StrCat("\"values\" ", i, ": ", matcher_or.status().message()));
160     }
161     values.push_back(std::make_unique<Rbac::Permission>(
162         Rbac::Permission::MakeHeaderPermission(std::move(matcher_or.value()))));
163   }
164   return Rbac::Permission::MakeOrPermission(std::move(values));
165 }
166 
ParseHeaders(const Json & json)167 absl::StatusOr<Rbac::Permission> ParseHeaders(const Json& json) {
168   absl::string_view key;
169   const Json* values = nullptr;
170   for (const auto& object : json.object()) {
171     if (object.first == "key") {
172       if (object.second.type() != Json::Type::kString) {
173         return absl::InvalidArgumentError("\"key\" is not a string.");
174       }
175       key = object.second.string();
176       if (absl::StartsWith(key, ":") || absl::StartsWith(key, "grpc-") ||
177           IsUnsupportedHeader(key)) {
178         return absl::InvalidArgumentError(
179             absl::StrFormat("Unsupported \"key\" %s.", key));
180       }
181     } else if (object.first == "values") {
182       if (object.second.type() != Json::Type::kArray) {
183         return absl::InvalidArgumentError("\"values\" is not an array.");
184       }
185       values = &object.second;
186     } else {
187       return absl::InvalidArgumentError(absl::StrFormat(
188           "policy contains unknown field \"%s\".", object.first));
189     }
190   }
191   if (key.empty()) {
192     return absl::InvalidArgumentError("\"key\" is not present.");
193   }
194   if (values == nullptr) {
195     return absl::InvalidArgumentError("\"values\" is not present.");
196   }
197   return ParseHeaderValues(*values, key);
198 }
199 
ParseHeadersArray(const Json & json)200 absl::StatusOr<Rbac::Permission> ParseHeadersArray(const Json& json) {
201   std::vector<std::unique_ptr<Rbac::Permission>> headers;
202   for (size_t i = 0; i < json.array().size(); ++i) {
203     const Json& child = json.array().at(i);
204     if (child.type() != Json::Type::kObject) {
205       return absl::InvalidArgumentError(
206           absl::StrCat("\"headers\" ", i, ": is not an object."));
207     }
208     auto headers_or = ParseHeaders(child);
209     if (!headers_or.ok()) {
210       return absl::Status(
211           headers_or.status().code(),
212           absl::StrCat("\"headers\" ", i, ": ", headers_or.status().message()));
213     }
214     headers.push_back(
215         std::make_unique<Rbac::Permission>(std::move(headers_or.value())));
216   }
217   return Rbac::Permission::MakeAndPermission(std::move(headers));
218 }
219 
ParsePathsArray(const Json & json)220 absl::StatusOr<Rbac::Permission> ParsePathsArray(const Json& json) {
221   std::vector<std::unique_ptr<Rbac::Permission>> paths;
222   for (size_t i = 0; i < json.array().size(); ++i) {
223     const Json& child = json.array().at(i);
224     if (child.type() != Json::Type::kString) {
225       return absl::InvalidArgumentError(
226           absl::StrCat("\"paths\" ", i, ": is not a string."));
227     }
228     auto matcher_or = GetStringMatcher(child.string());
229     if (!matcher_or.ok()) {
230       return absl::Status(
231           matcher_or.status().code(),
232           absl::StrCat("\"paths\" ", i, ": ", matcher_or.status().message()));
233     }
234     paths.push_back(std::make_unique<Rbac::Permission>(
235         Rbac::Permission::MakePathPermission(std::move(matcher_or.value()))));
236   }
237   return Rbac::Permission::MakeOrPermission(std::move(paths));
238 }
239 
ParseRequest(const Json & json)240 absl::StatusOr<Rbac::Permission> ParseRequest(const Json& json) {
241   std::vector<std::unique_ptr<Rbac::Permission>> request;
242   for (const auto& object : json.object()) {
243     if (object.first == "paths") {
244       if (object.second.type() != Json::Type::kArray) {
245         return absl::InvalidArgumentError("\"paths\" is not an array.");
246       }
247       auto paths_or = ParsePathsArray(object.second);
248       if (!paths_or.ok()) return paths_or.status();
249       if (!paths_or.value().permissions.empty()) {
250         request.push_back(
251             std::make_unique<Rbac::Permission>(std::move(paths_or.value())));
252       }
253     } else if (object.first == "headers") {
254       if (object.second.type() != Json::Type::kArray) {
255         return absl::InvalidArgumentError("\"headers\" is not an array.");
256       }
257       auto headers_or = ParseHeadersArray(object.second);
258       if (!headers_or.ok()) return headers_or.status();
259       if (!headers_or.value().permissions.empty()) {
260         request.push_back(
261             std::make_unique<Rbac::Permission>(std::move(headers_or.value())));
262       }
263     } else {
264       return absl::InvalidArgumentError(absl::StrFormat(
265           "policy contains unknown field \"%s\" in \"request\".",
266           object.first));
267     }
268   }
269   if (request.empty()) {
270     return Rbac::Permission::MakeAnyPermission();
271   }
272   return Rbac::Permission::MakeAndPermission(std::move(request));
273 }
274 
ParseRule(const Json & json,std::string * policy_name)275 absl::StatusOr<Rbac::Policy> ParseRule(const Json& json,
276                                        std::string* policy_name) {
277   absl::optional<Rbac::Principal> principals;
278   absl::optional<Rbac::Permission> permissions;
279   for (const auto& object : json.object()) {
280     if (object.first == "name") {
281       if (object.second.type() != Json::Type::kString) {
282         return absl::InvalidArgumentError(
283             absl::StrCat("\"name\" is not a string."));
284       }
285       *policy_name = object.second.string();
286     } else if (object.first == "source") {
287       if (object.second.type() != Json::Type::kObject) {
288         return absl::InvalidArgumentError("\"source\" is not an object.");
289       }
290       auto peer_or = ParsePeer(object.second);
291       if (!peer_or.ok()) return peer_or.status();
292       principals = std::move(*peer_or);
293     } else if (object.first == "request") {
294       if (object.second.type() != Json::Type::kObject) {
295         return absl::InvalidArgumentError("\"request\" is not an object.");
296       }
297       auto request_or = ParseRequest(object.second);
298       if (!request_or.ok()) return request_or.status();
299       permissions = std::move(*request_or);
300     } else {
301       return absl::InvalidArgumentError(absl::StrFormat(
302           "policy contains unknown field \"%s\" in \"rule\".", object.first));
303     }
304   }
305   if (policy_name->empty()) {
306     return absl::InvalidArgumentError(absl::StrCat("\"name\" is not present."));
307   }
308   if (!principals.has_value()) {
309     principals = Rbac::Principal::MakeAnyPrincipal();
310   }
311   if (!permissions.has_value()) {
312     permissions = Rbac::Permission::MakeAnyPermission();
313   }
314   return Rbac::Policy(std::move(*permissions), std::move(*principals));
315 }
316 
ParseRulesArray(const Json & json)317 absl::StatusOr<std::map<std::string, Rbac::Policy>> ParseRulesArray(
318     const Json& json) {
319   if (json.array().empty()) {
320     return absl::InvalidArgumentError("rules is empty.");
321   }
322   std::map<std::string, Rbac::Policy> policies;
323   for (size_t i = 0; i < json.array().size(); ++i) {
324     const Json& child = json.array().at(i);
325     if (child.type() != Json::Type::kObject) {
326       return absl::InvalidArgumentError(
327           absl::StrCat("rules ", i, ": is not an object."));
328     }
329     std::string policy_name;
330     auto policy_or = ParseRule(child, &policy_name);
331     if (!policy_or.ok()) {
332       return absl::Status(
333           policy_or.status().code(),
334           absl::StrCat("rules ", i, ": ", policy_or.status().message()));
335     }
336     policies[policy_name] = std::move(policy_or.value());
337   }
338   return std::move(policies);
339 }
340 
ParseDenyRulesArray(const Json & json,absl::string_view name)341 absl::StatusOr<Rbac> ParseDenyRulesArray(const Json& json,
342                                          absl::string_view name) {
343   auto policies_or = ParseRulesArray(json);
344   if (!policies_or.ok()) return policies_or.status();
345   return Rbac(std::string(name), Rbac::Action::kDeny,
346               std::move(policies_or.value()));
347 }
348 
ParseAllowRulesArray(const Json & json,absl::string_view name)349 absl::StatusOr<Rbac> ParseAllowRulesArray(const Json& json,
350                                           absl::string_view name) {
351   auto policies_or = ParseRulesArray(json);
352   if (!policies_or.ok()) return policies_or.status();
353   return Rbac(std::string(name), Rbac::Action::kAllow,
354               std::move(policies_or.value()));
355 }
356 
357 absl::StatusOr<std::unique_ptr<experimental::AuditLoggerFactory::Config>>
ParseAuditLogger(const Json & json,size_t pos)358 ParseAuditLogger(const Json& json, size_t pos) {
359   if (json.type() != Json::Type::kObject) {
360     return absl::InvalidArgumentError(
361         absl::StrFormat("\"audit_loggers[%d]\" is not an object.", pos));
362   }
363   for (const auto& object : json.object()) {
364     if (object.first != "name" && object.first != "is_optional" &&
365         object.first != "config") {
366       return absl::InvalidArgumentError(
367           absl::StrFormat("policy contains unknown field \"%s\" in "
368                           "\"audit_logging_options.audit_loggers[%d]\".",
369                           object.first, pos));
370     }
371   }
372   bool is_optional = false;
373   auto it = json.object().find("is_optional");
374   if (it != json.object().end()) {
375     switch (it->second.type()) {
376       case Json::Type::kBoolean:
377         is_optional = it->second.boolean();
378         break;
379       default:
380         return absl::InvalidArgumentError(absl::StrFormat(
381             "\"audit_loggers[%d].is_optional\" is not a boolean.", pos));
382     }
383   }
384   it = json.object().find("name");
385   if (it == json.object().end()) {
386     return absl::InvalidArgumentError(
387         absl::StrFormat("\"audit_loggers[%d].name\" is required.", pos));
388   }
389   if (it->second.type() != Json::Type::kString) {
390     return absl::InvalidArgumentError(
391         absl::StrFormat("\"audit_loggers[%d].name\" is not a string.", pos));
392   }
393   absl::string_view name = it->second.string();
394   // The config defaults to an empty object.
395   Json config = Json::FromObject({});
396   it = json.object().find("config");
397   if (it != json.object().end()) {
398     if (it->second.type() != Json::Type::kObject) {
399       return absl::InvalidArgumentError(absl::StrFormat(
400           "\"audit_loggers[%d].config\" is not an object.", pos));
401     }
402     config = it->second;
403   }
404   if (!AuditLoggerRegistry::FactoryExists(name)) {
405     if (is_optional) {
406       return nullptr;
407     }
408     return absl::InvalidArgumentError(
409         absl::StrFormat("\"audit_loggers[%d].name\" %s is not supported "
410                         "natively or registered.",
411                         pos, name));
412   }
413   auto result = AuditLoggerRegistry::ParseConfig(name, config);
414   if (!result.ok()) {
415     return absl::InvalidArgumentError(absl::StrFormat(
416         "\"audit_loggers[%d]\" %s", pos, result.status().message()));
417   }
418   return result;
419 }
420 
ParseAuditLoggingOptions(const Json & json,RbacPolicies * rbacs)421 absl::Status ParseAuditLoggingOptions(const Json& json, RbacPolicies* rbacs) {
422   CHECK_NE(rbacs, nullptr);
423   for (auto it = json.object().begin(); it != json.object().end(); ++it) {
424     if (it->first == "audit_condition") {
425       if (it->second.type() != Json::Type::kString) {
426         return absl::InvalidArgumentError(
427             "\"audit_condition\" is not a string.");
428       }
429       absl::string_view condition = it->second.string();
430       Rbac::AuditCondition deny_condition, allow_condition;
431       if (condition == "NONE") {
432         deny_condition = Rbac::AuditCondition::kNone;
433         allow_condition = Rbac::AuditCondition::kNone;
434       } else if (condition == "ON_ALLOW") {
435         deny_condition = Rbac::AuditCondition::kNone;
436         allow_condition = Rbac::AuditCondition::kOnAllow;
437       } else if (condition == "ON_DENY") {
438         deny_condition = Rbac::AuditCondition::kOnDeny;
439         allow_condition = Rbac::AuditCondition::kOnDeny;
440       } else if (condition == "ON_DENY_AND_ALLOW") {
441         deny_condition = Rbac::AuditCondition::kOnDeny;
442         allow_condition = Rbac::AuditCondition::kOnDenyAndAllow;
443       } else {
444         return absl::InvalidArgumentError(absl::StrFormat(
445             "Unsupported \"audit_condition\" value %s.", condition));
446       }
447       if (rbacs->deny_policy.has_value()) {
448         rbacs->deny_policy->audit_condition = deny_condition;
449       }
450       rbacs->allow_policy.audit_condition = allow_condition;
451     } else if (it->first == "audit_loggers") {
452       if (it->second.type() != Json::Type::kArray) {
453         return absl::InvalidArgumentError("\"audit_loggers\" is not an array.");
454       }
455       const auto& loggers = it->second.array();
456       for (size_t i = 0; i < loggers.size(); ++i) {
457         auto result = ParseAuditLogger(loggers.at(i), i);
458         if (!result.ok()) {
459           return result.status();
460         }
461         // Check the value since the unsupported logger config can also
462         // return ok when marked as optional.
463         if (result.value() != nullptr) {
464           // Only move the logger config over if audit condition is not NONE.
465           if (rbacs->allow_policy.audit_condition !=
466               Rbac::AuditCondition::kNone) {
467             rbacs->allow_policy.logger_configs.push_back(
468                 std::move(result.value()));
469           }
470           if (rbacs->deny_policy.has_value() &&
471               rbacs->deny_policy->audit_condition !=
472                   Rbac::AuditCondition::kNone) {
473             // Parse again since it returns unique_ptr, but result should be ok
474             // this time.
475             auto result = ParseAuditLogger(loggers.at(i), i);
476             CHECK(result.ok());
477             rbacs->deny_policy->logger_configs.push_back(
478                 std::move(result.value()));
479           }
480         }
481       }
482     } else {
483       return absl::InvalidArgumentError(absl::StrFormat(
484           "policy contains unknown field \"%s\" in \"audit_logging_options\".",
485           it->first));
486     }
487   }
488   return absl::OkStatus();
489 }
490 
491 }  // namespace
492 
GenerateRbacPolicies(absl::string_view authz_policy)493 absl::StatusOr<RbacPolicies> GenerateRbacPolicies(
494     absl::string_view authz_policy) {
495   auto json = JsonParse(authz_policy);
496   if (!json.ok()) {
497     return absl::InvalidArgumentError(
498         absl::StrCat("Failed to parse gRPC authorization policy. Error: ",
499                      json.status().ToString()));
500   }
501   if (json->type() != Json::Type::kObject) {
502     return absl::InvalidArgumentError(
503         "SDK authorization policy is not an object.");
504   }
505   auto it = json->object().find("name");
506   if (it == json->object().end()) {
507     return absl::InvalidArgumentError("\"name\" field is not present.");
508   }
509   if (it->second.type() != Json::Type::kString) {
510     return absl::InvalidArgumentError("\"name\" is not a string.");
511   }
512   absl::string_view name = it->second.string();
513   RbacPolicies rbacs;
514   bool has_allow_rbac = false;
515   for (const auto& object : json->object()) {
516     if (object.first == "name") {
517       continue;
518     } else if (object.first == "deny_rules") {
519       if (object.second.type() != Json::Type::kArray) {
520         return absl::InvalidArgumentError("\"deny_rules\" is not an array.");
521       }
522       auto deny_policy_or = ParseDenyRulesArray(object.second, name);
523       if (!deny_policy_or.ok()) {
524         return absl::Status(
525             deny_policy_or.status().code(),
526             absl::StrCat("deny_", deny_policy_or.status().message()));
527       }
528       rbacs.deny_policy = std::move(*deny_policy_or);
529     } else if (object.first == "allow_rules") {
530       if (object.second.type() != Json::Type::kArray) {
531         return absl::InvalidArgumentError("\"allow_rules\" is not an array.");
532       }
533       auto allow_policy_or = ParseAllowRulesArray(object.second, name);
534       if (!allow_policy_or.ok()) {
535         return absl::Status(
536             allow_policy_or.status().code(),
537             absl::StrCat("allow_", allow_policy_or.status().message()));
538       }
539       rbacs.allow_policy = std::move(*allow_policy_or);
540       has_allow_rbac = true;
541     } else if (object.first == "audit_logging_options") {
542       // This must be processed this after policies are all parsed.
543       continue;
544     } else {
545       return absl::InvalidArgumentError(absl::StrFormat(
546           "policy contains unknown field \"%s\".", object.first));
547     }
548   }
549   it = json->object().find("audit_logging_options");
550   if (it != json->object().end()) {
551     if (it->second.type() != Json::Type::kObject) {
552       return absl::InvalidArgumentError(
553           "\"audit_logging_options\" is not an object.");
554     }
555     absl::Status status = ParseAuditLoggingOptions(it->second, &rbacs);
556     if (!status.ok()) {
557       return status;
558     }
559   }
560   if (!has_allow_rbac) {
561     return absl::InvalidArgumentError("\"allow_rules\" is not present.");
562   }
563   return std::move(rbacs);
564 }
565 
566 }  // namespace grpc_core
567