mirror of
https://github.com/home-assistant/core.git
synced 2026-09-12 11:38:47 +01:00
Redact Z-Wave add-on options sensitive error details (#167239)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot
parent
b0511519a1
commit
4c8a660b2d
@@ -2,17 +2,69 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.hassio import AddonManager
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.hassio import AddonError, AddonManager
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.redact import async_redact_data
|
||||
from homeassistant.helpers.singleton import singleton
|
||||
|
||||
from .const import ADDON_SLUG, DOMAIN, LOGGER
|
||||
from .const import (
|
||||
ADDON_SLUG,
|
||||
CONF_ADDON_LR_S2_ACCESS_CONTROL_KEY,
|
||||
CONF_ADDON_LR_S2_AUTHENTICATED_KEY,
|
||||
CONF_ADDON_NETWORK_KEY,
|
||||
CONF_ADDON_S0_LEGACY_KEY,
|
||||
CONF_ADDON_S2_ACCESS_CONTROL_KEY,
|
||||
CONF_ADDON_S2_AUTHENTICATED_KEY,
|
||||
CONF_ADDON_S2_UNAUTHENTICATED_KEY,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
)
|
||||
|
||||
DATA_ADDON_MANAGER = f"{DOMAIN}_addon_manager"
|
||||
REDACT_ADDON_OPTION_KEYS = {
|
||||
CONF_ADDON_S0_LEGACY_KEY,
|
||||
CONF_ADDON_S2_ACCESS_CONTROL_KEY,
|
||||
CONF_ADDON_S2_AUTHENTICATED_KEY,
|
||||
CONF_ADDON_S2_UNAUTHENTICATED_KEY,
|
||||
CONF_ADDON_LR_S2_ACCESS_CONTROL_KEY,
|
||||
CONF_ADDON_LR_S2_AUTHENTICATED_KEY,
|
||||
CONF_ADDON_NETWORK_KEY,
|
||||
}
|
||||
|
||||
|
||||
def _redact_sensitive_option_values(message: str, config: dict[str, Any]) -> str:
|
||||
"""Redact sensitive add-on option values in an error string."""
|
||||
redacted_config = async_redact_data(config, REDACT_ADDON_OPTION_KEYS)
|
||||
|
||||
for key in REDACT_ADDON_OPTION_KEYS:
|
||||
option_value = config.get(key)
|
||||
if not isinstance(option_value, str) or not option_value:
|
||||
continue
|
||||
redacted_value = redacted_config.get(key)
|
||||
if not isinstance(redacted_value, str):
|
||||
continue
|
||||
message = message.replace(option_value, redacted_value)
|
||||
|
||||
return message
|
||||
|
||||
|
||||
class ZwaveAddonManager(AddonManager):
|
||||
"""Addon manager for Z-Wave JS with redacted option errors."""
|
||||
|
||||
async def async_set_addon_options(self, config: dict[str, Any]) -> None:
|
||||
"""Set add-on options."""
|
||||
try:
|
||||
await super().async_set_addon_options(config)
|
||||
except AddonError as err:
|
||||
raise AddonError(
|
||||
_redact_sensitive_option_values(str(err), config)
|
||||
) from None
|
||||
|
||||
|
||||
@singleton(DATA_ADDON_MANAGER)
|
||||
@callback
|
||||
def get_addon_manager(hass: HomeAssistant) -> AddonManager:
|
||||
"""Get the add-on manager."""
|
||||
return AddonManager(hass, LOGGER, "Z-Wave JS", ADDON_SLUG)
|
||||
return ZwaveAddonManager(hass, LOGGER, "Z-Wave JS", ADDON_SLUG)
|
||||
|
||||
@@ -46,6 +46,7 @@ HUMIDIFIER_ADC_T3000_ENTITY = "humidifier.adc_t3000_humidifier"
|
||||
DEHUMIDIFIER_ADC_T3000_ENTITY = "humidifier.adc_t3000_dehumidifier"
|
||||
|
||||
PROPERTY_ULTRAVIOLET = "Ultraviolet"
|
||||
TEST_SENSITIVE_NETWORK_KEY = "00112233445566778899AABBCCDDEEFF"
|
||||
|
||||
|
||||
def replace_value_of_zwave_value(
|
||||
|
||||
@@ -38,11 +38,14 @@ from homeassistant.components.zwave_js.helpers import SERVER_VERSION_TIMEOUT
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.redact import REDACTED
|
||||
from homeassistant.helpers.service_info.esphome import ESPHomeServiceInfo
|
||||
from homeassistant.helpers.service_info.hassio import HassioServiceInfo
|
||||
from homeassistant.helpers.service_info.usb import UsbServiceInfo
|
||||
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
|
||||
|
||||
from .common import TEST_SENSITIVE_NETWORK_KEY
|
||||
|
||||
from tests.common import MockConfigEntry, async_capture_events
|
||||
|
||||
ADDON_DISCOVERY_INFO = {
|
||||
@@ -2542,13 +2545,23 @@ async def test_addon_installed_failures(
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("supervisor", "addon_installed", "addon_info")
|
||||
@pytest.mark.parametrize("set_addon_options_side_effect", [SupervisorError()])
|
||||
@pytest.mark.parametrize(
|
||||
"set_addon_options_side_effect",
|
||||
[
|
||||
SupervisorError(
|
||||
"not a valid value for dictionary value @ data['options']. "
|
||||
f"Got {{'s0_legacy_key': '{TEST_SENSITIVE_NETWORK_KEY}'}}"
|
||||
)
|
||||
],
|
||||
)
|
||||
async def test_addon_installed_set_options_failure(
|
||||
hass: HomeAssistant,
|
||||
set_addon_options: AsyncMock,
|
||||
start_addon: AsyncMock,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test all failures when add-on is installed."""
|
||||
secret = TEST_SENSITIVE_NETWORK_KEY
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
@@ -2594,7 +2607,7 @@ async def test_addon_installed_set_options_failure(
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"s0_legacy_key": "new123",
|
||||
"s0_legacy_key": secret,
|
||||
"s2_access_control_key": "new456",
|
||||
"s2_authenticated_key": "new789",
|
||||
"s2_unauthenticated_key": "new987",
|
||||
@@ -2608,7 +2621,7 @@ async def test_addon_installed_set_options_failure(
|
||||
AddonsOptions(
|
||||
config={
|
||||
"device": "/test",
|
||||
"s0_legacy_key": "new123",
|
||||
"s0_legacy_key": secret,
|
||||
"s2_access_control_key": "new456",
|
||||
"s2_authenticated_key": "new789",
|
||||
"s2_unauthenticated_key": "new987",
|
||||
@@ -2622,6 +2635,10 @@ async def test_addon_installed_set_options_failure(
|
||||
assert result["reason"] == "addon_set_config_failed"
|
||||
|
||||
assert start_addon.call_count == 0
|
||||
assert "Failed to set the Z-Wave JS app options" in caplog.text
|
||||
assert "not a valid value for dictionary value" in caplog.text
|
||||
assert REDACTED in caplog.text
|
||||
assert secret not in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("supervisor", "addon_installed")
|
||||
|
||||
@@ -34,12 +34,14 @@ from homeassistant.helpers import (
|
||||
entity_registry as er,
|
||||
issue_registry as ir,
|
||||
)
|
||||
from homeassistant.helpers.redact import REDACTED
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .common import (
|
||||
AIR_TEMPERATURE_SENSOR,
|
||||
BULB_6_MULTI_COLOR_LIGHT_ENTITY,
|
||||
EATON_RF9640_ENTITY,
|
||||
TEST_SENSITIVE_NETWORK_KEY,
|
||||
)
|
||||
|
||||
from tests.common import (
|
||||
@@ -933,6 +935,46 @@ async def test_start_addon(
|
||||
assert start_addon.call_args == call("core_zwave_js")
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("addon_installed", "addon_info")
|
||||
@pytest.mark.parametrize(
|
||||
"set_addon_options_side_effect",
|
||||
[
|
||||
SupervisorError(
|
||||
"not a valid value for dictionary value @ data['options']. "
|
||||
f"Got {{'s0_legacy_key': '{TEST_SENSITIVE_NETWORK_KEY}'}}"
|
||||
)
|
||||
],
|
||||
)
|
||||
async def test_start_addon_redacts_set_options_error(
|
||||
hass: HomeAssistant,
|
||||
install_addon: AsyncMock,
|
||||
set_addon_options: AsyncMock,
|
||||
start_addon: AsyncMock,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test startup redacts add-on options backend error details."""
|
||||
device = "/test"
|
||||
secret = TEST_SENSITIVE_NETWORK_KEY
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="Z-Wave JS",
|
||||
data={"use_addon": True, "usb_path": device, "network_key": secret},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert install_addon.call_count == 0
|
||||
assert set_addon_options.call_count == 1
|
||||
assert start_addon.call_count == 0
|
||||
assert "Failed to set the Z-Wave JS app options" in caplog.text
|
||||
assert "not a valid value for dictionary value" in caplog.text
|
||||
assert REDACTED in caplog.text
|
||||
assert secret not in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("addon_info")
|
||||
async def test_install_addon(
|
||||
hass: HomeAssistant,
|
||||
|
||||
Reference in New Issue
Block a user