| #include "tlbmc/redfish/routes/certificate_service.h" |
| |
| #include <array> |
| #include <string> |
| |
| #include "absl/log/log.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/string_view.h" |
| #include "nlohmann/json.hpp" |
| #include "json_utils.h" |
| #include "tlbmc/redfish/app.h" |
| #include "tlbmc/redfish/request.h" |
| #include "tlbmc/redfish/response.h" |
| #include "tlbmc/redfish/routes/trust_bundle_manager.h" |
| |
| namespace milotic_tlbmc::certificate_service { |
| |
| using ::nlohmann::json; |
| |
| constexpr absl::string_view kPropertyNameCertificateUri = "CertificateUri"; |
| constexpr absl::string_view kCertificatesPath = |
| "/redfish/v1/Managers/bmc/Certificates"; |
| constexpr absl::string_view kCertificateNameTrustBundle = "TrustBundle"; |
| constexpr absl::string_view kCertificateNameServerCert = "ServerCert"; |
| constexpr absl::string_view kCertificateNameOsCert = |
| "OsVerificationCertificate"; |
| |
| namespace { |
| |
| void HandleInstallTrustBundle(const nlohmann::json& body, |
| RedfishResponse& resp) { |
| const std::string* cert_type = |
| milotic::authz::GetValueAsString(body, "CertificateType"); |
| if (cert_type == nullptr || *cert_type != "PEM") { |
| resp.SetToAbslStatus(absl::InvalidArgumentError( |
| "The 'CertificateType' property must be 'PEM'.")); |
| return; |
| } |
| |
| const std::string* cert_string = |
| milotic::authz::GetValueAsString(body, "CertificateString"); |
| if (cert_string == nullptr) { |
| resp.SetToAbslStatus(absl::InvalidArgumentError( |
| "The 'CertificateString' property is missing or malformed.")); |
| return; |
| } |
| |
| const std::string* signature_string = |
| milotic::authz::GetValueAsString(body, "DetachedSignature"); |
| if (signature_string == nullptr) { |
| resp.SetToAbslStatus( |
| absl::InvalidArgumentError("The 'DetachedSignature' property is " |
| "required for install trust bundle.")); |
| return; |
| } |
| |
| absl::Status status = TrustBundleManager::GetInstance().InstallTrustBundle( |
| *cert_string, *signature_string); |
| if (!status.ok()) { |
| resp.SetToAbslStatus(status); |
| return; |
| } |
| |
| resp.SetToAccepted( |
| absl::StrCat(kCertificatesPath, "/", kCertificateNameTrustBundle)); |
| resp.SetKeyInJsonBody("/@odata.type", "#Message.v1_1_2.Message"); |
| resp.SetKeyInJsonBody("/MessageId", "Base.1.14.Success"); |
| resp.SetKeyInJsonBody( |
| "/Message", |
| absl::StrFormat( |
| "Successfully installed the trust bundle. Restarting " |
| "redfish service to apply the trust bundle in %d seconds.", |
| kTrustBundleRestartDelaySeconds)); |
| resp.SetKeyInJsonBody("/Severity", "OK"); |
| resp.SetKeyInJsonBody("/Resolution", "None"); |
| } |
| |
| void HandleGenerateCsrActionInfo(const RedfishRequest& req, |
| RedfishResponse& resp) { |
| resp.SetKeyInJsonBody("/@odata.id", |
| "/redfish/v1/CertificateService/" |
| "GenerateCSRActionInfo"); |
| resp.SetKeyInJsonBody("/@odata.type", "#ActionInfo.v1_1_2.ActionInfo"); |
| resp.SetKeyInJsonBody( |
| "/Description", "This action shall make a certificate signing request."); |
| resp.SetKeyInJsonBody("/Id", "CertificateService.GenerateCSR"); |
| resp.SetKeyInJsonBody("/Name", "CertificateService.GenerateCSR"); |
| resp.SetKeyInJsonBody( |
| "/Parameters", |
| json::array({ |
| json::object({{"DataType", "String"}, |
| {"Name", "AlternativeNames"}, |
| {"Required", false}}), |
| json::object( |
| {{"DataType", "String"}, {"Name", "City"}, {"Required", true}}), |
| json::object({{"DataType", "String"}, |
| {"Name", "CommonName"}, |
| {"Required", true}}), |
| json::object({{"DataType", "String"}, |
| {"Name", "Country"}, |
| {"Required", true}}), |
| json::object({{"DataType", "String"}, |
| {"Name", "Organization"}, |
| {"Required", true}}), |
| json::object({{"DataType", "String"}, |
| {"Name", "OrganizationalUnit"}, |
| {"Required", true}}), |
| json::object( |
| {{"DataType", "String"}, {"Name", "State"}, {"Required", true}}), |
| json::object({{"DataType", "Object"}, |
| {"Name", "CertificateCollection"}, |
| {"ObjectDataType", "String"}, |
| {"Required", true}}), |
| })); |
| } |
| |
| void HandleReplaceCertificateActionInfo(const RedfishRequest& req, |
| RedfishResponse& resp) { |
| resp.SetKeyInJsonBody( |
| "/@odata.id", |
| "/redfish/v1/CertificateService/ReplaceCertificateActionInfo"); |
| resp.SetKeyInJsonBody("/@odata.type", "#ActionInfo.v1_3_0.ActionInfo"); |
| resp.SetKeyInJsonBody("/Id", "ReplaceCertificateActionInfo"); |
| resp.SetKeyInJsonBody("/Name", "Replace Certificate Action Info"); |
| resp.SetKeyInJsonBody( |
| "/Parameters", |
| json::array({ |
| json::object({{"DataType", "String"}, |
| {"Name", "CertificateString"}, |
| {"Required", true}}), |
| json::object({{"AllowableValues", json::array({"PEM"})}, |
| {"DataType", "String"}, |
| {"Name", "CertificateType"}, |
| {"Required", true}}), |
| json::object({{"DataType", "Object"}, |
| {"Name", "CertificateUri"}, |
| {"ObjectDataType", "@odata.id"}, |
| {"Required", true}}), |
| })); |
| |
| resp.SetKeyInJsonBody( |
| "/Oem", |
| json::object({ |
| {"Google", |
| json::object({ |
| {"Description", |
| "Google-specific parameters for certificate replacement."}, |
| {"Parameters", |
| json::array({json::object( |
| {{"Name", "DetachedSignature"}, |
| {"Required", false}, |
| {"DataType", "String"}, |
| {"Description", |
| "An optional PKCS#7 detached digital signature, " |
| "PEM-encoded, to authenticate the " |
| "CertificateString."}})})}, |
| })}, |
| })); |
| } |
| |
| void HandleInstallServerCert(const RedfishRequest& req, RedfishResponse& resp) { |
| resp.SetToAbslStatus(absl::UnimplementedError( |
| "Installing server certificates is not supported.")); |
| } |
| void HandleInstallOsCert(const RedfishRequest& req, RedfishResponse& resp) { |
| resp.SetToAbslStatus( |
| absl::UnimplementedError("Installing OS certificates is not supported.")); |
| } |
| |
| void HandleReplaceCertificate(const RedfishRequest& req, |
| RedfishResponse& resp) { |
| nlohmann::json body = nlohmann::json::parse(req.Body(), nullptr, false); |
| if (body.is_discarded()) { |
| resp.SetToAbslStatus(absl::InvalidArgumentError( |
| "The request body is missing or malformed.")); |
| return; |
| } |
| |
| nlohmann::json::json_pointer odata_id_ptr( |
| absl::StrCat("/", kPropertyNameCertificateUri, "/@odata.id")); |
| const std::string* uri = milotic::authz::GetValueAsString(body, odata_id_ptr); |
| |
| if (uri == nullptr) { |
| resp.SetToAbslStatus(absl::InvalidArgumentError( |
| absl::StrCat("The request body is missing or malformed '", |
| kPropertyNameCertificateUri, "' property."))); |
| return; |
| } |
| |
| if (*uri == |
| absl::StrCat(kCertificatesPath, "/", kCertificateNameTrustBundle)) { |
| HandleInstallTrustBundle(body, resp); |
| } else if (*uri == |
| absl::StrCat(kCertificatesPath, "/", kCertificateNameServerCert)) { |
| HandleInstallServerCert(req, resp); |
| } else if (*uri == |
| absl::StrCat(kCertificatesPath, "/", kCertificateNameOsCert)) { |
| HandleInstallOsCert(req, resp); |
| } else { |
| resp.SetToAbslStatus(absl::InvalidArgumentError(absl::StrCat( |
| "The provided '", kPropertyNameCertificateUri, "' is not supported."))); |
| } |
| } |
| |
| void HandleGenerateCsr(const RedfishRequest& req, RedfishResponse& resp) { |
| nlohmann::json body = nlohmann::json::parse(req.Body(), nullptr, false); |
| if (body.is_discarded()) { |
| resp.SetToAbslStatus(absl::InvalidArgumentError( |
| "The request body is missing or malformed.")); |
| return; |
| } |
| |
| constexpr std::array<absl::string_view, 6> required_string_params = { |
| "City", "CommonName", "Country", |
| "Organization", "OrganizationalUnit", "State"}; |
| for (absl::string_view param : required_string_params) { |
| if (milotic::authz::GetValueAsString(body, param) == nullptr) { |
| resp.SetToAbslStatus(absl::InvalidArgumentError(absl::StrCat( |
| "The request body is missing required property '", param, "'."))); |
| return; |
| } |
| } |
| |
| nlohmann::json::json_pointer odata_id_ptr("/CertificateCollection/@odata.id"); |
| const std::string* collection_uri = |
| milotic::authz::GetValueAsString(body, odata_id_ptr); |
| |
| if (collection_uri == nullptr) { |
| resp.SetToAbslStatus(absl::InvalidArgumentError( |
| "The 'CertificateCollection' property is missing or malformed.")); |
| return; |
| } |
| |
| if (*collection_uri != kCertificatesPath) { |
| resp.SetToAbslStatus(absl::InvalidArgumentError(absl::StrCat( |
| "The 'CertificateCollection' URI must be '", kCertificatesPath, "'."))); |
| return; |
| } |
| |
| resp.SetKeyInJsonBody( |
| "/CSRString", |
| "-----BEGIN CERTIFICATE REQUEST-----\nMIIC...<placeholder>...\n-----END " |
| "CERTIFICATE REQUEST-----\n"); |
| resp.SetKeyInJsonBody("/CertificateCollection/@odata.id", kCertificatesPath); |
| } |
| |
| void HandleCertificateService(const RedfishRequest& req, |
| RedfishResponse& resp) { |
| resp.SetKeyInJsonBody("/@odata.id", "/redfish/v1/CertificateService"); |
| resp.SetKeyInJsonBody("/@odata.type", |
| "#CertificateService.v1_1_0.CertificateService"); |
| resp.SetKeyInJsonBody("/Name", "Certificate Service"); |
| resp.SetKeyInJsonBody("/Id", "CertificateService"); |
| resp.SetKeyInJsonBody("/Description", "Actions related to certificates."); |
| |
| json generate_csr = |
| json::object({{"target", |
| "/redfish/v1/CertificateService/Actions/" |
| "CertificateService.GenerateCSR"}, |
| {"@Redfish.ActionInfo", |
| "/redfish/v1/CertificateService/GenerateCSRActionInfo"}}); |
| json replace_certificate = json::object( |
| {{"target", |
| "/redfish/v1/CertificateService/Actions/" |
| "CertificateService.ReplaceCertificate"}, |
| {"@Redfish.ActionInfo", |
| "/redfish/v1/CertificateService/ReplaceCertificateActionInfo"}}); |
| |
| resp.SetKeyInJsonBody( |
| "/Actions", |
| json::object( |
| {{"#CertificateService.GenerateCSR", generate_csr}, |
| {"#CertificateService.ReplaceCertificate", replace_certificate}})); |
| } |
| |
| } // namespace |
| |
| void RegisterRoutes(RedfishApp& app) { |
| TLBMC_ROUTE(app, "/redfish/v1/CertificateService/") |
| .methods(boost::beast::http::verb::get)(HandleCertificateService); |
| TLBMC_ROUTE(app, |
| "/redfish/v1/CertificateService/Actions/" |
| "CertificateService.GenerateCSR/") |
| .methods(boost::beast::http::verb::post)(HandleGenerateCsr); |
| TLBMC_ROUTE(app, |
| "/redfish/v1/CertificateService/Actions/" |
| "CertificateService.ReplaceCertificate/") |
| .methods(boost::beast::http::verb::post)(HandleReplaceCertificate); |
| TLBMC_ROUTE(app, "/redfish/v1/CertificateService/GenerateCSRActionInfo/") |
| .methods(boost::beast::http::verb::get)(HandleGenerateCsrActionInfo); |
| TLBMC_ROUTE(app, |
| "/redfish/v1/CertificateService/ReplaceCertificateActionInfo/") |
| .methods(boost::beast::http::verb::get)( |
| HandleReplaceCertificateActionInfo); |
| } |
| |
| } // namespace milotic_tlbmc::certificate_service |