| #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 "override_json.h" | |
| #include "pattern_to_entity_array.h" | |
| #include "redfish_entity_trie.h" | |
| #include "redfish_privileges.h" | |
| #include "redfish_privileges_json.h" | |
| #include "resource_uri_override.h" | |
| #include "subordinate_override.h" | |
| #include <redfish_authorization/private_pattern_to_entity_array.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); | |
| } | |
| for (std::size_t i = 0; i < private_pattern_entity_pair_array.size(); ++i) { | |
| const auto& [pattern, entity] = private_pattern_entity_pair_array[i]; | |
| redfish_entity_trie_.InsertUri(pattern, entity, | |
| pattern_entity_pair_array.size() + 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); | |
| } | |
| std::string RedfishAuthorizer::GetAnycastAddress( | |
| const std::string& peer_role) const { | |
| return authz_configuration_.GetAnycastAddress(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); | |
| for (const auto& [service, user] : anycast_users_) { | |
| user->SetAnycastAddress(authz_configuration_.GetAnycastAddress(service)); | |
| } | |
| 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); | |
| absl::MutexLock lock(mutex_); | |
| privilege_registry_json_ = std::move(registry_json); | |
| } | |
| 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(RedfishPrivilegesToJson(privileges)); | |
| } | |
| 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(OverrideToJson(*override)); | |
| continue; | |
| } | |
| resource_overrides_json.push_back(OverrideToJson(*override)); | |
| } | |
| 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 |