blob: f6c0f414ae63f6907190350f8bd7a866ea96ec19 [file] [log] [blame]
#pragma once
// ExternalStorer allows external users (HTTP clients) to temporarily store
// their data on this Redfish server, hence its name.
// The intended use cases are for catching logging messages and hardware error
// notifications from the host, but ExternalStorer is not limited to these.
// The backing store for this data is a RAM disk (tmpfs), so it will be lost
// when the BMC reboots or powers down. Not intended for long-term storage.
// To comply with relevant Redfish schemas, ExternalStorer will carefully
// merge user-provided data with what is already on the system. Overwriting
// system-provided data is not allowed. There are 3 addressing levels:
// Hook = The integration point, an existing Redfish URL containing one
// writable collection, as allowed by a schema.
// Instance = An external user will POST to create a new collection here,
// which will be added to what is already visible at the Hook.
// Entry = An external user will POST to create a new entry here,
// which will be added to that collection (the Instance).
// Example usage:
// GET /redfish/v1/Systems/system/LogServices
// -> This is a hook, it contains a writable "Members" collection
// POST /redfish/v1/Systems/system/LogServices
// -> Created instance, let's assume BMC assigns the ID of "MY_LOG"
// GET /redfish/v1/Systems/system/LogServices/MY_LOG
// -> As per schema, this contains an additional path component "Entries"
// GET /redfish/v1/Systems/system/LogServices/MY_LOG/Entries
// -> This is a container, ready to go, contains empty "Members" collection
// POST /redfish/v1/Systems/system/LogServices/MY_LOG/Entries
// -> Created entry, let's assume BMC assigns the ID of "MY_ALERT"
// GET /redfish/v1/Systems/system/LogServices/MY_LOG/Entries/MY_ALERT
// -> This retrieves the data previously stored when creating that entry
// GET /redfish/v1/Systems/system/LogServices/MY_LOG/Entries
// -> This container is no longer empty, it now has one element
// GET /redfish/v1/Systems/system/LogServices/MY_LOG
// -> Retrieves some content previously stored when creating the instance
// GET /redfish/v1/Systems/system/LogServices
// -> The "Members" collection now contains "MY_LOG" in addition to before
// All data is expressed in the form of a JSON dictionary. The backing store
// uses a similar directory layout, including the extra "Entries"
// subdirectory. The JSON content for the collection itself is stored as two
// special-case "index.json" filenames within that collection's directories.
// The on-disk file format is whatever is provided by the defaults for the
// nlohmann::json::dump() and nlohmann::json::parse() functions.
// Filesystem layout:
// Directory /run/bmcweb/redfish/v1/HOOK/INSTANCE
// Directory /run/bmcweb/redfish/v1/HOOK/INSTANCE/MIDDLE
// Directory /run/bmcweb/redfish/v1/HOOK/INSTANCE/MIDDLE/ENTRY
// File /run/bmcweb/redfish/v1/HOOK/INSTANCE/index.json
// File /run/bmcweb/redfish/v1/HOOK/INSTANCE/MIDDLE/index.json
// File /run/bmcweb/redfish/v1/HOOK/INSTANCE/MIDDLE/ENTRY/index.json
// HOOK is hardcoded, trimmed from URL, example: "Systems/system/LogServices"
// MIDDLE is hardcoded, a single word, example: "Entries"
// INSTANCE and ENTRY are user-generated (within reason) or BMC-generated
// Each ENTRY index.json file contains JSON of one entry within an instance
// The two higher "index.json" files contain JSON of that instance itself
// SECURITY WARNING: There is currently no limit on the amount of storage
// taken, nor any automatic cleanup of old content, so clients can cause
// a denial of service attack by consuming all storage. This will be
// addressed by future work.
#include "app.hpp"
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <cmath>
#include <cstddef>
#include <fstream>
#include <sstream>
namespace external_storer
{
// These should become constexpr in a future compiler
inline const std::filesystem::path defPathPrefix{"/run/bmcweb"};
inline const std::filesystem::path redfishPrefix{"/redfish/v1"};
inline const std::filesystem::path jsonFilename{"index.json"};
// Maximum allowed file size, helps to avoid DoS attacks
inline const size_t maxFileSize{static_cast<size_t>(1024 * 1024)};
// This class only holds configuration and accounting data for the hook.
// As for user-provided data, it is intentionally not here, as it is always
// fetched from the filesystem backing store when needed.
class Hook
{
private:
// Relative location of hook, after redfishPrefix, before instance
std::filesystem::path pathBase;
// Optional middle keyword, required by some schemas, example "Entries"
std::filesystem::path pathMiddle;
// Disallow these user instances, avoid already-existing path components
std::vector<std::string> denyList;
// Automatically expand these fields when listing the array of entries
std::vector<std::string> expandList;
// The root directory for local storage, changeable only for testing
std::filesystem::path pathPrefix;
public:
Hook(const std::string& b, const std::string& m,
const std::vector<std::string>& d, const std::vector<std::string>& e) :
pathBase(b),
pathMiddle(m), denyList(d), expandList(e), pathPrefix(defPathPrefix)
{}
void handleCreateInstance(
const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp);
void handleCreateMiddle(const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& instance);
void handleCreateEntry(const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& instance,
const std::string& middle);
// The 0-argument Get handled by just-in-time insert at integration point
void handleGetInstance(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& instance);
void handleGetMiddle(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& instance,
const std::string& middle);
void handleGetEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const nlohmann::json::json_pointer& jsonPtr,
const std::string& instance, const std::string& middle,
const std::string& entry);
// For use by the integration point
std::vector<std::string> listInstances() const;
// Utility functions for building up locations
std::filesystem::path locBase() const;
std::filesystem::path locInstance(const std::string& instance) const;
std::filesystem::path locMiddle(const std::string& instance) const;
std::filesystem::path locEntry(const std::string& instance,
const std::string& entry) const;
std::filesystem::path locToFileDir(const std::filesystem::path& loc) const;
std::filesystem::path locToFileJson(const std::filesystem::path& loc) const;
// For use only during testing
void deleteAll();
void setPathPrefix(const std::filesystem::path& newPrefix);
};
Hook makeLogServices();
std::shared_ptr<Hook>
rememberLogServices(const std::shared_ptr<Hook>& hookIncoming = nullptr);
} // namespace external_storer
namespace redfish
{
// The URL layout under LogServices requires "Entries" path component,
// which seems unnecessary, but is required by the schema.
// POST(HOOK) = create new instance
// POST(HOOK/INSTANCE) = create new entry | these 2 endpoints
// POST(HOOK/INSTANCE/Entries) = create new entry | do the same thing
// POST(HOOK/INSTANCE/Entries/ENTRY) = not allowed
// GET(HOOK) = supplement existing hook with our added instances
// GET(HOOK/INSTANCE) = return boilerplate of desired instance
// GET(HOOK/INSTANCE/Entries) = return Members array of all entries
// GET(HOOK/INSTANCE/Entries/ENTRY) = return content of desired entry
void requestRoutesExternalStorerLogServices(
App& app, const std::shared_ptr<external_storer::Hook>& hook);
// NOTE: Currently, this works, but by luck, perhaps due to the fact that
// the ExternalStorer routes are requested last. The router currently does
// not cleanly support overlapping routes. So, the wildcard GET matchers
// currently can not cleanly coexist with the various hardcoded string
// matchers (see denyList) at the same URL position. One possible
// solution is to add a priority system to disambiguate, as discussed here:
// https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/43502
void requestRoutesExternalStorer(App& app);
} // namespace redfish