| #include "tlbmc/redfish/routes/redfish_authorization.h" |
| |
| #include <string> |
| #include <string_view> |
| |
| #include "absl/functional/bind_front.h" |
| #include "absl/log/log.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/str_cat.h" |
| #include "g3/macros.h" |
| #include <nlohmann/json_fwd.hpp> |
| #include "json_utils.h" |
| #include "tlbmc/redfish/app.h" |
| #include "tlbmc/redfish/request.h" |
| #include "tlbmc/redfish/response.h" |
| #include "tlbmc/redfish/routes/action_managers/file_manager.h" |
| #include "bmcweb_authorizer_singleton.h" |
| |
| namespace milotic_tlbmc::redfish_authorization { |
| |
| using ::milotic::authz::BmcWebAuthorizerSingleton; |
| using ::milotic::authz::GetValueAsArray; |
| using ::milotic::authz::GetValueAsJson; |
| using ::milotic::authz::GetValueAsString; |
| |
| constexpr std::string_view kBasePrivilegeRegistryName = |
| "GoogleOem_1.0.0_PrivilegeRegistry.json"; |
| |
| constexpr std::string_view kAuthorizationConfigFilePath = |
| "/var/google/authz_policies/redfish.json"; |
| |
| constexpr std::string_view kAuthorizationPrivilegeRegistryFilePath = |
| "/var/google/authz_policies/GoogleOem_1.0.0_PrivilegeRegistry.json"; |
| |
| namespace internal { |
| |
| absl::Status ValidateAuthorizationConfig(const nlohmann::json& config) { |
| const std::string* base_privilege_registry = GetValueAsString( |
| config, nlohmann::json::json_pointer("/base_privilege_registry")); |
| if (base_privilege_registry == nullptr) { |
| return absl::InvalidArgumentError( |
| "The request body is missing the base_privilege_registry."); |
| } |
| if (*base_privilege_registry != kBasePrivilegeRegistryName) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "The base_privilege_registry must be ", kBasePrivilegeRegistryName)); |
| } |
| return absl::OkStatus(); |
| } |
| |
| absl::Status ContainsPrivilegeInRegistry( |
| const nlohmann::json::array_t& operation_map, std::string_view privilege) { |
| for (const auto& privileges : operation_map) { |
| const nlohmann::json::array_t* privilege_array = |
| GetValueAsArray(privileges, "Privilege"); |
| if (privilege_array == nullptr) { |
| return absl::InvalidArgumentError( |
| "The OperationMap is incorrectly formatted. Missing a Privilege " |
| "Array."); |
| } |
| // The privilege should be the only privilege required to access the |
| // resource. |
| if (privilege_array->size() != 1) { |
| continue; |
| } |
| for (const auto& privilege_in_registry : *privilege_array) { |
| if (privilege_in_registry == privilege) { |
| return absl::OkStatus(); |
| } |
| } |
| } |
| return absl::InvalidArgumentError(absl::StrCat( |
| "The request body does not contain the privilege: ", privilege, |
| " as its only requirement to access to resource.")); |
| } |
| |
| absl::Status ValidatePrivilegeRegistry( |
| const nlohmann::json& privilege_registry) { |
| const nlohmann::json::array_t* mappings = |
| GetValueAsArray(privilege_registry, "Mappings"); |
| if (mappings == nullptr) { |
| return absl::InvalidArgumentError( |
| "The request body is missing the Privilege Mappings."); |
| } |
| for (const auto& mapping : *mappings) { |
| const std::string* entity = GetValueAsString(mapping, "Entity"); |
| if (entity == nullptr || |
| *entity != "GoogleAuthorizationPrivilegeRegistry") { |
| continue; |
| } |
| const nlohmann::json* operation_map = |
| GetValueAsJson(mapping, "OperationMap"); |
| if (operation_map == nullptr) { |
| return absl::InvalidArgumentError( |
| "The request body is missing the OperationMap for Entity " |
| "GoogleAuthorizationPrivilegeRegistry."); |
| } |
| |
| // Look through the get_privileges to ensure that ReadGoogleResources is |
| // present. |
| const nlohmann::json::array_t* get_privileges = |
| GetValueAsArray(*operation_map, "GET"); |
| if (get_privileges == nullptr) { |
| return absl::InvalidArgumentError( |
| "The request body is missing the GET OperationMap for Entity " |
| "GoogleAuthorizationPrivilegeRegistry."); |
| } |
| LOG(INFO) << *get_privileges; |
| ECCLESIA_RETURN_IF_ERROR( |
| ContainsPrivilegeInRegistry(*get_privileges, "ReadGoogleResources")); |
| |
| // Validate the PUT privilege has ConfigureGoogleResources. |
| // Search for the first privilege in the PUT operation and return "" if not |
| // found. |
| const nlohmann::json::array_t* put_privileges = |
| GetValueAsArray(*operation_map, "PUT"); |
| if (put_privileges == nullptr) { |
| return absl::InvalidArgumentError( |
| "The request body is missing the PUT OperationMap for Entity " |
| "GoogleAuthorizationPrivilegeRegistry."); |
| } |
| ECCLESIA_RETURN_IF_ERROR(ContainsPrivilegeInRegistry( |
| *put_privileges, "ConfigureGoogleResources")); |
| |
| return absl::OkStatus(); |
| } |
| return absl::NotFoundError( |
| "The request body MUST authorize the " |
| "GoogleAuthorizationPrivilegeRegistry entity."); |
| } |
| } // namespace internal |
| |
| void HandleGetAuthorizationConfig(const RedfishRequest& req, |
| RedfishResponse& resp) { |
| resp.SetKeyInJsonBody("/@odata.id", |
| "/redfish/v1/UpdateService/Oem/Google/" |
| "AuthorizationConfig"); |
| resp.SetKeyInJsonBody("/@odata.type", |
| "#GoogleAuthorizationConfig.GoogleAuthorizationConfig"); |
| resp.SetKeyInJsonBody("/AuthorizationConfig", |
| BmcWebAuthorizerSingleton::GetInstance().GetConfig()); |
| } |
| |
| void HandlePutAuthorizationConfig(const std::string& system_root_path, |
| const RedfishRequest& req, |
| RedfishResponse& resp) { |
| nlohmann::json config = nlohmann::json::parse(req.Body(), nullptr, false); |
| if (config.is_discarded()) { |
| resp.SetToAbslStatus(absl::InvalidArgumentError( |
| "The request body is missing or malformed.")); |
| return; |
| } |
| |
| // Validate the Config: |
| // We will always ensure that the base_privilege_registry is not changed. |
| // Even if other fields change, the default privileges should ALWAYS allow for |
| // a PUT to be made. This will ensure that users can always recover the |
| // configuration. |
| |
| absl::Status validate_authz_config_status = |
| internal::ValidateAuthorizationConfig(config); |
| if (!validate_authz_config_status.ok()) { |
| resp.SetToAbslStatus(validate_authz_config_status); |
| return; |
| } |
| |
| absl::Status write_authz_config_status = FileManager::WriteToFile( |
| config.dump(2), |
| absl::StrCat(system_root_path, kAuthorizationConfigFilePath)); |
| |
| if (!write_authz_config_status.ok()) { |
| LOG(ERROR) << "Failed to write authorization config: " |
| << write_authz_config_status; |
| resp.SetToAbslStatus(write_authz_config_status); |
| return; |
| } |
| |
| BmcWebAuthorizerSingleton::GetInstance().ReloadRedfishAuthorizer(); |
| |
| resp.SetToAccepted( |
| "/redfish/v1/UpdateService/Oem/Google/AuthorizationConfig/"); |
| resp.SetKeyInJsonBody("/@odata.type", "#Message.v1_1_2.Message"); |
| resp.SetKeyInJsonBody("/MessageId", "Base.1.14.Success"); |
| resp.SetKeyInJsonBody("/Message", |
| "Successfully updated the authorization config."); |
| resp.SetKeyInJsonBody("/Severity", "OK"); |
| resp.SetKeyInJsonBody("/Resolution", "None"); |
| } |
| |
| void HandleGetAuthorizationPrivilegeRegistry(const RedfishRequest& req, |
| RedfishResponse& resp) { |
| resp.SetKeyInJsonBody("/@odata.id", |
| "/redfish/v1/UpdateService/Oem/Google/" |
| "AuthorizationPrivilegeRegistry"); |
| resp.SetKeyInJsonBody("/@odata.type", |
| "#GoogleAuthorizationPrivilegeRegistry." |
| "GoogleAuthorizationPrivilegeRegistry"); |
| resp.SetKeyInJsonBody( |
| "/AuthorizationPrivilegeRegistry", |
| BmcWebAuthorizerSingleton::GetInstance().GetPrivilegeRegistry()); |
| } |
| |
| void HandlePutAuthorizationPrivilegeRegistry( |
| const std::string& system_root_path, const RedfishRequest& req, |
| RedfishResponse& resp) { |
| nlohmann::json privilege_registry = |
| nlohmann::json::parse(req.Body(), nullptr, false); |
| if (privilege_registry.is_discarded()) { |
| resp.SetToAbslStatus(absl::InvalidArgumentError( |
| "The request body is missing or malformed.")); |
| return; |
| } |
| |
| // Validate the Privilege Registry: |
| // We will always ensure that GET and PUT for |
| // GoogleAuthorizationPrivilegeRegistry entity has ReadGoogleResources and |
| // ConfigureGoogleResources respectively as their first privilege. This will |
| // ensure that users can always recover the configuration. |
| absl::Status validate_privilege_registry_status = |
| internal::ValidatePrivilegeRegistry(privilege_registry); |
| if (!validate_privilege_registry_status.ok()) { |
| resp.SetToAbslStatus(validate_privilege_registry_status); |
| return; |
| } |
| |
| absl::Status write_privilege_registry_status = FileManager::WriteToFile( |
| privilege_registry.dump(2), |
| absl::StrCat(system_root_path, kAuthorizationPrivilegeRegistryFilePath)); |
| |
| if (!write_privilege_registry_status.ok()) { |
| LOG(ERROR) << "Failed to write authorization privilege registry: " |
| << write_privilege_registry_status; |
| resp.SetToAbslStatus(write_privilege_registry_status); |
| return; |
| } |
| |
| BmcWebAuthorizerSingleton::GetInstance().SetBasePrivilegesFolder( |
| absl::StrCat(system_root_path, "/var/google/authz_policies")); |
| BmcWebAuthorizerSingleton::GetInstance().ReloadRedfishAuthorizer(); |
| |
| resp.SetToAccepted( |
| "/redfish/v1/UpdateService/Oem/Google/AuthorizationPrivilegeRegistry/"); |
| resp.SetKeyInJsonBody("/@odata.type", "#Message.v1_1_2.Message"); |
| resp.SetKeyInJsonBody("/MessageId", "Base.1.14.Success"); |
| resp.SetKeyInJsonBody( |
| "/Message", "Successfully updated the authorization privilege registry."); |
| resp.SetKeyInJsonBody("/Severity", "OK"); |
| resp.SetKeyInJsonBody("/Resolution", "None"); |
| } |
| |
| void RegisterRoutes(RedfishApp& app, const std::string& system_root_path) { |
| TLBMC_ROUTE(app, "/redfish/v1/UpdateService/Oem/Google/AuthorizationConfig/") |
| .methods(boost::beast::http::verb::get)(HandleGetAuthorizationConfig); |
| TLBMC_ROUTE(app, "/redfish/v1/UpdateService/Oem/Google/AuthorizationConfig/") |
| .methods(boost::beast::http::verb::put)( |
| absl::bind_front(HandlePutAuthorizationConfig, system_root_path)); |
| TLBMC_ROUTE( |
| app, |
| "/redfish/v1/UpdateService/Oem/Google/AuthorizationPrivilegeRegistry/") |
| .methods(boost::beast::http::verb::get)( |
| HandleGetAuthorizationPrivilegeRegistry); |
| TLBMC_ROUTE( |
| app, |
| "/redfish/v1/UpdateService/Oem/Google/AuthorizationPrivilegeRegistry/") |
| .methods(boost::beast::http::verb::put)(absl::bind_front( |
| HandlePutAuthorizationPrivilegeRegistry, system_root_path)); |
| } |
| |
| } // namespace milotic_tlbmc::redfish_authorization |