Block peer certs on supervisor (#66837)

Co-authored-by: Pascal Vizeli <pvizeli@syshack.ch>
Co-authored-by: Mike Degatano <michael.degatano@gmail.com>
pull/67075/head
Paulus Schoutsen 2022-02-22 13:59:40 -08:00 committed by GitHub
parent 756e711850
commit 938b64081b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 121 additions and 31 deletions

View File

@ -23,7 +23,7 @@ from .const import (
SIGNAL_BOOTSTRAP_INTEGRATONS,
)
from .exceptions import HomeAssistantError
from .helpers import area_registry, device_registry, entity_registry
from .helpers import area_registry, device_registry, entity_registry, supervisor
from .helpers.dispatcher import async_dispatcher_send
from .helpers.typing import ConfigType
from .setup import (
@ -398,7 +398,7 @@ def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]:
domains.update(hass.config_entries.async_domains())
# Make sure the Hass.io component is loaded
if "HASSIO" in os.environ:
if supervisor.has_supervisor():
domains.add("hassio")
return domains

View File

@ -104,7 +104,7 @@ class Analytics:
@property
def supervisor(self) -> bool:
"""Return bool if a supervisor is present."""
return hassio.is_hassio(self.hass)
return hassio.is_hassio()
async def load(self) -> None:
"""Load preferences."""

View File

@ -41,6 +41,8 @@ from homeassistant.helpers.device_registry import (
async_get_registry,
)
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.frame import report
from homeassistant.helpers.supervisor import has_supervisor
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.loader import bind_hass
@ -394,12 +396,21 @@ def get_core_info(hass):
@callback
@bind_hass
def is_hassio(hass: HomeAssistant) -> bool:
def is_hassio(
hass: HomeAssistant | None = None, # pylint: disable=unused-argument
) -> bool:
"""Return true if Hass.io is loaded.
Async friendly.
"""
return DOMAIN in hass.config.components
if hass is not None:
report(
"hass param deprecated for is_hassio",
exclude_integrations={DOMAIN},
error_if_core=False,
)
return has_supervisor()
@callback
@ -412,11 +423,8 @@ def get_supervisor_ip() -> str:
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: C901
"""Set up the Hass.io component."""
# Check local setup
for env in ("HASSIO", "HASSIO_TOKEN"):
if os.environ.get(env):
continue
_LOGGER.error("Missing %s environment variable", env)
if not has_supervisor():
_LOGGER.error("Supervisor not available")
if config_entries := hass.config_entries.async_entries(DOMAIN):
hass.async_create_task(
hass.config_entries.async_remove(config_entries[0].entry_id)

View File

@ -23,8 +23,7 @@ from homeassistant.components.network import async_get_source_ip
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, SERVER_PORT
from homeassistant.core import Event, HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import storage
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import config_validation as cv, storage, supervisor
from homeassistant.helpers.network import NoURLAvailableError, get_url
from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import bind_hass
@ -167,6 +166,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
login_threshold = conf[CONF_LOGIN_ATTEMPTS_THRESHOLD]
ssl_profile = conf[CONF_SSL_PROFILE]
if ssl_peer_certificate is not None and supervisor.has_supervisor():
_LOGGER.warning(
"Peer certificates are not supported when running the supervisor"
)
ssl_peer_certificate = None
server = HomeAssistantHTTP(
hass,
server_host=server_host,

View File

@ -195,7 +195,7 @@ class CoreConfigOnboardingView(_BaseOnboardingView):
from homeassistant.components import hassio
if (
hassio.is_hassio(hass)
hassio.is_hassio()
and "raspberrypi" in hassio.get_core_info(hass)["machine"]
):
onboard_integrations.append("rpi_power")

View File

@ -45,7 +45,7 @@ class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
# Set a unique_id to make sure discovery flow is aborted on progress.
await self.async_set_unique_id(DOMAIN, raise_on_progress=False)
if not hassio.is_hassio(self.hass):
if not hassio.is_hassio():
return self._async_use_mqtt_integration()
return await self.async_step_on_supervisor()

View File

@ -74,7 +74,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
_LOGGER.debug("Fetched version %s: %s", newest, release_notes)
# Load data from Supervisor
if hassio.is_hassio(hass):
if hassio.is_hassio():
core_info = hassio.get_core_info(hass)
newest = core_info["version_latest"]

View File

@ -332,14 +332,14 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
if is_hassio(self.hass):
if is_hassio():
return await self.async_step_on_supervisor()
return await self.async_step_manual()
async def async_step_usb(self, discovery_info: usb.UsbServiceInfo) -> FlowResult:
"""Handle USB Discovery."""
if not is_hassio(self.hass):
if not is_hassio():
return self.async_abort(reason="discovery_requires_supervisor")
if self._async_current_entries():
return self.async_abort(reason="already_configured")
@ -641,7 +641,7 @@ class OptionsFlowHandler(BaseZwaveJSFlow, config_entries.OptionsFlow):
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Manage the options."""
if is_hassio(self.hass):
if is_hassio():
return await self.async_step_on_supervisor()
return await self.async_step_manual()

View File

@ -0,0 +1,11 @@
"""Supervisor helper."""
import os
from homeassistant.core import callback
@callback
def has_supervisor() -> bool:
"""Return true if supervisor is available."""
return "SUPERVISOR" in os.environ

View File

@ -21,6 +21,8 @@ def hassio_env():
), patch.dict(os.environ, {"HASSIO_TOKEN": HASSIO_TOKEN}), patch(
"homeassistant.components.hassio.HassIO.get_info",
Mock(side_effect=HassioAPIError()),
), patch.dict(
os.environ, {"SUPERVISOR": "127.0.0.1"}
):
yield

View File

@ -11,7 +11,11 @@ from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"}
MOCK_ENVIRON = {
"HASSIO": "127.0.0.1",
"HASSIO_TOKEN": "abcdefgh",
"SUPERVISOR": "127.0.0.1",
}
@pytest.fixture(autouse=True)

View File

@ -16,7 +16,11 @@ from homeassistant.util import dt as dt_util
from tests.common import MockConfigEntry, async_fire_time_changed
MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"}
MOCK_ENVIRON = {
"HASSIO": "127.0.0.1",
"HASSIO_TOKEN": "abcdefgh",
"SUPERVISOR": "127.0.0.1",
}
@pytest.fixture(autouse=True)
@ -151,7 +155,6 @@ async def test_setup_api_ping(hass, aioclient_mock):
assert aioclient_mock.call_count == 10
assert hass.components.hassio.get_core_info()["version_latest"] == "1.0.0"
assert hass.components.hassio.is_hassio()
async def test_setup_api_panel(hass, aioclient_mock):
@ -334,7 +337,6 @@ async def test_warn_when_cannot_connect(hass, caplog):
result = await async_setup_component(hass, "hassio", {})
assert result
assert hass.components.hassio.is_hassio()
assert "Not connected with the supervisor / system too busy!" in caplog.text

View File

@ -11,7 +11,11 @@ from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"}
MOCK_ENVIRON = {
"HASSIO": "127.0.0.1",
"HASSIO_TOKEN": "abcdefgh",
"SUPERVISOR": "127.0.0.1",
}
@pytest.fixture(autouse=True)

View File

@ -36,7 +36,9 @@ def hassio_env_fixture():
with patch.dict(os.environ, {"HASSIO": "127.0.0.1"}), patch(
"homeassistant.components.hassio.HassIO.is_connected",
return_value={"result": "ok", "data": {}},
), patch.dict(os.environ, {"HASSIO_TOKEN": "123456"}):
), patch.dict(os.environ, {"HASSIO_TOKEN": "123456"}), patch.dict(
os.environ, {"SUPERVISOR": "127.0.0.1"}
):
yield

View File

@ -261,6 +261,42 @@ async def test_peer_cert(hass, tmpdir):
assert len(mock_load_verify_locations.mock_calls) == 1
async def test_peer_cert_ignored_with_supervisor(hass, tmpdir):
"""Test peer certiicate requirement ignored in supervised deployments."""
cert_path, key_path, peer_cert_path = await hass.async_add_executor_job(
_setup_empty_ssl_pem_files, tmpdir
)
with patch("ssl.SSLContext.load_cert_chain"), patch(
"homeassistant.components.http.supervisor.has_supervisor", return_value=True
), patch(
"ssl.SSLContext.load_verify_locations"
) as mock_load_verify_locations, patch(
"homeassistant.util.ssl.server_context_modern",
side_effect=server_context_modern,
) as mock_context:
assert (
await async_setup_component(
hass,
"http",
{
"http": {
"ssl_peer_certificate": peer_cert_path,
"ssl_profile": "modern",
"ssl_certificate": cert_path,
"ssl_key": key_path,
}
},
)
is True
)
await hass.async_start()
await hass.async_block_till_done()
assert len(mock_context.mock_calls) == 1
mock_load_verify_locations.assert_not_called()
async def test_emergency_ssl_certificate_when_invalid(hass, tmpdir, caplog):
"""Test http can startup with an emergency self signed cert when the current one is broken."""

View File

@ -86,6 +86,8 @@ async def mock_supervisor_fixture(hass, aioclient_mock):
return_value={"panels": {}},
), patch.dict(
os.environ, {"HASSIO_TOKEN": "123456"}
), patch.dict(
os.environ, {"SUPERVISOR": "127.0.0.1"}
):
yield

View File

@ -7,8 +7,6 @@ from homeassistant.components import updater
from homeassistant.helpers.update_coordinator import UpdateFailed
from homeassistant.setup import async_setup_component
from tests.common import mock_component
NEW_VERSION = "10000.0"
MOCK_VERSION = "10.0"
MOCK_DEV_VERSION = "10.0.dev0"
@ -113,12 +111,12 @@ async def test_new_version_shows_entity_after_hour_hassio(
hass, mock_get_newest_version
):
"""Test if binary sensor gets updated if new version is available / Hass.io."""
mock_component(hass, "hassio")
hass.data["hassio_core_info"] = {"version_latest": "999.0"}
with patch("homeassistant.components.updater.hassio.is_hassio", return_value=True):
hass.data["hassio_core_info"] = {"version_latest": "999.0"}
assert await async_setup_component(hass, updater.DOMAIN, {updater.DOMAIN: {}})
assert await async_setup_component(hass, updater.DOMAIN, {updater.DOMAIN: {}})
await hass.async_block_till_done()
await hass.async_block_till_done()
assert hass.states.is_state("binary_sensor.updater", "on")
assert (

View File

@ -0,0 +1,16 @@
"""Test the Hassio helper."""
from unittest.mock import patch
from homeassistant.helpers.supervisor import has_supervisor
async def test_has_supervisor_yes():
"""Test has_supervisor when supervisor available."""
with patch("homeassistant.helpers.supervisor.os.environ", {"SUPERVISOR": True}):
assert has_supervisor()
async def test_has_supervisor_no():
"""Test has_supervisor when supervisor not available."""
with patch("homeassistant.helpers.supervisor.os.environ"):
assert not has_supervisor()

View File

@ -86,7 +86,7 @@ async def test_load_hassio(hass):
with patch.dict(os.environ, {}, clear=True):
assert bootstrap._get_domains(hass, {}) == set()
with patch.dict(os.environ, {"HASSIO": "1"}):
with patch.dict(os.environ, {"SUPERVISOR": "1"}):
assert bootstrap._get_domains(hass, {}) == {"hassio"}