| #include "redfish_authorizer.h" |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <fstream> |
| #include <iterator> |
| #include <limits> |
| #include <memory> |
| #include <string> |
| #include <string_view> |
| #include <unordered_set> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/log/log.h" |
| #include "absl/status/status.h" |
| #include "absl/synchronization/mutex.h" |
| #include "authorizer_enums.h" |
| #include "nlohmann/json.hpp" |
| #include "nlohmann/json_fwd.hpp" |
| #include "config_parser.h" |
| #include "json_utils.h" |
| #include "override.h" |
| #include "pattern_to_entity_array.h" |
| #include "redfish_entity_trie.h" |
| #include "redfish_privileges.h" |
| #include "resource_uri_override.h" |
| #include "subordinate_override.h" |
| |
| namespace milotic::authz { |
| |
| using ::ecclesia::Operation; |
| using ::ecclesia::ResourceEntity; |
| using ::ecclesia::StringToOperation; |
| using ::ecclesia::StringToResourceEntity; |
| |
| RedfishAuthorizer::RedfishAuthorizer() |
| : RedfishAuthorizer(AuthorizerOptions()) {} |
| |
| RedfishAuthorizer::RedfishAuthorizer(const AuthorizerOptions& options) |
| : options_(options), |
| authz_configuration_(nlohmann::json(), options.offline_node_entities_path, |
| options.google_machine_identity_path) { |
| ResetEntityPrivilegesMappings(); |
| LoadRedfishEntityTrie(); |
| ReloadConfiguration(); |
| } |
| |
| void RedfishAuthorizer::LoadRedfishEntityTrie() { |
| for (std::size_t i = 0; i < pattern_entity_pair_array.size(); ++i) { |
| const auto& [pattern, entity] = pattern_entity_pair_array[i]; |
| redfish_entity_trie_.InsertUri(pattern, entity, i); |
| } |
| // Now loads non-standard URI pattern into the trie |
| std::ifstream configuration_file(options_.pattern_entity_overrides_path); |
| if (!configuration_file.is_open()) { |
| LOG(WARNING) << "Pattern entity overrides file at '" |
| << options_.pattern_entity_overrides_path << "' is missing."; |
| return; |
| } |
| std::string configuration = { |
| std::istreambuf_iterator<char>(configuration_file), |
| std::istreambuf_iterator<char>()}; |
| constexpr bool kAllowException = false; |
| nlohmann::json configuration_json = |
| nlohmann::json::parse(configuration, nullptr, kAllowException); |
| if (configuration_json.is_discarded()) { |
| LOG(WARNING) << "Pattern entity overrides file at '" |
| << options_.pattern_entity_overrides_path |
| << "' is not a JSON document."; |
| return; |
| } |
| const nlohmann::json::array_t* pattern_entity_pairs = |
| GetValueAsArray(configuration_json, "pattern_entity_mappings_override"); |
| if (pattern_entity_pairs == nullptr) { |
| LOG(WARNING) << "There are no pattern entity mappings."; |
| return; |
| } |
| |
| for (const nlohmann::json& pattern_entity_pair : *pattern_entity_pairs) { |
| const std::string* pattern = |
| GetValueAsString(pattern_entity_pair, "pattern"); |
| const std::string* entity_str = |
| GetValueAsString(pattern_entity_pair, "resource"); |
| if (pattern == nullptr || entity_str == nullptr) { |
| LOG(WARNING) << "Pattern entity pair is invalid."; |
| return; |
| } |
| ResourceEntity entity = StringToResourceEntity(*entity_str); |
| // TODO(nanzhou): support indexing non-standard URI patterns |
| redfish_entity_trie_.InsertUri(*pattern, entity, |
| std::numeric_limits<std::size_t>::max()); |
| } |
| } |
| |
| ResourceEntity RedfishAuthorizer::GetEntityTypeFromRedfishUri( |
| std::string_view uri) const { |
| return redfish_entity_trie_.GetEntityType(uri); |
| } |
| |
| RedfishAuthorizer::RedfishAuthorizer(const nlohmann::json& registry_json, |
| const nlohmann::json& configuration_json) { |
| ResetEntityPrivilegesMappings(); |
| LoadRedfishEntityTrie(); |
| ReconstructPrivilegeRegistry( |
| GetValueAsArray(registry_json, "Mappings"), |
| GetValueAsArray(configuration_json, "oem_privilege_mappings"), |
| GetValueAsJson(configuration_json, "additional_override_mappings"), |
| GetValueAsArray(registry_json, "AdditionalSubtreeMappings"), |
| /*platform_subtree_mappings=*/nullptr); |
| } |
| |
| static nlohmann::json ParseJsonConfig(const std::string& configuration_path) { |
| std::ifstream configuration_file(configuration_path); |
| if (!configuration_file.is_open()) { |
| LOG(WARNING) << "Configuration file at '" << configuration_path |
| << "' is missing."; |
| return nlohmann::json(); |
| } |
| std::string configuration = { |
| std::istreambuf_iterator<char>(configuration_file), |
| std::istreambuf_iterator<char>()}; |
| constexpr bool kAllowException = false; |
| nlohmann::json configuration_json = |
| nlohmann::json::parse(configuration, nullptr, kAllowException); |
| if (configuration_json.is_discarded()) { |
| LOG(WARNING) << "Configuration file at '" << configuration_path |
| << "' is not a JSON document."; |
| return nlohmann::json(); |
| } |
| return configuration_json; |
| } |
| |
| nlohmann::json RedfishAuthorizer::ParseAuthConfig() const { |
| return ParseJsonConfig(options_.configuration_path); |
| } |
| |
| void RedfishAuthorizer::ParseSubscriptionLimit( |
| const nlohmann::json& configuration_json) { |
| const int64_t* subscription_limit = |
| GetValueAsInt(configuration_json, "universal_subscription_limit"); |
| if (subscription_limit == nullptr) { |
| LOG(WARNING) << "Subscription limit not defined in Authorization Config; a " |
| "default value 1 will be used"; |
| return; |
| } |
| if (*subscription_limit < 1) { |
| LOG(WARNING) << "Subscription shouldn't be less than 1; a default value 1 " |
| "will be used"; |
| return; |
| } |
| subscription_tracker_.SetUniversalSubscriptionLimit(*subscription_limit); |
| } |
| |
| std::string RedfishAuthorizer::FindBasePrivilegeRegistryPath( |
| const nlohmann::json& configuration_json) const { |
| const std::string* base_privilege_registry_str = |
| GetValueAsString(configuration_json, "base_privilege_registry"); |
| if (base_privilege_registry_str == nullptr) { |
| LOG(WARNING) |
| << "Base Privilege Registry not defined in Authorization Config"; |
| return ""; |
| } |
| |
| std::string base_privilege_registry_path = |
| options_.base_privileges_folder + "/" + *base_privilege_registry_str; |
| |
| std::ifstream registry_file(base_privilege_registry_path); |
| if (!registry_file.is_open()) { |
| LOG(WARNING) << "Base registry file at '" << base_privilege_registry_path |
| << "' is missing."; |
| return ""; |
| } |
| return base_privilege_registry_path; |
| } |
| |
| std::string RedfishAuthorizer::GetPeerRedfishRole( |
| const PeerSpiffeIdentity& peer) const { |
| return authz_configuration_.GetPeerRedfishRole(peer); |
| } |
| |
| uint64_t RedfishAuthorizer::GetSampleRateLimit( |
| const std::string& peer_role) const { |
| return authz_configuration_.GetSampleRateLimit(peer_role); |
| } |
| |
| void RedfishAuthorizer::ReloadConfiguration() { |
| ReloadConfiguration(options_.configuration_path); |
| } |
| |
| void RedfishAuthorizer::ReloadConfiguration( |
| const std::string& configuration_path) { |
| nlohmann::json configuration_json = ParseJsonConfig(configuration_path); |
| |
| if (configuration_json.empty()) { |
| return; |
| } |
| |
| // Load subscription limit |
| ParseSubscriptionLimit(configuration_json); |
| |
| nlohmann::json platform_configuration_json = |
| ParseJsonConfig(options_.platform_configuration_path); |
| const std::string* platform_name_str = |
| GetValueAsString(platform_configuration_json, "name"); |
| if (platform_name_str != nullptr) { |
| LOG(WARNING) << "Loading platform configuration '" << *platform_name_str |
| << "'" << " from " << options_.platform_configuration_path; |
| } |
| |
| // Load authz config |
| authz_configuration_.ReloadConfig(configuration_json, |
| platform_configuration_json); |
| |
| std::string base_privilege_registry_path = |
| FindBasePrivilegeRegistryPath(configuration_json); |
| |
| if (base_privilege_registry_path.empty()) { |
| return; |
| } |
| |
| ResetEntityPrivilegesMappings(); |
| |
| ReconstructPrivilegeRegistry( |
| base_privilege_registry_path, |
| GetValueAsArray(configuration_json, "oem_privilege_mappings"), |
| GetValueAsJson(configuration_json, "additional_override_mappings"), |
| platform_configuration_json); |
| } |
| |
| void RedfishAuthorizer::SetBasePrivilegesFolder( |
| std::string_view base_privileges_folder) { |
| options_.base_privileges_folder = base_privileges_folder; |
| ReloadConfiguration(); |
| } |
| |
| void RedfishAuthorizer::SetPrivileges( |
| EntityPrivilegesMappings&& new_entity_privileges_mappings, |
| OverrideMappings&& new_override_mappings) { |
| absl::MutexLock lock(&mutex_); |
| entity_privileges_mappings_ = std::move(new_entity_privileges_mappings); |
| override_mappings_ = std::move(new_override_mappings); |
| } |
| |
| RedfishAuthorizer::EntityPrivilegesMappings |
| RedfishAuthorizer::DefaultEntityPrivilegesMappings() { |
| return EntityPrivilegesMappings( |
| static_cast<int>(ResourceEntity::kUndefined) + 1, |
| std::vector<std::vector<RedfishPrivileges>>( |
| static_cast<int>(Operation::kUndefined) + 1)); |
| } |
| |
| RedfishAuthorizer::OverrideMappings |
| RedfishAuthorizer::DefaultOverrideMappings() { |
| return OverrideMappings(static_cast<int>(ResourceEntity::kUndefined) + 1); |
| } |
| |
| void RedfishAuthorizer::ResetEntityPrivilegesMappings() { |
| absl::MutexLock lock(&mutex_); |
| entity_privileges_mappings_ = DefaultEntityPrivilegesMappings(); |
| override_mappings_ = DefaultOverrideMappings(); |
| } |
| |
| std::vector<RedfishPrivileges> RedfishAuthorizer::GetPrivilegesArrFromJsonArr( |
| const nlohmann::json::array_t& operation_privilege_arr_json) const { |
| std::vector<RedfishPrivileges> privileges_or_arr; |
| for (const nlohmann::json& operation_privilege : |
| operation_privilege_arr_json) { |
| RedfishPrivileges redfish_privileges; |
| const nlohmann::json::array_t* privilege_arr = |
| GetValueAsArray(operation_privilege, "Privilege"); |
| if (privilege_arr == nullptr) { |
| continue; |
| } |
| for (const nlohmann::json& privilege : *privilege_arr) { |
| const std::string* privilege_str = |
| privilege.get_ptr<const std::string*>(); |
| if (privilege_str == nullptr) { |
| LOG(WARNING) << "The value of the privilege isn't a string."; |
| LOG(WARNING) << "|value|=" << privilege.dump(2); |
| continue; |
| } |
| redfish_privileges.InsertPrivilege(*privilege_str); |
| } |
| // NOTE: there won't be an empty required privilege set ever |
| if (redfish_privileges.GetPrivileges().empty()) { |
| continue; |
| } |
| privileges_or_arr.push_back(redfish_privileges); |
| } |
| return privileges_or_arr; |
| } |
| |
| std::vector<std::string> RedfishAuthorizer::GetTargetsArrFromJsonArr( |
| const nlohmann::json::array_t& targets_arr_json) const { |
| std::vector<std::string> targets; |
| for (const nlohmann::json& target : targets_arr_json) { |
| const std::string* target_str = target.get_ptr<const std::string*>(); |
| if (target_str == nullptr) { |
| continue; |
| } |
| targets.push_back(*target_str); |
| } |
| return targets; |
| } |
| |
| absl::flat_hash_map<ecclesia::Operation, std::vector<RedfishPrivileges>> |
| RedfishAuthorizer::GetOperationMapFromJson( |
| const nlohmann::json& override_operation_map) const { |
| absl::flat_hash_map<ecclesia::Operation, std::vector<RedfishPrivileges>> |
| override_operation_privileges_map; |
| |
| for (Operation operation : all_operations) { |
| const nlohmann::json::array_t* override_operation_privilege_arr = |
| GetValueAsArray(override_operation_map, OperationToString(operation)); |
| if (override_operation_privilege_arr == nullptr) { |
| continue; |
| } |
| override_operation_privileges_map[operation] = |
| GetPrivilegesArrFromJsonArr(*override_operation_privilege_arr); |
| } |
| return override_operation_privileges_map; |
| } |
| |
| void RedfishAuthorizer::ReconstructPrivilegeRegistry( |
| const std::string& base_registry_path, |
| const nlohmann::json::array_t* oem_mappings, |
| const nlohmann::json* override_mappings, |
| const nlohmann::json& platform_config_json) { |
| std::ifstream registry_file(base_registry_path); |
| if (!registry_file.is_open()) { |
| LOG(WARNING) << "Base registry file at '" << base_registry_path |
| << "' is missing."; |
| return; |
| } |
| std::string registry = {std::istreambuf_iterator<char>(registry_file), |
| std::istreambuf_iterator<char>()}; |
| constexpr bool kAllowException = false; |
| nlohmann::json registry_json = |
| nlohmann::json::parse(registry, nullptr, kAllowException); |
| if (registry_json.is_discarded()) { |
| LOG(WARNING) << "Base registry file at '" << base_registry_path |
| << "' is not a JSON document."; |
| return; |
| } |
| const nlohmann::json::array_t* mappings = |
| GetValueAsArray(registry_json, "Mappings"); |
| const nlohmann::json::array_t* additional_subtree_mappings = |
| GetValueAsArray(registry_json, "AdditionalSubtreeMappings"); |
| const nlohmann::json::array_t* platform_subtree_mappings = |
| GetValueAsArray(platform_config_json, "AdditionalSubtreeMappings"); |
| ReconstructPrivilegeRegistry(mappings, oem_mappings, override_mappings, |
| additional_subtree_mappings, |
| platform_subtree_mappings); |
| } |
| |
| void RedfishAuthorizer::ReconstructPrivilegeRegistry( |
| const nlohmann::json::array_t* registry_mappings, |
| const nlohmann::json::array_t* oem_mappings, |
| const nlohmann::json* override_mappings, |
| const nlohmann::json::array_t* additional_subtree_mappings, |
| const nlohmann::json::array_t* platform_subtree_mappings) { |
| if (registry_mappings == nullptr) { |
| return; |
| } |
| EntityPrivilegesMappings new_entity_privileges_mappings = |
| DefaultEntityPrivilegesMappings(); |
| OverrideMappings new_override_mappings = DefaultOverrideMappings(); |
| |
| for (const nlohmann::json& mapping : *registry_mappings) { |
| const std::string* entity_str = GetValueAsString(mapping, "Entity"); |
| const nlohmann::json* operation_map = |
| GetValueAsJson(mapping, "OperationMap"); |
| if (operation_map == nullptr || entity_str == nullptr) { |
| continue; |
| } |
| ResourceEntity entity = StringToResourceEntity(*entity_str); |
| if (entity == ResourceEntity::kUndefined) { |
| continue; |
| } |
| |
| for (Operation operation : all_operations) { |
| const nlohmann::json::array_t* operation_privilege_arr = |
| GetValueAsArray(*operation_map, OperationToString(operation)); |
| if (operation_privilege_arr == nullptr) { |
| continue; |
| } |
| |
| new_entity_privileges_mappings[static_cast<int>(entity)][static_cast<int>( |
| operation)] = GetPrivilegesArrFromJsonArr(*operation_privilege_arr); |
| } |
| |
| // Apply overrides if applicable |
| ApplySubordinateOverridesToEntity( |
| GetValueAsArray(mapping, "SubordinateOverrides"), entity, |
| new_override_mappings); |
| ApplyResourceOverridesToEntity( |
| GetValueAsArray(mapping, "ResourceURIOverrides"), entity, |
| new_override_mappings); |
| } |
| |
| if (oem_mappings != nullptr) { |
| ApplyOemMappings(*oem_mappings, new_entity_privileges_mappings); |
| } |
| |
| if (override_mappings != nullptr) { |
| ApplyOverrideMappings(*override_mappings, new_override_mappings); |
| } |
| |
| std::vector<SubtreePrivilegeMapping> additional_subtree_mappings_vector; |
| if (additional_subtree_mappings != nullptr) { |
| ParseAdditionalSubtreeMappings(additional_subtree_mappings_vector, |
| *additional_subtree_mappings); |
| } |
| if (platform_subtree_mappings != nullptr) { |
| ParseAdditionalSubtreeMappings(additional_subtree_mappings_vector, |
| *platform_subtree_mappings); |
| } |
| |
| redfish_entity_trie_.ReplaceSubtreePrivilegeMappings( |
| std::move(additional_subtree_mappings_vector)); |
| |
| SetPrivileges(std::move(new_entity_privileges_mappings), |
| std::move(new_override_mappings)); |
| } |
| |
| void RedfishAuthorizer::ApplySubordinateOverrides( |
| const nlohmann::json::array_t* subordinate_overrides, |
| OverrideMappings& new_override_mappings) { |
| if (subordinate_overrides == nullptr) { |
| return; |
| } |
| for (const nlohmann::json& subordinate_override : *subordinate_overrides) { |
| const std::string* entity_str = |
| GetValueAsString(subordinate_override, "entity"); |
| const nlohmann::json::array_t* targets_json = |
| GetValueAsArray(subordinate_override, "targets"); |
| const nlohmann::json* override_operation_map = |
| GetValueAsJson(subordinate_override, "operation_map"); |
| |
| if (entity_str == nullptr || targets_json == nullptr || |
| targets_json->empty() || override_operation_map == nullptr) { |
| LOG(WARNING) << "Override is setup incorrectly"; |
| LOG(WARNING) << "|json|=" << subordinate_override.dump(2); |
| continue; |
| } |
| |
| ResourceEntity entity = StringToResourceEntity(*entity_str); |
| if (entity == ResourceEntity::kUndefined) { |
| LOG(WARNING) << "|entity| is unsupported; |entity|=" << *entity_str; |
| continue; |
| } |
| |
| std::vector<std::string> targets = GetTargetsArrFromJsonArr(*targets_json); |
| |
| if (targets.empty()) { |
| continue; |
| } |
| |
| new_override_mappings[static_cast<int>(entity)].push_back( |
| std::make_shared<SubordinateOverride>( |
| std::move(targets), |
| GetOperationMapFromJson(*override_operation_map), |
| redfish_entity_trie_)); |
| } |
| } |
| |
| void RedfishAuthorizer::ApplySubordinateOverridesToEntity( |
| const nlohmann::json::array_t* subordinate_overrides, ResourceEntity entity, |
| OverrideMappings& new_override_mappings) { |
| if (subordinate_overrides == nullptr) { |
| return; |
| } |
| |
| for (const nlohmann::json& subordinate_override : *subordinate_overrides) { |
| const nlohmann::json::array_t* targets_json = |
| GetValueAsArray(subordinate_override, "Targets"); |
| const nlohmann::json* override_operation_map = |
| GetValueAsJson(subordinate_override, "OperationMap"); |
| |
| if (targets_json == nullptr || override_operation_map == nullptr) { |
| continue; |
| } |
| |
| std::vector<std::string> targets = GetTargetsArrFromJsonArr(*targets_json); |
| |
| if (targets.empty()) { |
| continue; |
| } |
| |
| new_override_mappings[static_cast<int>(entity)].push_back( |
| std::make_shared<SubordinateOverride>( |
| std::move(targets), |
| GetOperationMapFromJson(*override_operation_map), |
| redfish_entity_trie_)); |
| } |
| } |
| |
| void RedfishAuthorizer::ApplyResourceOverrides( |
| const nlohmann::json::array_t* resource_overrides, |
| OverrideMappings& new_override_mappings) { |
| if (resource_overrides == nullptr) { |
| return; |
| } |
| for (const nlohmann::json& resource_override : *resource_overrides) { |
| const std::string* entity_str = |
| GetValueAsString(resource_override, "entity"); |
| const nlohmann::json::array_t* targets_json = |
| GetValueAsArray(resource_override, "targets"); |
| const nlohmann::json* override_operation_map = |
| GetValueAsJson(resource_override, "operation_map"); |
| |
| if (entity_str == nullptr || targets_json == nullptr || |
| targets_json->empty() || override_operation_map == nullptr) { |
| LOG(WARNING) << "Override is setup incorrectly"; |
| LOG(WARNING) << "|json|=" << resource_override.dump(2); |
| continue; |
| } |
| |
| ResourceEntity entity = StringToResourceEntity(*entity_str); |
| if (entity == ResourceEntity::kUndefined) { |
| LOG(WARNING) << "|entity| is unsupported; |entity|=" << *entity_str; |
| continue; |
| } |
| |
| std::vector<std::string> targets = GetTargetsArrFromJsonArr(*targets_json); |
| |
| if (targets.empty()) { |
| continue; |
| } |
| |
| new_override_mappings[static_cast<int>(entity)].push_back( |
| std::make_shared<ResourceUriOverride>( |
| std::move(targets), |
| GetOperationMapFromJson(*override_operation_map))); |
| } |
| } |
| |
| void RedfishAuthorizer::ApplyResourceOverridesToEntity( |
| const nlohmann::json::array_t* resource_overrides, ResourceEntity entity, |
| OverrideMappings& new_override_mappings) { |
| if (resource_overrides == nullptr) { |
| return; |
| } |
| |
| for (const nlohmann::json& resource_override : *resource_overrides) { |
| const nlohmann::json::array_t* targets_json = |
| GetValueAsArray(resource_override, "Targets"); |
| const nlohmann::json* override_operation_map = |
| GetValueAsJson(resource_override, "OperationMap"); |
| |
| if (targets_json == nullptr || override_operation_map == nullptr) { |
| continue; |
| } |
| |
| std::vector<std::string> targets = GetTargetsArrFromJsonArr(*targets_json); |
| |
| if (targets.empty()) { |
| continue; |
| } |
| |
| new_override_mappings[static_cast<int>(entity)].push_back( |
| std::make_shared<ResourceUriOverride>( |
| std::move(targets), |
| GetOperationMapFromJson(*override_operation_map))); |
| } |
| } |
| |
| void RedfishAuthorizer::ApplyOverrideMappings( |
| const nlohmann::json& override_mappings_json, |
| OverrideMappings& new_override_mappings) { |
| ApplySubordinateOverrides( |
| GetValueAsArray(override_mappings_json, "subordinate_overrides"), |
| new_override_mappings); |
| ApplyResourceOverrides( |
| GetValueAsArray(override_mappings_json, "resource_overrides"), |
| new_override_mappings); |
| } |
| |
| void RedfishAuthorizer::ApplyOemMappings( |
| const nlohmann::json::array_t& oem_mappings, |
| EntityPrivilegesMappings& entity_privileges_mappings) { |
| for (const nlohmann::json& oem_mapping : oem_mappings) { |
| const std::string* oem_privilege = |
| GetValueAsString(oem_mapping, "oem_privilege"); |
| if (oem_privilege == nullptr) { |
| continue; |
| } |
| RedfishPrivileges new_privileges({*oem_privilege}); |
| const nlohmann::json::array_t* resource_operations = |
| GetValueAsArray(oem_mapping, "resource_operations"); |
| if (resource_operations == nullptr) { |
| continue; |
| } |
| for (const nlohmann::json& resource_operation : *resource_operations) { |
| const std::string* resource_entity_str = |
| GetValueAsString(resource_operation, "resource_entity"); |
| if (resource_entity_str == nullptr) { |
| continue; |
| } |
| ResourceEntity entity = StringToResourceEntity(*resource_entity_str); |
| if (entity == ResourceEntity::kUndefined) { |
| LOG(WARNING) << "|resource_entity| is unsupported; |resource_entity|=" |
| << *resource_entity_str; |
| continue; |
| } |
| const nlohmann::json::array_t* operations = |
| GetValueAsArray(resource_operation, "operations"); |
| if (operations == nullptr) { |
| continue; |
| } |
| for (const nlohmann::json& operation_json : *operations) { |
| const std::string* operation_str = |
| operation_json.get_ptr<const std::string*>(); |
| if (operation_str == nullptr) { |
| LOG(WARNING) << "The value of the operation isn't a string."; |
| LOG(WARNING) << "|value|=" << operation_json; |
| continue; |
| } |
| Operation operation = StringToOperation(*operation_str); |
| if (operation == Operation::kUndefined) { |
| LOG(WARNING) << "|operation| is unsupported; |operation|=" |
| << *operation_str; |
| continue; |
| } |
| AppendPrivilege(new_privileges, entity, operation, |
| entity_privileges_mappings); |
| } |
| } |
| } |
| } |
| |
| void RedfishAuthorizer::ParseAdditionalSubtreeMappings( |
| std::vector<SubtreePrivilegeMapping>& additional_subtree_mappings, |
| const nlohmann::json& additional_subtree_mappings_json) { |
| for (const nlohmann::json& subtree_mapping : |
| additional_subtree_mappings_json) { |
| const std::string* subtree_prefix = |
| GetValueAsString(subtree_mapping, "SubtreePrefix"); |
| const nlohmann::json* operation_map_json = |
| GetValueAsJson(subtree_mapping, "OperationMap"); |
| if (subtree_prefix == nullptr || operation_map_json == nullptr) { |
| continue; |
| } |
| for (const auto& [operation, privileges] : |
| GetOperationMapFromJson(*operation_map_json)) { |
| for (const auto& privilege : privileges) { |
| additional_subtree_mappings.push_back( |
| SubtreePrivilegeMapping{.url = *subtree_prefix, |
| .operation = operation, |
| .privileges = privilege}); |
| } |
| } |
| } |
| } |
| |
| void RedfishAuthorizer::AppendPrivilege( |
| const RedfishPrivileges& privileges, ResourceEntity resource_entity, |
| Operation operation, EntityPrivilegesMappings& entity_privileges_mappings) { |
| entity_privileges_mappings[static_cast<int>(resource_entity)] |
| [static_cast<int>(operation)] |
| .push_back(privileges); |
| } |
| |
| std::vector<RedfishPrivileges> RedfishAuthorizer::GetRequiredPrivileges( |
| ResourceEntity resource_entity, Operation operation) const { |
| absl::MutexLock lock(&mutex_); |
| return entity_privileges_mappings_[static_cast<int>(resource_entity)] |
| [static_cast<int>(operation)]; |
| } |
| |
| std::vector<std::shared_ptr<Override>> RedfishAuthorizer::GetOverrides( |
| ResourceEntity resource_entity) const { |
| absl::MutexLock lock(&mutex_); |
| return override_mappings_[static_cast<int>(resource_entity)]; |
| } |
| |
| absl::Status RedfishAuthorizer::RecordNewSubscription( |
| const PeerSpiffeIdentity& peer) { |
| return subscription_tracker_.RecordNewSubscription(peer); |
| } |
| |
| absl::Status RedfishAuthorizer::RecordNewUnsubscription( |
| const PeerSpiffeIdentity& peer) { |
| return subscription_tracker_.RecordNewUnsubscription(peer); |
| } |
| |
| bool RedfishAuthorizer::IsPeerAuthorized( |
| ResourceEntity entity, Operation operation, |
| const RedfishPrivileges& peer_privileges) const { |
| if (entity == ResourceEntity::kUndefined || |
| operation == Operation::kUndefined) { |
| return false; |
| } |
| |
| std::vector<RedfishPrivileges> required_privilege_sets = |
| GetRequiredPrivileges(entity, operation); |
| |
| if (required_privilege_sets.empty()) { |
| LOG(WARNING) << "No required privileges found for " |
| << ResourceEntityToString(entity) << " and operation " |
| << OperationToString(operation) |
| << ". This most likely means that you are using an outdated " |
| "redfish authz policy."; |
| } |
| |
| for (const RedfishPrivileges& privileges : required_privilege_sets) { |
| // NOTE: there won't be an empty required privilege set ever; otherwise, |
| // we skip this required privilege set. |
| if (privileges.GetPrivileges().empty()) { |
| LOG(WARNING) |
| << "Precondition failed: required privilege set will never be " |
| "empty; check the base privilege registry for " |
| << ResourceEntityToString(entity) << " and operation " |
| << OperationToString(operation); |
| continue; |
| } |
| if (peer_privileges.IsSupersetOf(privileges)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool RedfishAuthorizer::IsPeerAuthorized( |
| std::string_view uri, Operation operation, |
| const RedfishPrivileges& peer_privileges) const { |
| ResourceEntity entity = GetEntityTypeFromRedfishUri(uri); |
| if (entity == ResourceEntity::kUndefined) { |
| LOG(WARNING) << "Can't map |uri| to Redfish resource; |uri|=" << uri; |
| return false; |
| } |
| if (operation == Operation::kUndefined) { |
| LOG(WARNING) << "|operation| is undefined"; |
| return false; |
| } |
| |
| // Check Overrides |
| for (const std::shared_ptr<Override>& override : GetOverrides(entity)) { |
| // If the override is applicable, then peer must be authorized through |
| // override |
| if (override->IsApplicable(uri, operation)) { |
| return override->IsPeerAuthorized(uri, operation, peer_privileges); |
| } |
| } |
| |
| // Check Additional Subtree Mappings |
| for (const auto& privileges : |
| redfish_entity_trie_.GetAdditionalSubtreePrivileges(uri, operation)) { |
| // NOTE: there won't be an empty required privilege set ever; otherwise, |
| // we skip this required privilege set. |
| if (privileges.GetPrivileges().empty()) { |
| LOG(WARNING) |
| << "Precondition failed: required privilege set will never be " |
| "empty; check the additional subtree mappings for " |
| << uri << " and operation " << OperationToString(operation); |
| continue; |
| } |
| if (peer_privileges.IsSupersetOf(privileges)) { |
| return true; |
| } |
| } |
| |
| return IsPeerAuthorized(entity, operation, peer_privileges); |
| } |
| |
| nlohmann::json::array_t RedfishAuthorizer::RedfishPrivilegeRegistryMappings() |
| const { |
| nlohmann::json::array_t privilege_registry_mappings; |
| |
| for (size_t i = 0; i < static_cast<size_t>(ResourceEntity::kUndefined); ++i) { |
| nlohmann::json::object_t entity_privilege_mappings; |
| ResourceEntity entity = static_cast<ResourceEntity>(i); |
| entity_privilege_mappings["Entity"] = ResourceEntityToString(entity); |
| |
| // Get OperationMap |
| entity_privilege_mappings["OperationMap"] = nlohmann::json::object_t(); |
| for (size_t j = 0; j < static_cast<size_t>(Operation::kUndefined); ++j) { |
| nlohmann::json::array_t operation_privileges; |
| Operation operation = static_cast<Operation>(j); |
| std::vector<RedfishPrivileges> privilege_set = |
| GetRequiredPrivileges(entity, operation); |
| for (const RedfishPrivileges& privileges : privilege_set) { |
| operation_privileges.push_back(privileges.ToJson()); |
| } |
| entity_privilege_mappings["OperationMap"][OperationToString(operation)] = |
| operation_privileges; |
| } |
| |
| // Get Overrides |
| std::vector<std::shared_ptr<Override>> overrides = GetOverrides(entity); |
| nlohmann::json::array_t subordinate_overrides_json = |
| nlohmann::json::array_t(); |
| nlohmann::json::array_t resource_overrides_json = nlohmann::json::array_t(); |
| for (const std::shared_ptr<Override>& override : overrides) { |
| if (override->GetOverrideType() == Override::Type::kSubordinateOverride) { |
| subordinate_overrides_json.push_back(override->ToJson()); |
| continue; |
| } |
| resource_overrides_json.push_back(override->ToJson()); |
| } |
| |
| if (!subordinate_overrides_json.empty()) { |
| entity_privilege_mappings["SubordinateOverrides"] = |
| subordinate_overrides_json; |
| } |
| if (!resource_overrides_json.empty()) { |
| entity_privilege_mappings["ResourceURIOverrides"] = |
| resource_overrides_json; |
| } |
| |
| privilege_registry_mappings.push_back(entity_privilege_mappings); |
| } |
| |
| return privilege_registry_mappings; |
| } |
| |
| nlohmann::json::object_t RedfishAuthorizer::GetRedfishPrivilegeRegistry() |
| const { |
| nlohmann::json::object_t privilege_registry; |
| |
| privilege_registry["@odata.id"] = "/redfish/v1/AccountService/PrivilegeMap"; |
| privilege_registry["@odata.type"] = |
| "#PrivilegeRegistry.v1_1_4.PrivilegeRegistry"; |
| privilege_registry["Id"] = "Redfish_1.3.1_PrivilegeRegistry"; |
| privilege_registry["Name"] = "Privilege Mapping array collection"; |
| privilege_registry["PrivilegesUsed"] = |
| authz_configuration_.GetDefaultRedfishPrivileges(); |
| privilege_registry["OEMPrivilegesUsed"] = |
| authz_configuration_.GetOemPrivileges(); |
| privilege_registry["Mappings"] = RedfishPrivilegeRegistryMappings(); |
| |
| return privilege_registry; |
| } |
| |
| } // namespace milotic::authz |