diff options
-rw-r--r-- | bmcweb.service.in | 1 | ||||
-rw-r--r-- | crow/include/crow/app.h | 48 | ||||
-rw-r--r-- | crow/include/crow/http_server.h | 77 | ||||
-rw-r--r-- | include/ssl_key_handler.hpp | 32 | ||||
-rw-r--r-- | redfish-core/include/redfish.hpp | 8 | ||||
-rw-r--r-- | redfish-core/lib/certificate_service.hpp | 804 | ||||
-rw-r--r-- | redfish-core/lib/network_protocol.hpp | 4 | ||||
-rw-r--r-- | redfish-core/lib/service_root.hpp | 5 | ||||
-rw-r--r-- | src/webserver_main.cpp | 9 |
9 files changed, 927 insertions, 61 deletions
diff --git a/bmcweb.service.in b/bmcweb.service.in index 644eccae5f..fdb023b809 100644 --- a/bmcweb.service.in +++ b/bmcweb.service.in @@ -5,6 +5,7 @@ Wants=network.target After=network.target [Service] +ExecReload=kill -s HUP $MAINPID ExecStart=@CMAKE_INSTALL_PREFIX@/bin/bmcweb Type=simple WorkingDirectory=/home/root diff --git a/crow/include/crow/app.h b/crow/include/crow/app.h index 95bbaed0bc..4bdf9ff852 100644 --- a/crow/include/crow/app.h +++ b/crow/include/crow/app.h @@ -99,12 +99,12 @@ template <typename... Middlewares> class Crow if (-1 == socketFd) { sslServer = std::move(std::make_unique<ssl_server_t>( - this, bindaddrStr, portUint, &middlewares, &sslContext, io)); + this, bindaddrStr, portUint, sslContext, &middlewares, io)); } else { sslServer = std::move(std::make_unique<ssl_server_t>( - this, socketFd, &middlewares, &sslContext, io)); + this, socketFd, sslContext, &middlewares, io)); } sslServer->setTickFunction(tickInterval, tickFunction); sslServer->run(); @@ -114,12 +114,12 @@ template <typename... Middlewares> class Crow if (-1 == socketFd) { server = std::move(std::make_unique<server_t>( - this, bindaddrStr, portUint, &middlewares, nullptr, io)); + this, bindaddrStr, portUint, nullptr, &middlewares, io)); } else { server = std::move(std::make_unique<server_t>( - this, socketFd, &middlewares, nullptr, io)); + this, socketFd, nullptr, &middlewares, io)); } server->setTickFunction(tickInterval, tickFunction); server->run(); @@ -153,36 +153,42 @@ template <typename... Middlewares> class Crow self_t& sslFile(const std::string& crt_filename, const std::string& key_filename) { - sslContext.set_verify_mode(boost::asio::ssl::verify_peer); - sslContext.use_certificate_file(crt_filename, ssl_context_t::pem); - sslContext.use_private_key_file(key_filename, ssl_context_t::pem); - sslContext.set_options(boost::asio::ssl::context::default_workarounds | - boost::asio::ssl::context::no_sslv2 | - boost::asio::ssl::context::no_sslv3 | - boost::asio::ssl::context::no_tlsv1 | - boost::asio::ssl::context::no_tlsv1_1); + sslContext = std::make_shared<ssl_context_t>( + boost::asio::ssl::context::tls_server); + sslContext->set_verify_mode(boost::asio::ssl::verify_peer); + sslContext->use_certificate_file(crt_filename, ssl_context_t::pem); + sslContext->use_private_key_file(key_filename, ssl_context_t::pem); + sslContext->set_options(boost::asio::ssl::context::default_workarounds | + boost::asio::ssl::context::no_sslv2 | + boost::asio::ssl::context::no_sslv3 | + boost::asio::ssl::context::no_tlsv1 | + boost::asio::ssl::context::no_tlsv1_1); return *this; } self_t& sslFile(const std::string& pem_filename) { - sslContext.set_verify_mode(boost::asio::ssl::verify_peer); - sslContext.load_verify_file(pem_filename); - sslContext.set_options(boost::asio::ssl::context::default_workarounds | - boost::asio::ssl::context::no_sslv2 | - boost::asio::ssl::context::no_sslv3 | - boost::asio::ssl::context::no_tlsv1 | - boost::asio::ssl::context::no_tlsv1_1); + sslContext = std::make_shared<ssl_context_t>( + boost::asio::ssl::context::tls_server); + sslContext->set_verify_mode(boost::asio::ssl::verify_peer); + sslContext->load_verify_file(pem_filename); + sslContext->set_options(boost::asio::ssl::context::default_workarounds | + boost::asio::ssl::context::no_sslv2 | + boost::asio::ssl::context::no_sslv3 | + boost::asio::ssl::context::no_tlsv1 | + boost::asio::ssl::context::no_tlsv1_1); return *this; } - self_t& ssl(boost::asio::ssl::context&& ctx) + self_t& ssl(std::shared_ptr<boost::asio::ssl::context>&& ctx) { sslContext = std::move(ctx); + BMCWEB_LOG_INFO << "app::ssl context use_count=" + << sslContext.use_count(); return *this; } - ssl_context_t sslContext{boost::asio::ssl::context::tls_server}; + std::shared_ptr<ssl_context_t> sslContext = nullptr; #else template <typename T, typename... Remain> self_t& ssl_file(T&&, Remain&&...) diff --git a/crow/include/crow/http_server.h b/crow/include/crow/http_server.h index 36bab51438..d8a258f0f8 100644 --- a/crow/include/crow/http_server.h +++ b/crow/include/crow/http_server.h @@ -15,8 +15,10 @@ #include <boost/date_time/posix_time/posix_time.hpp> #include <chrono> #include <cstdint> +#include <filesystem> #include <future> #include <memory> +#include <ssl_key_handler.hpp> #include <utility> #include <vector> @@ -35,39 +37,39 @@ class Server { public: Server(Handler* handler, std::unique_ptr<tcp::acceptor>&& acceptor, + std::shared_ptr<boost::asio::ssl::context>& adaptor_ctx, std::tuple<Middlewares...>* middlewares = nullptr, - boost::asio::ssl::context* adaptor_ctx = nullptr, std::shared_ptr<boost::asio::io_context> io = std::make_shared<boost::asio::io_context>()) : ioService(std::move(io)), - acceptor(std::move(acceptor)), signals(*ioService, SIGINT, SIGTERM), - tickTimer(*ioService), handler(handler), middlewares(middlewares), - adaptorCtx(adaptor_ctx) + acceptor(std::move(acceptor)), + signals(*ioService, SIGINT, SIGTERM, SIGHUP), tickTimer(*ioService), + handler(handler), adaptorCtx(adaptor_ctx), middlewares(middlewares) { } Server(Handler* handler, const std::string& bindaddr, uint16_t port, + std::shared_ptr<boost::asio::ssl::context>& adaptor_ctx, std::tuple<Middlewares...>* middlewares = nullptr, - boost::asio::ssl::context* adaptor_ctx = nullptr, std::shared_ptr<boost::asio::io_context> io = std::make_shared<boost::asio::io_context>()) : Server(handler, std::make_unique<tcp::acceptor>( *io, tcp::endpoint(boost::asio::ip::make_address(bindaddr), port)), - middlewares, adaptor_ctx, io) + adaptor_ctx, middlewares, io) { } Server(Handler* handler, int existing_socket, + std::shared_ptr<boost::asio::ssl::context>& adaptor_ctx, std::tuple<Middlewares...>* middlewares = nullptr, - boost::asio::ssl::context* adaptor_ctx = nullptr, std::shared_ptr<boost::asio::io_context> io = std::make_shared<boost::asio::io_context>()) : Server(handler, std::make_unique<tcp::acceptor>(*io, boost::asio::ip::tcp::v6(), existing_socket), - middlewares, adaptor_ctx, io) + adaptor_ctx, middlewares, io) { } @@ -109,6 +111,7 @@ class Server void run() { + loadCertificate(); updateDateStr(); getCachedDateStr = [this]() -> std::string { @@ -153,11 +156,61 @@ class Server BMCWEB_LOG_INFO << serverName << " server is running, local endpoint " << acceptor->local_endpoint(); + startAsyncWaitForSignal(); + doAccept(); + } - signals.async_wait([&](const boost::system::error_code& /*error*/, - int /*signal_number*/) { stop(); }); + void loadCertificate() + { +#ifdef BMCWEB_ENABLE_SSL + namespace fs = std::filesystem; + // Cleanup older certificate file existing in the system + fs::path oldCert = "/home/root/server.pem"; + if (fs::exists(oldCert)) + { + fs::remove("/home/root/server.pem"); + } + fs::path certPath = "/etc/ssl/certs/https/"; + // if path does not exist create the path so that + // self signed certificate can be created in the + // path + if (!fs::exists(certPath)) + { + fs::create_directories(certPath); + } + fs::path certFile = certPath / "server.pem"; + BMCWEB_LOG_INFO << "Building SSL Context file=" << certFile; + std::string sslPemFile(certFile); + ensuressl::ensureOpensslKeyPresentAndValid(sslPemFile); + std::shared_ptr<boost::asio::ssl::context> sslContext = + ensuressl::getSslContext(sslPemFile); + adaptorCtx = sslContext; + handler->ssl(std::move(sslContext)); +#endif + } - doAccept(); + void startAsyncWaitForSignal() + { + signals.async_wait([this](const boost::system::error_code& ec, + int signalNo) { + if (ec) + { + BMCWEB_LOG_INFO << "Error in signal handler" << ec.message(); + } + else + { + if (signalNo == SIGHUP) + { + BMCWEB_LOG_INFO << "Receivied reload signal"; + loadCertificate(); + this->startAsyncWaitForSignal(); + } + else + { + stop(); + } + } + }); } void stop() @@ -240,6 +293,6 @@ class Server #ifdef BMCWEB_ENABLE_SSL bool useSsl{false}; #endif - boost::asio::ssl::context* adaptorCtx; + std::shared_ptr<boost::asio::ssl::context> adaptorCtx; }; // namespace crow } // namespace crow diff --git a/include/ssl_key_handler.hpp b/include/ssl_key_handler.hpp index e309d70a30..2b67661d62 100644 --- a/include/ssl_key_handler.hpp +++ b/include/ssl_key_handler.hpp @@ -279,28 +279,30 @@ inline void ensureOpensslKeyPresentAndValid(const std::string &filepath) } } -inline boost::asio::ssl::context getSslContext(const std::string &ssl_pem_file) +inline std::shared_ptr<boost::asio::ssl::context> + getSslContext(const std::string &ssl_pem_file) { - boost::asio::ssl::context mSslContext{ - boost::asio::ssl::context::tls_server}; - mSslContext.set_options(boost::asio::ssl::context::default_workarounds | - boost::asio::ssl::context::no_sslv2 | - boost::asio::ssl::context::no_sslv3 | - boost::asio::ssl::context::single_dh_use | - boost::asio::ssl::context::no_tlsv1 | - boost::asio::ssl::context::no_tlsv1_1); + std::shared_ptr<boost::asio::ssl::context> mSslContext = + std::make_shared<boost::asio::ssl::context>( + boost::asio::ssl::context::tls_server); + mSslContext->set_options(boost::asio::ssl::context::default_workarounds | + boost::asio::ssl::context::no_sslv2 | + boost::asio::ssl::context::no_sslv3 | + boost::asio::ssl::context::single_dh_use | + boost::asio::ssl::context::no_tlsv1 | + boost::asio::ssl::context::no_tlsv1_1); // m_ssl_context.set_verify_mode(boost::asio::ssl::verify_peer); - mSslContext.use_certificate_file(ssl_pem_file, - boost::asio::ssl::context::pem); - mSslContext.use_private_key_file(ssl_pem_file, - boost::asio::ssl::context::pem); + mSslContext->use_certificate_file(ssl_pem_file, + boost::asio::ssl::context::pem); + mSslContext->use_private_key_file(ssl_pem_file, + boost::asio::ssl::context::pem); // Set up EC curves to auto (boost asio doesn't have a method for this) // There is a pull request to add this. Once this is included in an asio // drop, use the right way // http://stackoverflow.com/questions/18929049/boost-asio-with-ecdsa-certificate-issue - if (SSL_CTX_set_ecdh_auto(mSslContext.native_handle(), 1) != 1) + if (SSL_CTX_set_ecdh_auto(mSslContext->native_handle(), 1) != 1) { BMCWEB_LOG_ERROR << "Error setting tmp ecdh list\n"; } @@ -316,7 +318,7 @@ inline boost::asio::ssl::context getSslContext(const std::string &ssl_pem_file) "ECDHE-ECDSA-AES128-SHA256:" "ECDHE-RSA-AES128-SHA256"; - if (SSL_CTX_set_cipher_list(mSslContext.native_handle(), + if (SSL_CTX_set_cipher_list(mSslContext->native_handle(), mozillaModern.c_str()) != 1) { BMCWEB_LOG_ERROR << "Error setting cipher list\n"; diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp index 91578f13c3..9edffa9692 100644 --- a/redfish-core/include/redfish.hpp +++ b/redfish-core/include/redfish.hpp @@ -16,6 +16,7 @@ #pragma once #include "../lib/account_service.hpp" +#include "../lib/certificate_service.hpp" #include "../lib/chassis.hpp" #include "../lib/cpudimm.hpp" #include "../lib/ethernet.hpp" @@ -115,7 +116,12 @@ class RedfishService nodes.emplace_back(std::make_unique<BaseMessageRegistry>(app)); nodes.emplace_back(std::make_unique<OpenBMCMessageRegistryFile>(app)); nodes.emplace_back(std::make_unique<OpenBMCMessageRegistry>(app)); - + nodes.emplace_back(std::make_unique<CertificateService>(app)); + nodes.emplace_back( + std::make_unique<CertificateActionsReplaceCertificate>(app)); + nodes.emplace_back(std::make_unique<CertificateLocations>(app)); + nodes.emplace_back(std::make_unique<HTTPSCertificateCollection>(app)); + nodes.emplace_back(std::make_unique<HTTPSCertificate>(app)); for (const auto& node : nodes) { node->initPrivileges(); diff --git a/redfish-core/lib/certificate_service.hpp b/redfish-core/lib/certificate_service.hpp new file mode 100644 index 0000000000..8045e4ea91 --- /dev/null +++ b/redfish-core/lib/certificate_service.hpp @@ -0,0 +1,804 @@ +/* +// Copyright (c) 2018 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ +#pragma once + +#include "node.hpp" + +#include <variant> +namespace redfish +{ +namespace certs +{ +constexpr char const *httpsObjectPath = + "/xyz/openbmc_project/certs/server/https"; +constexpr char const *certInstallIntf = "xyz.openbmc_project.Certs.Install"; +constexpr char const *certReplaceIntf = "xyz.openbmc_project.Certs.Replace"; +constexpr char const *certPropIntf = "xyz.openbmc_project.Certs.Certificate"; +constexpr char const *dbusPropIntf = "org.freedesktop.DBus.Properties"; +constexpr char const *dbusObjManagerIntf = "org.freedesktop.DBus.ObjectManager"; +constexpr char const *mapperBusName = "xyz.openbmc_project.ObjectMapper"; +constexpr char const *mapperObjectPath = "/xyz/openbmc_project/object_mapper"; +constexpr char const *mapperIntf = "xyz.openbmc_project.ObjectMapper"; +} // namespace certs + +/** + * The Certificate schema defines a Certificate Service which represents the + * actions available to manage certificates and links to where certificates + * are installed. + */ +class CertificateService : public Node +{ + public: + CertificateService(CrowApp &app) : + Node(app, "/redfish/v1/CertificateService/") + { + // TODO: Issue#61 No entries are available for Certificate + // sevice at https://www.dmtf.org/standards/redfish + // "redfish standard registries". Need to modify after DMTF + // publish Privilege details for certificate service + entityPrivileges = { + {boost::beast::http::verb::get, {{"Login"}}}, + {boost::beast::http::verb::head, {{"Login"}}}, + {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, + {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, + {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, + {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; + } + + private: + void doGet(crow::Response &res, const crow::Request &req, + const std::vector<std::string> ¶ms) override + { + res.jsonValue = { + {"@odata.type", "#CertificateService.v1_0_0.CertificateService"}, + {"@odata.id", "/redfish/v1/CertificateService"}, + {"@odata.context", + "/redfish/v1/$metadata#CertificateService.CertificateService"}, + {"Id", "CertificateService"}, + {"Name", "Certificate Service"}, + {"Description", "Actions available to manage certificates"}}; + res.jsonValue["CertificateLocations"] = { + {"@odata.id", + "/redfish/v1/CertificateService/CertificateLocations"}}; + res.jsonValue["Actions"]["#CertificateService.ReplaceCertificate"] = { + {"target", "/redfish/v1/CertificateService/Actions/" + "CertificateService.ReplaceCertificate"}, + {"CertificateType@Redfish.AllowableValues", {"PEM"}}}; + res.end(); + } +}; // CertificateService +/** + * @brief Find the ID specified in the URL + * Finds the numbers specified after the last "/" in the URL and returns. + * @param[in] path URL + * @return -1 on failure and number on success + */ +long getIDFromURL(const std::string_view url) +{ + std::size_t found = url.rfind("/"); + if (found == std::string::npos) + { + return -1; + } + if ((found + 1) < url.length()) + { + char *endPtr; + std::string_view str = url.substr(found + 1); + long value = std::strtol(str.data(), &endPtr, 10); + if (endPtr != &str.back()) + { + return -1; + } + return value; + } + return -1; +} + +/** + * Class to create a temporary certificate file for uploading to system + */ +class CertificateFile +{ + public: + CertificateFile() = delete; + CertificateFile(const CertificateFile &) = delete; + CertificateFile &operator=(const CertificateFile &) = delete; + CertificateFile(CertificateFile &&) = delete; + CertificateFile &operator=(CertificateFile &&) = delete; + CertificateFile(const std::string &certString) + { + char dirTemplate[] = "/tmp/Certs.XXXXXX"; + char *tempDirectory = mkdtemp(dirTemplate); + if (tempDirectory) + { + certDirectory = tempDirectory; + certificateFile = certDirectory / "cert.pem"; + std::ofstream out(certificateFile, std::ofstream::out | + std::ofstream::binary | + std::ofstream::trunc); + out << certString; + out.close(); + BMCWEB_LOG_DEBUG << "Creating certificate file" << certificateFile; + } + } + ~CertificateFile() + { + if (std::filesystem::exists(certDirectory)) + { + BMCWEB_LOG_DEBUG << "Removing certificate file" << certificateFile; + try + { + std::filesystem::remove_all(certDirectory); + } + catch (const std::filesystem::filesystem_error &e) + { + BMCWEB_LOG_ERROR << "Failed to remove temp directory" + << certDirectory; + } + } + } + std::string getCertFilePath() + { + return certificateFile; + } + + private: + std::filesystem::path certificateFile; + std::filesystem::path certDirectory; +}; + +/** + * @brief Parse and update Certficate Issue/Subject property + * + * @param[in] asyncResp Shared pointer to the response message + * @param[in] str Issuer/Subject value in key=value pairs + * @param[in] type Issuer/Subject + * @return None + */ +static void updateCertIssuerOrSubject(nlohmann::json &out, + const std::string_view value) +{ + // example: O=openbmc-project.xyz,CN=localhost + std::string_view::iterator i = value.begin(); + while (i != value.end()) + { + std::string_view::iterator tokenBegin = i; + while (i != value.end() && *i != '=') + { + i++; + } + if (i == value.end()) + { + break; + } + const std::string_view key(tokenBegin, i - tokenBegin); + i++; + tokenBegin = i; + while (i != value.end() && *i != ',') + { + i++; + } + const std::string_view val(tokenBegin, i - tokenBegin); + if (key == "L") + { + out["City"] = val; + } + else if (key == "CN") + { + out["CommonName"] = val; + } + else if (key == "C") + { + out["Country"] = val; + } + else if (key == "O") + { + out["Organization"] = val; + } + else if (key == "OU") + { + out["OrganizationalUnit"] = val; + } + else if (key == "ST") + { + out["State"] = val; + } + // skip comma character + if (i != value.end()) + { + i++; + } + } +} + +/** + * @brief Retrieve the certificates properties and append to the response + * message + * + * @param[in] asyncResp Shared pointer to the response message + * @param[in] objectPath Path of the D-Bus service object + * @param[in] certId Id of the certificate + * @param[in] certURL URL of the certificate object + * @param[in] name name of the certificate + * @return None + */ +static void getCertificateProperties( + const std::shared_ptr<AsyncResp> &asyncResp, const std::string &objectPath, + long certId, const std::string &certURL, const std::string &name) +{ + using PropertyType = + std::variant<std::string, uint64_t, std::vector<std::string>>; + using PropertiesMap = boost::container::flat_map<std::string, PropertyType>; + BMCWEB_LOG_DEBUG << "getCertificateProperties Path=" << objectPath + << " certId=" << certId << " certURl=" << certURL; + crow::connections::systemBus->async_method_call( + [asyncResp, objectPath, certURL, certId, + name](const boost::system::error_code ec, const GetObjectType &resp) { + if (ec) + { + BMCWEB_LOG_ERROR << "DBUS response error: " << ec; + messages::internalError(asyncResp->res); + return; + } + if (resp.size() > 1 || resp.empty()) + { + BMCWEB_LOG_ERROR << "Invalid number of objects found " + << resp.size(); + messages::internalError(asyncResp->res); + return; + } + const std::string &service = resp.begin()->first; + crow::connections::systemBus->async_method_call( + [asyncResp, certURL, certId, + name](const boost::system::error_code ec, + const PropertiesMap &properties) { + if (ec) + { + BMCWEB_LOG_ERROR << "DBUS response error: " << ec; + messages::internalError(asyncResp->res); + return; + } + asyncResp->res.jsonValue = { + {"@odata.id", certURL}, + {"@odata.type", "#Certificate.v1_0_0.Certificate"}, + {"@odata.context", + "/redfish/v1/$metadata#Certificate.Certificate"}, + {"Id", std::to_string(certId)}, + {"Name", name}, + {"Description", name}}; + for (const auto &property : properties) + { + if (property.first == "CertificateString") + { + asyncResp->res.jsonValue["CertificateString"] = ""; + const std::string *value = + std::get_if<std::string>(&property.second); + if (value) + { + asyncResp->res.jsonValue["CertificateString"] = + *value; + } + } + else if (property.first == "KeyUsage") + { + nlohmann::json &keyUsage = + asyncResp->res.jsonValue["KeyUsage"]; + keyUsage = nlohmann::json::array(); + const std::vector<std::string> *value = + std::get_if<std::vector<std::string>>( + &property.second); + if (value) + { + for (const std::string &usage : *value) + { + keyUsage.push_back(usage); + } + } + } + else if (property.first == "Issuer") + { + const std::string *value = + std::get_if<std::string>(&property.second); + if (value) + { + updateCertIssuerOrSubject( + asyncResp->res.jsonValue["Issuer"], *value); + } + } + else if (property.first == "Subject") + { + const std::string *value = + std::get_if<std::string>(&property.second); + if (value) + { + updateCertIssuerOrSubject( + asyncResp->res.jsonValue["Subject"], + *value); + } + } + else if (property.first == "ValidNotAfter") + { + const uint64_t *value = + std::get_if<uint64_t>(&property.second); + if (value) + { + std::time_t time = + static_cast<std::time_t>(*value); + asyncResp->res.jsonValue["ValidNotAfter"] = + crow::utility::getDateTime(time); + } + } + else if (property.first == "ValidNotBefore") + { + const uint64_t *value = + std::get_if<uint64_t>(&property.second); + if (value) + { + std::time_t time = + static_cast<std::time_t>(*value); + asyncResp->res.jsonValue["ValidNotBefore"] = + crow::utility::getDateTime(time); + } + } + } + asyncResp->res.addHeader("Location", certURL); + }, + service, objectPath, certs::dbusPropIntf, "GetAll", + certs::certPropIntf); + }, + certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf, + "GetObject", objectPath, + std::array<const char *, 1>{certs::certPropIntf}); +} + +using GetObjectType = + std::vector<std::pair<std::string, std::vector<std::string>>>; + +/** + * Action to replace an existing certificate + */ +class CertificateActionsReplaceCertificate : public Node +{ + public: + CertificateActionsReplaceCertificate(CrowApp &app) : + Node(app, "/redfish/v1/CertificateService/Actions/" + "CertificateService.ReplaceCertificate/") + { + entityPrivileges = { + {boost::beast::http::verb::get, {{"Login"}}}, + {boost::beast::http::verb::head, {{"Login"}}}, + {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, + {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, + {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, + {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; + } + + private: + void doPost(crow::Response &res, const crow::Request &req, + const std::vector<std::string> ¶ms) override + { + std::string certificate; + nlohmann::json certificateUri; + std::optional<std::string> certificateType = "PEM"; + auto asyncResp = std::make_shared<AsyncResp>(res); + if (!json_util::readJson(req, asyncResp->res, "CertificateString", + certificate, "CertificateUri", certificateUri, + "CertificateType", certificateType)) + { + BMCWEB_LOG_ERROR << "Required parameters are missing"; + messages::internalError(asyncResp->res); + return; + } + + if (!certificateType) + { + // should never happen, but it never hurts to be paranoid. + return; + } + if (certificateType != "PEM") + { + messages::actionParameterNotSupported( + asyncResp->res, "CertificateType", "ReplaceCertificate"); + return; + } + + std::string certURI; + if (!redfish::json_util::readJson(certificateUri, asyncResp->res, + "@odata.id", certURI)) + { + messages::actionParameterMissing( + asyncResp->res, "ReplaceCertificate", "CertificateUri"); + return; + } + + if (!boost::starts_with( + certURI, + "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")) + { + BMCWEB_LOG_ERROR << "Unsupported certificate URI" << certURI; + messages::actionParameterValueFormatError(asyncResp->res, certURI, + "CertificateUri", + "ReplaceCertificate"); + return; + } + + BMCWEB_LOG_INFO << "Certificate URI to replace" << certURI; + long id = getIDFromURL(certURI); + if (id < 0) + { + messages::actionParameterValueFormatError(asyncResp->res, certURI, + "CertificateUri", + "ReplaceCertificate"); + return; + } + std::string objectPath; + std::string name; + if (boost::starts_with( + certURI, + "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")) + { + objectPath = + std::string(certs::httpsObjectPath) + "/" + std::to_string(id); + name = "HTTPS certificate"; + } + else + { + messages::actionParameterNotSupported( + asyncResp->res, "CertificateUri", "ReplaceCertificate"); + return; + } + + std::shared_ptr<CertificateFile> certFile = + std::make_shared<CertificateFile>(certificate); + + crow::connections::systemBus->async_method_call( + [asyncResp, objectPath, certFile, id, certURI, name]( + const boost::system::error_code ec, const GetObjectType &resp) { + if (ec) + { + BMCWEB_LOG_ERROR << "DBUS response error: " << ec; + messages::internalError(asyncResp->res); + return; + } + if (resp.size() > 1 || resp.empty()) + { + BMCWEB_LOG_ERROR << "Invalid number of objects found " + << resp.size(); + messages::internalError(asyncResp->res); + return; + } + const std::string &service = resp.begin()->first; + crow::connections::systemBus->async_method_call( + [asyncResp, certFile, objectPath, certURI, id, + name](const boost::system::error_code ec) { + if (ec) + { + BMCWEB_LOG_ERROR << "DBUS response error: " << ec; + messages::internalError(asyncResp->res); + return; + } + getCertificateProperties(asyncResp, objectPath, id, + certURI, name); + BMCWEB_LOG_DEBUG << "HTTPS certificate install file=" + << certFile->getCertFilePath(); + }, + service, objectPath, certs::certReplaceIntf, "Replace", + certFile->getCertFilePath()); + }, + certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf, + "GetObject", objectPath, + std::array<std::string, 1>({certs::certReplaceIntf})); + } +}; // CertificateActionsReplaceCertificate + +/** + * Certificate resource describes a certificate used to prove the identity + * of a component, account or service. + */ +class HTTPSCertificate : public Node +{ + public: + template <typename CrowApp> + HTTPSCertificate(CrowApp &app) : + Node(app, + "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" + "<str>/", + std::string()) + { + entityPrivileges = { + {boost::beast::http::verb::get, {{"Login"}}}, + {boost::beast::http::verb::head, {{"Login"}}}, + {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, + {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, + {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, + {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; + } + + void doGet(crow::Response &res, const crow::Request &req, + const std::vector<std::string> ¶ms) override + { + auto asyncResp = std::make_shared<AsyncResp>(res); + if (params.size() != 1) + { + messages::internalError(asyncResp->res); + return; + } + long id = getIDFromURL(req.url); + + BMCWEB_LOG_DEBUG << "HTTPSCertificate::doGet ID=" << std::to_string(id); + std::string certURL = + "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" + + std::to_string(id); + std::string objectPath = certs::httpsObjectPath; + objectPath += "/"; + objectPath += std::to_string(id); + getCertificateProperties(asyncResp, objectPath, id, certURL, + "HTTPS Certificate"); + } + +}; // namespace redfish + +/** + * Collection of HTTPS certificates + */ +class HTTPSCertificateCollection : public Node +{ + public: + template <typename CrowApp> + HTTPSCertificateCollection(CrowApp &app) : + Node(app, + "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/") + { + entityPrivileges = { + {boost::beast::http::verb::get, {{"Login"}}}, + {boost::beast::http::verb::head, {{"Login"}}}, + {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, + {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, + {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, + {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; + } + void doGet(crow::Response &res, const crow::Request &req, + const std::vector<std::string> ¶ms) override + { + res.jsonValue = { + {"@odata.id", + "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"}, + {"@odata.type", "#CertificateCollection.CertificateCollection"}, + {"@odata.context", + "/redfish/v1/" + "$metadata#CertificateCollection.CertificateCollection"}, + {"Name", "HTTPS Certificates Collection"}, + {"Description", "A Collection of HTTPS certificate instances"}}; + auto asyncResp = std::make_shared<AsyncResp>(res); + crow::connections::systemBus->async_method_call( + [asyncResp](const boost::system::error_code ec, + const GetObjectType &resp) { + if (ec) + { + BMCWEB_LOG_ERROR << "DBUS response error: " << ec; + messages::internalError(asyncResp->res); + return; + } + if (resp.size() > 1 || resp.empty()) + { + BMCWEB_LOG_ERROR << "Invalid number of objects found " + << resp.size(); + messages::internalError(asyncResp->res); + return; + } + const std::string &service = resp.begin()->first; + crow::connections::systemBus->async_method_call( + [asyncResp](const boost::system::error_code ec, + const ManagedObjectType &certs) { + if (ec) + { + BMCWEB_LOG_ERROR << "DBUS response error: " << ec; + messages::internalError(asyncResp->res); + return; + } + nlohmann::json &members = + asyncResp->res.jsonValue["Members"]; + members = nlohmann::json::array(); + for (const auto &cert : certs) + { + long id = getIDFromURL(cert.first.str); + if (id != -1) + { + members.push_back( + {{"@odata.id", + "/redfish/v1/Managers/bmc/" + "NetworkProtocol/HTTPS/Certificates/" + + std::to_string(id)}}); + } + } + asyncResp->res.jsonValue["Members@odata.count"] = + members.size(); + }, + service, certs::httpsObjectPath, certs::dbusObjManagerIntf, + "GetManagedObjects"); + }, + certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf, + "GetObject", certs::httpsObjectPath, + std::array<const char *, 1>{certs::certInstallIntf}); + } + + void doPost(crow::Response &res, const crow::Request &req, + const std::vector<std::string> ¶ms) override + { + BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost"; + auto asyncResp = std::make_shared<AsyncResp>(res); + asyncResp->res.jsonValue = {{"Name", "HTTPS Certificate"}, + {"Description", "HTTPS Certificate"}}; + + std::shared_ptr<CertificateFile> certFile = + std::make_shared<CertificateFile>(req.body); + + crow::connections::systemBus->async_method_call( + [asyncResp, certFile](const boost::system::error_code ec, + const GetObjectType &resp) { + if (ec) + { + BMCWEB_LOG_ERROR << "DBUS response error: " << ec; + messages::internalError(asyncResp->res); + return; + } + if (resp.size() > 1 || resp.empty()) + { + BMCWEB_LOG_ERROR << "Invalid number of objects found " + << resp.size(); + messages::internalError(asyncResp->res); + return; + } + const std::string &service = resp.begin()->first; + crow::connections::systemBus->async_method_call( + [asyncResp, certFile](const boost::system::error_code ec) { + if (ec) + { + BMCWEB_LOG_ERROR << "DBUS response error: " << ec; + messages::internalError(asyncResp->res); + return; + } + // TODO: Issue#84 supporting only 1 certificate + long certId = 1; + std::string certURL = + "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/" + "Certificates/" + + std::to_string(certId); + std::string objectPath = + std::string(certs::httpsObjectPath) + "/" + + std::to_string(certId); + getCertificateProperties(asyncResp, objectPath, certId, + certURL, "HTTPS Certificate"); + BMCWEB_LOG_DEBUG << "HTTPS certificate install file=" + << certFile->getCertFilePath(); + }, + service, certs::httpsObjectPath, certs::certInstallIntf, + "Install", certFile->getCertFilePath()); + }, + certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf, + "GetObject", certs::httpsObjectPath, + std::array<const char *, 1>{certs::certInstallIntf}); + } +}; // HTTPSCertificateCollection + +/** + * @brief Retrieve the certificates installed list and append to the response + * + * @param[in] asyncResp Shared pointer to the response message + * @param[in] certURL Path of the certificate object + * @param[in] path Path of the D-Bus service object + * @return None + */ +static void getCertificateLocations(std::shared_ptr<AsyncResp> &asyncResp, + const std::string &certURL, + const std::string &path) +{ + BMCWEB_LOG_DEBUG << "getCertificateLocations URI=" << certURL + << " Path=" << path; + crow::connections::systemBus->async_method_call( + [asyncResp, path, certURL](const boost::system::error_code ec, + const GetObjectType &resp) { + if (ec) + { + BMCWEB_LOG_ERROR << "DBUS response error: " << ec; + messages::internalError(asyncResp->res); + return; + } + if (resp.size() > 1 || resp.empty()) + { + BMCWEB_LOG_ERROR << "Invalid number of objects found " + << resp.size(); + messages::internalError(asyncResp->res); + return; + } + const std::string &service = resp.begin()->first; + crow::connections::systemBus->async_method_call( + [asyncResp, certURL](const boost::system::error_code ec, + const ManagedObjectType &certs) { + if (ec) + { + BMCWEB_LOG_ERROR << "DBUS response error: " << ec; + messages::internalError(asyncResp->res); + return; + } + nlohmann::json &links = + asyncResp->res.jsonValue["Links"]["Certificates"]; + for (auto &cert : certs) + { + long id = getIDFromURL(cert.first.str); + if (id != -1) + { + links.push_back( + {{"@odata.id", certURL + std::to_string(id)}}); + } + } + asyncResp->res + .jsonValue["Links"]["Certificates@odata.count"] = + links.size(); + }, + service, path, certs::dbusObjManagerIntf, "GetManagedObjects"); + }, + certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf, + "GetObject", path, std::array<std::string, 0>()); +} + +/** + * The certificate location schema defines a resource that an administrator + * can use in order to locate all certificates installed on a given service. + */ +class CertificateLocations : public Node +{ + public: + template <typename CrowApp> + CertificateLocations(CrowApp &app) : + Node(app, "/redfish/v1/CertificateService/CertificateLocations/") + { + entityPrivileges = { + {boost::beast::http::verb::get, {{"Login"}}}, + {boost::beast::http::verb::head, {{"Login"}}}, + {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, + {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, + {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, + {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; + } + + private: + void doGet(crow::Response &res, const crow::Request &req, + const std::vector<std::string> ¶ms) override + { + res.jsonValue = { + {"@odata.id", + "/redfish/v1/CertificateService/CertificateLocations"}, + {"@odata.type", + "#CertificateLocations.v1_0_0.CertificateLocations"}, + {"@odata.context", + "/redfish/v1/$metadata#CertificateLocations.CertificateLocations"}, + {"Name", "Certificate Locations"}, + {"Id", "CertificateLocations"}, + {"Description", + "Defines a resource that an administrator can use in order to " + "locate all certificates installed on a given service"}}; + auto asyncResp = std::make_shared<AsyncResp>(res); + nlohmann::json &links = + asyncResp->res.jsonValue["Links"]["Certificates"]; + links = nlohmann::json::array(); + getCertificateLocations( + asyncResp, + "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/", + certs::httpsObjectPath); + } +}; // CertificateLocations +} // namespace redfish diff --git a/redfish-core/lib/network_protocol.hpp b/redfish-core/lib/network_protocol.hpp index d24f246109..23965faced 100644 --- a/redfish-core/lib/network_protocol.hpp +++ b/redfish-core/lib/network_protocol.hpp @@ -234,6 +234,9 @@ class NetworkProtocol : public Node messages::internalError(asyncResp->res); return; } + asyncResp->res.jsonValue["HTTPS"]["Certificates"] = { + {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol/" + "HTTPS/Certificates/"}}; for (auto& unit : resp) { @@ -252,7 +255,6 @@ class NetworkProtocol : public Node "running") || (std::get<NET_PROTO_UNIT_SUB_STATE>(unit) == "listening"); - crow::connections::systemBus->async_method_call( [asyncResp, service{std::string(service)}]( const boost::system::error_code ec, diff --git a/redfish-core/lib/service_root.hpp b/redfish-core/lib/service_root.hpp index ad2b2f2989..3b9adbf37c 100644 --- a/redfish-core/lib/service_root.hpp +++ b/redfish-core/lib/service_root.hpp @@ -42,7 +42,7 @@ class ServiceRoot : public Node void doGet(crow::Response& res, const crow::Request& req, const std::vector<std::string>& params) override { - res.jsonValue["@odata.type"] = "#ServiceRoot.v1_1_1.ServiceRoot"; + res.jsonValue["@odata.type"] = "#ServiceRoot.v1_5_0.ServiceRoot"; res.jsonValue["@odata.id"] = "/redfish/v1"; res.jsonValue["@odata.context"] = "/redfish/v1/$metadata#ServiceRoot.ServiceRoot"; @@ -65,8 +65,9 @@ class ServiceRoot : public Node res.jsonValue["UpdateService"] = { {"@odata.id", "/redfish/v1/UpdateService"}}; - res.jsonValue["UUID"] = uuid; + res.jsonValue["CertificateService"] = { + {"@odata.id", "/redfish/v1/CertificateService"}}; res.end(); } diff --git a/src/webserver_main.cpp b/src/webserver_main.cpp index a4cf582aa7..1c9c1f18d3 100644 --- a/src/webserver_main.cpp +++ b/src/webserver_main.cpp @@ -61,15 +61,6 @@ int main(int argc, char** argv) auto io = std::make_shared<boost::asio::io_context>(); CrowApp app(io); -#ifdef BMCWEB_ENABLE_SSL - std::string sslPemFile("server.pem"); - std::cout << "Building SSL Context\n"; - - ensuressl::ensureOpensslKeyPresentAndValid(sslPemFile); - std::cout << "SSL Enabled\n"; - auto sslContext = ensuressl::getSslContext(sslPemFile); - app.ssl(std::move(sslContext)); -#endif // Static assets need to be initialized before Authorization, because auth // needs to build the whitelist from the static routes |