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