blob: 916c78007a5bd949fa672e47c2de505b16d8b084 [file]
#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