Skip to content

Commit

Permalink
Add guard to recreate certificates on KV mismatch (#604)
Browse files Browse the repository at this point in the history
* Add guard to recreate certificates on KV mismatch

* Use spesific message

* Add test
  • Loading branch information
ludeeus authored Mar 14, 2024
1 parent 91a81de commit b99621f
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 2 deletions.
5 changes: 5 additions & 0 deletions hass_nabucasa/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,8 @@
Unable to create a certificate. We will automatically
retry it and notify you when it's available.
"""

MESSAGE_LOAD_CERTIFICATE_FAILURE = """
Unable to load the certificate. We will automatically
recreate it and notify you when it's available.
"""
16 changes: 14 additions & 2 deletions hass_nabucasa/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from enum import Enum
import logging
import random
from ssl import SSLContext
from ssl import SSLContext, SSLError
from typing import TYPE_CHECKING, cast

import aiohttp
Expand Down Expand Up @@ -294,9 +294,21 @@ async def load_backend(self) -> bool:
while self.cloud.client.aiohttp_runner is None:
await asyncio.sleep(1)

try:
context = await self._create_context()
except SSLError as err:
if err.reason == "KEY_VALUES_MISMATCH":
self.cloud.client.user_message(
"cloud_remote_acme",
"Home Assistant Cloud",
const.MESSAGE_LOAD_CERTIFICATE_FAILURE,
)
await self._recreate_acme(domains, email)
self._certificate_status = CertificateStatus.ERROR
return False

# Setup snitun / aiohttp wrapper
_LOGGER.debug("Initializing SniTun")
context = await self._create_context()
self._snitun = SniTunClientAioHttp(
self.cloud.client.aiohttp_runner,
context,
Expand Down
54 changes: 54 additions & 0 deletions tests/test_remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import asyncio
from datetime import timedelta
from ssl import SSLError
from unittest.mock import patch

from acme import client, messages
Expand Down Expand Up @@ -1032,3 +1033,56 @@ async def reset_acme(self):
assert remote._certificate_status is CertificateStatus.ERROR

await remote.stop()


@pytest.mark.parametrize(
("reason", "should_reset"),
(
(
"KEY_VALUES_MISMATCH",
True,
),
(
"Boom",
False,
),
),
)
async def test_context_error_handling(
auth_cloud_mock,
mock_cognito,
valid_acme_mock,
aioclient_mock,
snitun_mock,
reason,
should_reset,
):
"""Test that we reset if we hit an error reason that require resetting."""
auth_cloud_mock.servicehandlers_server = "test.local"

remote = RemoteUI(auth_cloud_mock)

aioclient_mock.post(
"https://test.local/instance/register",
json={
"domain": "test.dui.nabu.casa",
"email": "[email protected]",
"server": "rest-remote.nabu.casa",
},
)

ssl_error = SSLError()
ssl_error.reason = reason

with patch(
"hass_nabucasa.remote.RemoteUI._create_context",
side_effect=ssl_error,
):
assert remote._certificate_status is None
await remote.load_backend()

await asyncio.sleep(0.1)
assert remote._acme.call_reset == should_reset
assert remote._certificate_status is CertificateStatus.ERROR

await remote.stop()

0 comments on commit b99621f

Please sign in to comment.