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