mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Startup with an emergency self signed cert if the ssl certificate cannot be loaded (#66707)
This commit is contained in:
parent
0269ad4738
commit
3bf2be1765
@ -158,8 +158,11 @@ async def async_setup_hass(
|
|||||||
|
|
||||||
safe_mode = True
|
safe_mode = True
|
||||||
old_config = hass.config
|
old_config = hass.config
|
||||||
|
old_logging = hass.data.get(DATA_LOGGING)
|
||||||
|
|
||||||
hass = core.HomeAssistant()
|
hass = core.HomeAssistant()
|
||||||
|
if old_logging:
|
||||||
|
hass.data[DATA_LOGGING] = old_logging
|
||||||
hass.config.skip_pip = old_config.skip_pip
|
hass.config.skip_pip = old_config.skip_pip
|
||||||
hass.config.internal_url = old_config.internal_url
|
hass.config.internal_url = old_config.internal_url
|
||||||
hass.config.external_url = old_config.external_url
|
hass.config.external_url = old_config.external_url
|
||||||
|
@ -1,22 +1,31 @@
|
|||||||
"""Support to serve the Home Assistant API as WSGI application."""
|
"""Support to serve the Home Assistant API as WSGI application."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import datetime
|
||||||
from ipaddress import IPv4Network, IPv6Network, ip_network
|
from ipaddress import IPv4Network, IPv6Network, ip_network
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import ssl
|
import ssl
|
||||||
|
from tempfile import NamedTemporaryFile
|
||||||
from typing import Any, Final, Optional, TypedDict, Union, cast
|
from typing import Any, Final, Optional, TypedDict, Union, cast
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from aiohttp.typedefs import StrOrURL
|
from aiohttp.typedefs import StrOrURL
|
||||||
from aiohttp.web_exceptions import HTTPMovedPermanently, HTTPRedirection
|
from aiohttp.web_exceptions import HTTPMovedPermanently, HTTPRedirection
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.hazmat.primitives import hashes, serialization
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
|
from cryptography.x509.oid import NameOID
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
from yarl import URL
|
||||||
|
|
||||||
from homeassistant.components.network import async_get_source_ip
|
from homeassistant.components.network import async_get_source_ip
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, SERVER_PORT
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, SERVER_PORT
|
||||||
from homeassistant.core import Event, HomeAssistant
|
from homeassistant.core import Event, HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import storage
|
from homeassistant.helpers import storage
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.helpers.network import NoURLAvailableError, get_url
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
from homeassistant.setup import async_start_setup, async_when_setup_or_start
|
from homeassistant.setup import async_start_setup, async_when_setup_or_start
|
||||||
@ -231,6 +240,7 @@ class HomeAssistantHTTP:
|
|||||||
self.ssl_profile = ssl_profile
|
self.ssl_profile = ssl_profile
|
||||||
self.runner: web.AppRunner | None = None
|
self.runner: web.AppRunner | None = None
|
||||||
self.site: HomeAssistantTCPSite | None = None
|
self.site: HomeAssistantTCPSite | None = None
|
||||||
|
self.context: ssl.SSLContext | None = None
|
||||||
|
|
||||||
async def async_initialize(
|
async def async_initialize(
|
||||||
self,
|
self,
|
||||||
@ -258,6 +268,11 @@ class HomeAssistantHTTP:
|
|||||||
|
|
||||||
setup_cors(self.app, cors_origins)
|
setup_cors(self.app, cors_origins)
|
||||||
|
|
||||||
|
if self.ssl_certificate:
|
||||||
|
self.context = await self.hass.async_add_executor_job(
|
||||||
|
self._create_ssl_context
|
||||||
|
)
|
||||||
|
|
||||||
def register_view(self, view: HomeAssistantView | type[HomeAssistantView]) -> None:
|
def register_view(self, view: HomeAssistantView | type[HomeAssistantView]) -> None:
|
||||||
"""Register a view with the WSGI server.
|
"""Register a view with the WSGI server.
|
||||||
|
|
||||||
@ -329,35 +344,100 @@ class HomeAssistantHTTP:
|
|||||||
self.app.router.add_route("GET", url_path, serve_file)
|
self.app.router.add_route("GET", url_path, serve_file)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def start(self) -> None:
|
def _create_ssl_context(self) -> ssl.SSLContext | None:
|
||||||
"""Start the aiohttp server."""
|
context: ssl.SSLContext | None = None
|
||||||
context: ssl.SSLContext | None
|
assert self.ssl_certificate is not None
|
||||||
if self.ssl_certificate:
|
try:
|
||||||
|
if self.ssl_profile == SSL_INTERMEDIATE:
|
||||||
|
context = ssl_util.server_context_intermediate()
|
||||||
|
else:
|
||||||
|
context = ssl_util.server_context_modern()
|
||||||
|
context.load_cert_chain(self.ssl_certificate, self.ssl_key)
|
||||||
|
except OSError as error:
|
||||||
|
if not self.hass.config.safe_mode:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
f"Could not use SSL certificate from {self.ssl_certificate}: {error}"
|
||||||
|
) from error
|
||||||
|
_LOGGER.error(
|
||||||
|
"Could not read SSL certificate from %s: %s",
|
||||||
|
self.ssl_certificate,
|
||||||
|
error,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
if self.ssl_profile == SSL_INTERMEDIATE:
|
context = self._create_emergency_ssl_context()
|
||||||
context = ssl_util.server_context_intermediate()
|
|
||||||
else:
|
|
||||||
context = ssl_util.server_context_modern()
|
|
||||||
await self.hass.async_add_executor_job(
|
|
||||||
context.load_cert_chain, self.ssl_certificate, self.ssl_key
|
|
||||||
)
|
|
||||||
except OSError as error:
|
except OSError as error:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Could not read SSL certificate from %s: %s",
|
"Could not create an emergency self signed ssl certificate: %s",
|
||||||
self.ssl_certificate,
|
|
||||||
error,
|
error,
|
||||||
)
|
)
|
||||||
return
|
context = None
|
||||||
|
else:
|
||||||
|
_LOGGER.critical(
|
||||||
|
"Home Assistant is running in safe mode with an emergency self signed ssl certificate because the configured SSL certificate was not usable"
|
||||||
|
)
|
||||||
|
return context
|
||||||
|
|
||||||
if self.ssl_peer_certificate:
|
if self.ssl_peer_certificate:
|
||||||
context.verify_mode = ssl.CERT_REQUIRED
|
if context is None:
|
||||||
await self.hass.async_add_executor_job(
|
raise HomeAssistantError(
|
||||||
context.load_verify_locations, self.ssl_peer_certificate
|
"Failed to create ssl context, no fallback available because a peer certificate is required."
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
context.verify_mode = ssl.CERT_REQUIRED
|
||||||
context = None
|
context.load_verify_locations(self.ssl_peer_certificate)
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def _create_emergency_ssl_context(self) -> ssl.SSLContext:
|
||||||
|
"""Create an emergency ssl certificate so we can still startup."""
|
||||||
|
context = ssl_util.server_context_modern()
|
||||||
|
host: str
|
||||||
|
try:
|
||||||
|
host = cast(str, URL(get_url(self.hass, prefer_external=True)).host)
|
||||||
|
except NoURLAvailableError:
|
||||||
|
host = "homeassistant.local"
|
||||||
|
key = rsa.generate_private_key(
|
||||||
|
public_exponent=65537,
|
||||||
|
key_size=2048,
|
||||||
|
)
|
||||||
|
subject = issuer = x509.Name(
|
||||||
|
[
|
||||||
|
x509.NameAttribute(
|
||||||
|
NameOID.ORGANIZATION_NAME, "Home Assistant Emergency Certificate"
|
||||||
|
),
|
||||||
|
x509.NameAttribute(NameOID.COMMON_NAME, host),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
cert = (
|
||||||
|
x509.CertificateBuilder()
|
||||||
|
.subject_name(subject)
|
||||||
|
.issuer_name(issuer)
|
||||||
|
.public_key(key.public_key())
|
||||||
|
.serial_number(x509.random_serial_number())
|
||||||
|
.not_valid_before(datetime.datetime.utcnow())
|
||||||
|
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=30))
|
||||||
|
.add_extension(
|
||||||
|
x509.SubjectAlternativeName([x509.DNSName(host)]),
|
||||||
|
critical=False,
|
||||||
|
)
|
||||||
|
.sign(key, hashes.SHA256())
|
||||||
|
)
|
||||||
|
with NamedTemporaryFile() as cert_pem, NamedTemporaryFile() as key_pem:
|
||||||
|
cert_pem.write(cert.public_bytes(serialization.Encoding.PEM))
|
||||||
|
key_pem.write(
|
||||||
|
key.private_bytes(
|
||||||
|
serialization.Encoding.PEM,
|
||||||
|
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||||
|
encryption_algorithm=serialization.NoEncryption(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cert_pem.flush()
|
||||||
|
key_pem.flush()
|
||||||
|
context.load_cert_chain(cert_pem.name, key_pem.name)
|
||||||
|
return context
|
||||||
|
|
||||||
|
async def start(self) -> None:
|
||||||
|
"""Start the aiohttp server."""
|
||||||
# Aiohttp freezes apps after start so that no changes can be made.
|
# Aiohttp freezes apps after start so that no changes can be made.
|
||||||
# However in Home Assistant components can be discovered after boot.
|
# However in Home Assistant components can be discovered after boot.
|
||||||
# This will now raise a RunTimeError.
|
# This will now raise a RunTimeError.
|
||||||
@ -369,7 +449,7 @@ class HomeAssistantHTTP:
|
|||||||
await self.runner.setup()
|
await self.runner.setup()
|
||||||
|
|
||||||
self.site = HomeAssistantTCPSite(
|
self.site = HomeAssistantTCPSite(
|
||||||
self.runner, self.server_host, self.server_port, ssl_context=context
|
self.runner, self.server_host, self.server_port, ssl_context=self.context
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await self.site.start()
|
await self.site.start()
|
||||||
|
@ -3,11 +3,13 @@ from datetime import timedelta
|
|||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from ipaddress import ip_network
|
from ipaddress import ip_network
|
||||||
import logging
|
import logging
|
||||||
|
import pathlib
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import homeassistant.components.http as http
|
import homeassistant.components.http as http
|
||||||
|
from homeassistant.helpers.network import NoURLAvailableError
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
from homeassistant.util.ssl import server_context_intermediate, server_context_modern
|
from homeassistant.util.ssl import server_context_intermediate, server_context_modern
|
||||||
@ -15,6 +17,26 @@ from homeassistant.util.ssl import server_context_intermediate, server_context_m
|
|||||||
from tests.common import async_fire_time_changed
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
|
def _setup_broken_ssl_pem_files(tmpdir):
|
||||||
|
test_dir = tmpdir.mkdir("test_broken_ssl")
|
||||||
|
cert_path = pathlib.Path(test_dir) / "cert.pem"
|
||||||
|
cert_path.write_text("garbage")
|
||||||
|
key_path = pathlib.Path(test_dir) / "key.pem"
|
||||||
|
key_path.write_text("garbage")
|
||||||
|
return cert_path, key_path
|
||||||
|
|
||||||
|
|
||||||
|
def _setup_empty_ssl_pem_files(tmpdir):
|
||||||
|
test_dir = tmpdir.mkdir("test_empty_ssl")
|
||||||
|
cert_path = pathlib.Path(test_dir) / "cert.pem"
|
||||||
|
cert_path.write_text("-")
|
||||||
|
peer_cert_path = pathlib.Path(test_dir) / "peer_cert.pem"
|
||||||
|
peer_cert_path.write_text("-")
|
||||||
|
key_path = pathlib.Path(test_dir) / "key.pem"
|
||||||
|
key_path.write_text("-")
|
||||||
|
return cert_path, key_path, peer_cert_path
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_stack():
|
def mock_stack():
|
||||||
"""Mock extract stack."""
|
"""Mock extract stack."""
|
||||||
@ -118,62 +140,278 @@ async def test_proxy_config_only_trust_proxies(hass):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_ssl_profile_defaults_modern(hass):
|
async def test_ssl_profile_defaults_modern(hass, tmpdir):
|
||||||
"""Test default ssl profile."""
|
"""Test default ssl profile."""
|
||||||
assert await async_setup_component(hass, "http", {}) is True
|
|
||||||
|
|
||||||
hass.http.ssl_certificate = "bla"
|
cert_path, key_path, _ = await hass.async_add_executor_job(
|
||||||
|
_setup_empty_ssl_pem_files, tmpdir
|
||||||
|
)
|
||||||
|
|
||||||
with patch("ssl.SSLContext.load_cert_chain"), patch(
|
with patch("ssl.SSLContext.load_cert_chain"), patch(
|
||||||
"homeassistant.util.ssl.server_context_modern",
|
"homeassistant.util.ssl.server_context_modern",
|
||||||
side_effect=server_context_modern,
|
side_effect=server_context_modern,
|
||||||
) as mock_context:
|
) as mock_context:
|
||||||
|
assert (
|
||||||
|
await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"http",
|
||||||
|
{"http": {"ssl_certificate": cert_path, "ssl_key": key_path}},
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
await hass.async_start()
|
await hass.async_start()
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(mock_context.mock_calls) == 1
|
assert len(mock_context.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
async def test_ssl_profile_change_intermediate(hass):
|
async def test_ssl_profile_change_intermediate(hass, tmpdir):
|
||||||
"""Test setting ssl profile to intermediate."""
|
"""Test setting ssl profile to intermediate."""
|
||||||
assert (
|
|
||||||
await async_setup_component(
|
|
||||||
hass, "http", {"http": {"ssl_profile": "intermediate"}}
|
|
||||||
)
|
|
||||||
is True
|
|
||||||
)
|
|
||||||
|
|
||||||
hass.http.ssl_certificate = "bla"
|
cert_path, key_path, _ = await hass.async_add_executor_job(
|
||||||
|
_setup_empty_ssl_pem_files, tmpdir
|
||||||
|
)
|
||||||
|
|
||||||
with patch("ssl.SSLContext.load_cert_chain"), patch(
|
with patch("ssl.SSLContext.load_cert_chain"), patch(
|
||||||
"homeassistant.util.ssl.server_context_intermediate",
|
"homeassistant.util.ssl.server_context_intermediate",
|
||||||
side_effect=server_context_intermediate,
|
side_effect=server_context_intermediate,
|
||||||
) as mock_context:
|
) as mock_context:
|
||||||
|
assert (
|
||||||
|
await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"http",
|
||||||
|
{
|
||||||
|
"http": {
|
||||||
|
"ssl_profile": "intermediate",
|
||||||
|
"ssl_certificate": cert_path,
|
||||||
|
"ssl_key": key_path,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
await hass.async_start()
|
await hass.async_start()
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(mock_context.mock_calls) == 1
|
assert len(mock_context.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
async def test_ssl_profile_change_modern(hass):
|
async def test_ssl_profile_change_modern(hass, tmpdir):
|
||||||
"""Test setting ssl profile to modern."""
|
"""Test setting ssl profile to modern."""
|
||||||
assert (
|
|
||||||
await async_setup_component(hass, "http", {"http": {"ssl_profile": "modern"}})
|
|
||||||
is True
|
|
||||||
)
|
|
||||||
|
|
||||||
hass.http.ssl_certificate = "bla"
|
cert_path, key_path, _ = await hass.async_add_executor_job(
|
||||||
|
_setup_empty_ssl_pem_files, tmpdir
|
||||||
|
)
|
||||||
|
|
||||||
with patch("ssl.SSLContext.load_cert_chain"), patch(
|
with patch("ssl.SSLContext.load_cert_chain"), patch(
|
||||||
"homeassistant.util.ssl.server_context_modern",
|
"homeassistant.util.ssl.server_context_modern",
|
||||||
side_effect=server_context_modern,
|
side_effect=server_context_modern,
|
||||||
) as mock_context:
|
) as mock_context:
|
||||||
|
assert (
|
||||||
|
await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"http",
|
||||||
|
{
|
||||||
|
"http": {
|
||||||
|
"ssl_profile": "modern",
|
||||||
|
"ssl_certificate": cert_path,
|
||||||
|
"ssl_key": key_path,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
await hass.async_start()
|
await hass.async_start()
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(mock_context.mock_calls) == 1
|
assert len(mock_context.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_peer_cert(hass, tmpdir):
|
||||||
|
"""Test required peer cert."""
|
||||||
|
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(
|
||||||
|
"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
|
||||||
|
assert len(mock_load_verify_locations.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
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."""
|
||||||
|
|
||||||
|
cert_path, key_path = await hass.async_add_executor_job(
|
||||||
|
_setup_broken_ssl_pem_files, tmpdir
|
||||||
|
)
|
||||||
|
|
||||||
|
hass.config.safe_mode = True
|
||||||
|
assert (
|
||||||
|
await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"http",
|
||||||
|
{
|
||||||
|
"http": {"ssl_certificate": cert_path, "ssl_key": key_path},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_start()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert (
|
||||||
|
"Home Assistant is running in safe mode with an emergency self signed ssl certificate because the configured SSL certificate was not usable"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hass.http.site is not None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_emergency_ssl_certificate_not_used_when_not_safe_mode(
|
||||||
|
hass, tmpdir, caplog
|
||||||
|
):
|
||||||
|
"""Test an emergency cert is only used in safe mode."""
|
||||||
|
|
||||||
|
cert_path, key_path = await hass.async_add_executor_job(
|
||||||
|
_setup_broken_ssl_pem_files, tmpdir
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
await async_setup_component(
|
||||||
|
hass, "http", {"http": {"ssl_certificate": cert_path, "ssl_key": key_path}}
|
||||||
|
)
|
||||||
|
is False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_emergency_ssl_certificate_when_invalid_get_url_fails(
|
||||||
|
hass, tmpdir, caplog
|
||||||
|
):
|
||||||
|
"""Test http falls back to no ssl when an emergency cert cannot be created when the configured one is broken.
|
||||||
|
|
||||||
|
Ensure we can still start of we cannot determine the external url as well.
|
||||||
|
"""
|
||||||
|
cert_path, key_path = await hass.async_add_executor_job(
|
||||||
|
_setup_broken_ssl_pem_files, tmpdir
|
||||||
|
)
|
||||||
|
hass.config.safe_mode = True
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.http.get_url", side_effect=NoURLAvailableError
|
||||||
|
) as mock_get_url:
|
||||||
|
assert (
|
||||||
|
await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"http",
|
||||||
|
{
|
||||||
|
"http": {"ssl_certificate": cert_path, "ssl_key": key_path},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
await hass.async_start()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(mock_get_url.mock_calls) == 1
|
||||||
|
assert (
|
||||||
|
"Home Assistant is running in safe mode with an emergency self signed ssl certificate because the configured SSL certificate was not usable"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hass.http.site is not None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_invalid_ssl_and_cannot_create_emergency_cert(hass, tmpdir, caplog):
|
||||||
|
"""Test http falls back to no ssl when an emergency cert cannot be created when the configured one is broken."""
|
||||||
|
|
||||||
|
cert_path, key_path = await hass.async_add_executor_job(
|
||||||
|
_setup_broken_ssl_pem_files, tmpdir
|
||||||
|
)
|
||||||
|
hass.config.safe_mode = True
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.http.x509.CertificateBuilder", side_effect=OSError
|
||||||
|
) as mock_builder:
|
||||||
|
assert (
|
||||||
|
await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"http",
|
||||||
|
{
|
||||||
|
"http": {"ssl_certificate": cert_path, "ssl_key": key_path},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
await hass.async_start()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert "Could not create an emergency self signed ssl certificate" in caplog.text
|
||||||
|
assert len(mock_builder.mock_calls) == 1
|
||||||
|
|
||||||
|
assert hass.http.site is not None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_invalid_ssl_and_cannot_create_emergency_cert_with_ssl_peer_cert(
|
||||||
|
hass, tmpdir, caplog
|
||||||
|
):
|
||||||
|
"""Test http falls back to no ssl when an emergency cert cannot be created when the configured one is broken.
|
||||||
|
|
||||||
|
When there is a peer cert verification and we cannot create
|
||||||
|
an emergency cert (probably will never happen since this means
|
||||||
|
the system is very broken), we do not want to startup http
|
||||||
|
as it would allow connections that are not verified by the cert.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cert_path, key_path = await hass.async_add_executor_job(
|
||||||
|
_setup_broken_ssl_pem_files, tmpdir
|
||||||
|
)
|
||||||
|
hass.config.safe_mode = True
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.http.x509.CertificateBuilder", side_effect=OSError
|
||||||
|
) as mock_builder:
|
||||||
|
assert (
|
||||||
|
await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"http",
|
||||||
|
{
|
||||||
|
"http": {
|
||||||
|
"ssl_certificate": cert_path,
|
||||||
|
"ssl_key": key_path,
|
||||||
|
"ssl_peer_certificate": cert_path,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
is False
|
||||||
|
)
|
||||||
|
await hass.async_start()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert "Could not create an emergency self signed ssl certificate" in caplog.text
|
||||||
|
assert len(mock_builder.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
async def test_cors_defaults(hass):
|
async def test_cors_defaults(hass):
|
||||||
"""Test the CORS default settings."""
|
"""Test the CORS default settings."""
|
||||||
with patch("homeassistant.components.http.setup_cors") as mock_setup:
|
with patch("homeassistant.components.http.setup_cors") as mock_setup:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user