Add tlBMC Redfish PATCH override route and dev-signed safety gating for sensors This change implements software sensor override management via Redfish: - Supports `PATCH /redfish/v1/Chassis/<ChassisId>/Sensors/<SensorId>` with payload `Oem.Google.SensorValueOverridden: true` and `Reading` to instantiate a software override (enforces that `Reading` must be present). - Supports `PATCH` with `Oem.Google.SensorValueOverridden: false` to clear an active override (enforces that `Reading` must NOT be present). - Conditionally decorates `GET` responses with `Oem.Google.SensorValueOverridden: true` only when a software override is active, omitting the field entirely when inactive. - Implements compile-time safety gating using `constexpr bool enableTlbmcSensorOverride` from `bmcweb_config.h` (configured via Meson option `tlbmc-sensor-override` and Bazel build flag `tlbmc_sensor_override`), with runtime environment variable fallback `ENABLE_TLBMC_SENSOR_OVERRIDE` for test environments. Returns HTTP 403 Forbidden (`Base.1.13.0.ActionNotSupported`) on production builds when disabled. Google-Bug-Id:550410302 PiperOrigin-RevId: 979525293 Change-Id: I1843082e86e8a0e6ff422f3f7a2c92199fdd7abf
diff --git a/config/bmcweb_config.h.in b/config/bmcweb_config.h.in index 396d7c6..440bbe7 100644 --- a/config/bmcweb_config.h.in +++ b/config/bmcweb_config.h.in
@@ -44,6 +44,7 @@ constexpr bool enableFastSanity = @ENABLE_FAST_SANITY@ == 1; constexpr const char* entityConfigLocation = "@ENTITY_CONFIG_LOCATION@"; constexpr bool tlbmcAllowSensorCreationFailure = @TLBMC_ALLOW_SENSOR_CREATION_FAILURE@ == 1; +constexpr bool enableTlbmcSensorOverride = @ENABLE_TLBMC_SENSOR_OVERRIDE@ == 1; constexpr bool enableRedfishSystemBootNumber = @ENABLE_REDFISH_SYSTEM_BOOTNUMBER@ == 1; constexpr bool enableConfigureCoreCount = @ENABLE_CONFIGURE_CORE_COUNT@ == 1; constexpr bool enableRedfishConfigureIfs = @ENABLE_REDFISH_CONFIGURE_IFS@ == 1;
diff --git a/config/meson.build b/config/meson.build index dd403a6..16ef745 100644 --- a/config/meson.build +++ b/config/meson.build
@@ -26,6 +26,7 @@ conf_data.set10('ENABLE_FAST_SANITY', get_option('fast-sanity').enabled()) conf_data.set('ENTITY_CONFIG_LOCATION', get_option('entity-config-location')) conf_data.set10('TLBMC_ALLOW_SENSOR_CREATION_FAILURE', get_option('tlbmc-allow-sensor-creation-failure').enabled()) +conf_data.set10('ENABLE_TLBMC_SENSOR_OVERRIDE', get_option('tlbmc-sensor-override').enabled()) conf_data.set10('ENABLE_REDFISH_SYSTEM_BOOTNUMBER', get_option('enable-redfish-system-bootnumber').enabled()) conf_data.set10('ENABLE_CONFIGURE_CORE_COUNT', get_option('enable-configure-core-count').enabled()) conf_data.set10('ENABLE_REDFISH_CONFIGURE_IFS', get_option('redfish-configure-ifs').enabled())
diff --git a/meson_options.txt b/meson_options.txt index bbfdc0a..ad0b3e0 100644 --- a/meson_options.txt +++ b/meson_options.txt
@@ -847,6 +847,13 @@ ) option( + 'tlbmc-sensor-override', + type: 'feature', + value: 'disabled', + description: 'Enable tlBMC sensor error injection and software overrides for tests. Do not enable in production builds' +) + +option( 'sysfs-base-path-ut', type : 'string', value : '/tmpfs/src/ci_workspace/openbmc-build-scripts/additional',
diff --git a/tlbmc/redfish/routes/sensor.cc b/tlbmc/redfish/routes/sensor.cc index a46d9e3..cd15e38 100644 --- a/tlbmc/redfish/routes/sensor.cc +++ b/tlbmc/redfish/routes/sensor.cc
@@ -9,6 +9,7 @@ #include <utility> #include <vector> +#include "absl/flags/flag.h" #include "absl/functional/bind_front.h" #include "absl/status/status.h" #include "absl/status/statusor.h" @@ -16,6 +17,7 @@ #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "absl/strings/substitute.h" +#include "bmcweb_config.h" #include "http_request.hpp" #include "async_resp.hpp" #include <nlohmann/json.hpp> @@ -35,6 +37,10 @@ #include "tlbmc/store/store.h" #include "smart_router.h" +ABSL_FLAG(bool, enable_tlbmc_sensor_override, enableTlbmcSensorOverride, + "Enable creation of tlBMC sensor overrides. Defaults to true when " + "compiled with tlbmc-sensor-override and false otherwise."); + namespace milotic_tlbmc::sensor { namespace { @@ -135,6 +141,120 @@ FillResponseWithSensorData(sensor, sensor_pointer, resp, store); } +void SetSuccessResponse(RedfishResponse& resp, absl::string_view message, + absl::string_view chassis_id, + absl::string_view sensor_id) { + nlohmann::json success = {{{"@odata.type", "#Message.v1_1_1.Message"}, + {"Message", message}, + {"MessageArgs", nlohmann::json::array_t()}, + {"MessageId", "Base.1.13.0.Success"}, + {"MessageSeverity", "OK"}, + {"Resolution", "None"}}}; + resp.SetKeyInJsonBody("/@Message.ExtendedInfo", success); + resp.SetKeyInJsonBody("/@odata.id", + CreateUrl({"redfish", "v1", "Chassis", chassis_id, + "Sensors", sensor_id})); +} + +const nlohmann::json* FindSensorOverrideControl(const nlohmann::json& body) { + auto oem_it = body.find("Oem"); + if (oem_it == body.end() || !oem_it->is_object()) { + return nullptr; + } + auto google_it = oem_it->find("Google"); + if (google_it == oem_it->end() || !google_it->is_object()) { + return nullptr; + } + auto override_it = google_it->find("SensorValueOverridden"); + if (override_it == google_it->end()) { + return nullptr; + } + return &(*override_it); +} + +absl::StatusOr<SensorValue> ParseReadingValue( + const nlohmann::json& reading_json, const Sensor& sensor) { + SensorValue val; + if (sensor.GetSensorAttributesStatic().unit() == UNIT_COUNT) { + if (!reading_json.is_number_integer()) { + return absl::InvalidArgumentError( + "Reading must be an integer for count unit"); + } + val.set_count(reading_json.get<int64_t>()); + } else { + if (!reading_json.is_number()) { + return absl::InvalidArgumentError("Reading must be a number"); + } + val.set_reading(reading_json.get<double>()); + } + return val; +} + +// Handles PATCH requests configuring or clearing software sensor overrides. +// Explicit behavior: +// 1. If SensorValueOverridden is true, a 'Reading' field MUST be provided. +// 2. If SensorValueOverridden is false, a 'Reading' field MUST NOT be provided. +void HandleSensorOverridePatch(Store& store, const Sensor& sensor, + const nlohmann::json& body, + const nlohmann::json& override_val_json, + RedfishResponse& resp, + absl::string_view chassis_id, + absl::string_view sensor_id) { + if (!absl::GetFlag(FLAGS_enable_tlbmc_sensor_override)) { + resp.SetToForbidden( + "Sensor error injection and software overrides are disabled on " + "production images. Use a dev-signed image or enable " + "--enable_tlbmc_sensor_override."); + return; + } + + if (!override_val_json.is_boolean()) { + resp.SetToBadRequest("SensorValueOverridden must be a boolean"); + return; + } + + bool is_override = override_val_json.get<bool>(); + auto reading_it = body.find("Reading"); + + // Behavior 1: If Override is false, we MUST NOT receive a reading. + if (!is_override) { + if (reading_it != body.end()) { + resp.SetToBadRequest( + "Clearing override must not include 'Reading' field"); + return; + } + absl::Status status = store.ClearSensorOverride(std::string(sensor_id)); + if (!status.ok()) { + resp.SetToAbslStatus(status); + return; + } + SetSuccessResponse(resp, "Sensor override successfully cleared.", + chassis_id, sensor_id); + return; + } + + // Behavior 2: If Override is true, we MUST provide a reading. + if (reading_it == body.end()) { + resp.SetToBadRequest("Setting override requires 'Reading' field"); + return; + } + + absl::StatusOr<SensorValue> val = ParseReadingValue(*reading_it, sensor); + if (!val.ok()) { + resp.SetToBadRequest(val.status().message()); + return; + } + + absl::Status status = store.SetSensorOverride(std::string(sensor_id), *val); + if (!status.ok()) { + resp.SetToAbslStatus(status); + return; + } + + SetSuccessResponse(resp, "Sensor override successfully created.", chassis_id, + sensor_id); +} + void HandleSensorPatch(const RedfishApp& app, const RedfishRequest& req, RedfishResponse& resp, const std::string& chassis_id, const std::string& sensor_id) { @@ -154,42 +274,35 @@ return; } - if (!body.contains("Reading")) { + // Check for OEM Google Override control. + const nlohmann::json* override_json = FindSensorOverrideControl(body); + if (override_json != nullptr) { + HandleSensorOverridePatch(store, *sensor, body, *override_json, resp, + chassis_id, sensor_id); + return; + } + + // Standard sensor write PATCH. + auto reading_it = body.find("Reading"); + if (reading_it == body.end()) { resp.SetToBadRequest("Request does not contain 'Reading' field"); return; } - SensorValue val; - if (sensor->GetSensorAttributesStatic().unit() == UNIT_COUNT) { - if (!body["Reading"].is_number_integer()) { - resp.SetToBadRequest("Reading must be an integer for count unit"); - return; - } - val.set_count(body["Reading"].get<int64_t>()); - } else { - if (!body["Reading"].is_number()) { - resp.SetToBadRequest("Reading must be a number"); - return; - } - val.set_reading(body["Reading"].get<double>()); + absl::StatusOr<SensorValue> val = ParseReadingValue(*reading_it, *sensor); + if (!val.ok()) { + resp.SetToBadRequest(val.status().message()); + return; } - absl::Status status = store.WriteToSensor(sensor_id, val); + absl::Status status = store.WriteToSensor(sensor_id, *val); if (!status.ok()) { resp.SetToAbslStatus(status); return; } - nlohmann::json success = {{{"@odata.type", "#Message.v1_1_1.Message"}, - {"Message", "The request completed successfully."}, - {"MessageArgs", nlohmann::json::array_t()}, - {"MessageId", "Base.1.13.0.Success"}, - {"MessageSeverity", "OK"}, - {"Resolution", "None"}}}; - resp.SetKeyInJsonBody("/@Message.ExtendedInfo", success); - resp.SetKeyInJsonBody("/@odata.id", - CreateUrl({"redfish", "v1", "Chassis", chassis_id, - "Sensors", sensor_id})); + SetSuccessResponse(resp, "The request completed successfully.", chassis_id, + sensor_id); } std::string_view GetOemSensorStateString(Status status) { @@ -450,6 +563,11 @@ } } } + + if (sensor->IsOverridden()) { + resp.SetKeyInJsonBody( + sensor_pointer / "Oem" / "Google" / "SensorValueOverridden", true); + } } void RegisterRoutes(RedfishApp& app) {
diff --git a/tlbmc/redfish/routes/sensor.h b/tlbmc/redfish/routes/sensor.h index ec6a5f3..cb3f188 100644 --- a/tlbmc/redfish/routes/sensor.h +++ b/tlbmc/redfish/routes/sensor.h
@@ -3,12 +3,15 @@ #include <memory> +#include "absl/flags/declare.h" #include <nlohmann/json.hpp> #include "tlbmc/redfish/app.h" #include "tlbmc/redfish/response.h" #include "tlbmc/sensors/sensor.h" #include "tlbmc/store/store.h" +ABSL_DECLARE_FLAG(bool, enable_tlbmc_sensor_override); + namespace milotic_tlbmc::sensor { // Fills the response as a Redfish Sensor resource type with the sensor data.