| |
| #include "resource_authz.h" |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <string> |
| #include <vector> |
| |
| #include "absl/log/log.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/string_view.h" |
| #include "grpcpp/security/auth_context.h" |
| #include "metrics.h" |
| #include "proxy_config.pb.h" |
| #include "redfish_plugin.h" |
| |
| namespace milotic { |
| |
| static bool RequestVerbMatches(milotic_grpc_proxy::RequestVerb proto_verb, |
| RedfishPlugin::RequestVerb verb) { |
| switch (proto_verb) { |
| case milotic_grpc_proxy::REQUEST_VERB_UNSPECIFIED: |
| return true; |
| case milotic_grpc_proxy::REQUEST_VERB_GET: |
| return verb == RedfishPlugin::RequestVerb::kGet; |
| case milotic_grpc_proxy::REQUEST_VERB_POST: |
| return verb == RedfishPlugin::RequestVerb::kPost; |
| case milotic_grpc_proxy::REQUEST_VERB_PUT: |
| return verb == RedfishPlugin::RequestVerb::kPut; |
| case milotic_grpc_proxy::REQUEST_VERB_PATCH: |
| return verb == RedfishPlugin::RequestVerb::kPatch; |
| case milotic_grpc_proxy::REQUEST_VERB_DELETE: |
| return verb == RedfishPlugin::RequestVerb::kDelete; |
| case milotic_grpc_proxy::REQUEST_VERB_SUBSCRIBE: |
| return verb == RedfishPlugin::RequestVerb::kSubscribe; |
| case milotic_grpc_proxy::REQUEST_VERB_BIDIRECTIONAL_STREAM: |
| return verb == RedfishPlugin::RequestVerb::kBidirectionalStream; |
| default: |
| return false; |
| } |
| } |
| |
| static bool RuleApplies( |
| const milotic_grpc_proxy::AuthorizationPolicy::Rule& rule, |
| AuthorizationContext& context) { |
| switch (rule.permission_case()) { |
| case milotic_grpc_proxy::AuthorizationPolicy::Rule::kPermissionId: |
| if (context.permission_checker == nullptr) { |
| LOG(ERROR) << "Permission checker is null"; |
| return false; |
| } |
| return context.permission_checker->Check(context, rule.permission_id()); |
| case milotic_grpc_proxy::AuthorizationPolicy::Rule::kLocal: { |
| auto values = |
| context.grpc_context.FindPropertyValues("transport_security_type"); |
| return std::find(values.begin(), values.end(), "local") != values.end(); |
| } |
| case milotic_grpc_proxy::AuthorizationPolicy::Rule::PERMISSION_NOT_SET: |
| return true; |
| } |
| return false; |
| } |
| |
| static bool ResourceMatches(absl::string_view expected, bool with_subtree, |
| absl::string_view redfish_id) { |
| if (expected.ends_with('/')) { |
| expected.remove_suffix(1); |
| } |
| |
| // Match required |
| if (!redfish_id.starts_with(expected)) return false; |
| |
| // Prefix matches |
| if (redfish_id.ends_with('/')) { |
| redfish_id.remove_suffix(1); |
| } |
| if (redfish_id.size() == expected.size()) return true; |
| |
| // Contains suffix |
| if (redfish_id[expected.size()] != '/' && redfish_id[expected.size()] != '?') |
| return false; |
| |
| // Suffix is subtree or query params |
| if (with_subtree) return true; |
| |
| // Subtree should not match |
| size_t params_pos = redfish_id.find('?'); |
| if (params_pos == absl::string_view::npos) return false; |
| |
| // Has query params |
| if (redfish_id.find("$expand=", params_pos) != absl::string_view::npos) { |
| return false; |
| } |
| |
| // No expand in query params |
| redfish_id.remove_suffix(redfish_id.size() - params_pos); |
| if (redfish_id.ends_with('/')) { |
| redfish_id.remove_suffix(1); |
| } |
| |
| // Query params removed |
| return redfish_id.size() == expected.size(); |
| } |
| |
| absl::Status AuthorizeRequest( |
| const milotic_grpc_proxy::AuthorizationPolicy& authorization_policy, |
| RedfishPlugin::RequestVerb verb, absl::string_view redfish_id, |
| AuthorizationContext& auth_context) { |
| using Mapping = milotic_grpc_proxy::AuthorizationPolicy::Mapping; |
| |
| for (const Mapping& mapping : authorization_policy.mappings()) { |
| if (!RequestVerbMatches(mapping.request_verb(), verb)) continue; |
| if (!std::any_of(mapping.resource_path().begin(), |
| mapping.resource_path().end(), |
| [redfish_id, &mapping](absl::string_view resource_path) { |
| return ResourceMatches( |
| resource_path, mapping.with_subtree(), redfish_id); |
| })) |
| continue; |
| CommonMetrics::Get().mapping_counter.Increment(mapping.name()); |
| for (const auto& [name, rule] : mapping.allow()) { |
| bool ok = RuleApplies(rule, auth_context); |
| CommonMetrics::Get().rule_counter.Increment({name, ok}); |
| if (ok) { |
| return absl::OkStatus(); |
| } |
| } |
| return absl::PermissionDeniedError( |
| absl::StrFormat("Permission denied for %s %s using mapping %s", |
| VerbToString(verb), redfish_id, mapping.name())); |
| } |
| return absl::PermissionDeniedError( |
| absl::StrFormat("Permission denied: No maching rule found for %s %s", |
| VerbToString(verb), redfish_id)); |
| } |
| |
| } // namespace milotic |