blob: 4aa68ef919145535bb479b98c46b52d2f81004d6 [file] [log] [blame]
#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