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