mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Check cloud trusted proxies (#24395)
This commit is contained in:
parent
7887d6d6e4
commit
f77514c6f2
@ -38,3 +38,7 @@ DISPATCHER_REMOTE_UPDATE = 'cloud_remote_update'
|
|||||||
|
|
||||||
class InvalidTrustedNetworks(Exception):
|
class InvalidTrustedNetworks(Exception):
|
||||||
"""Raised when invalid trusted networks config."""
|
"""Raised when invalid trusted networks config."""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidTrustedProxies(Exception):
|
||||||
|
"""Raised when invalid trusted proxies config."""
|
||||||
|
@ -18,7 +18,8 @@ from homeassistant.components.google_assistant import helpers as google_helpers
|
|||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
DOMAIN, REQUEST_TIMEOUT, PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE,
|
DOMAIN, REQUEST_TIMEOUT, PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE,
|
||||||
PREF_GOOGLE_SECURE_DEVICES_PIN, InvalidTrustedNetworks)
|
PREF_GOOGLE_SECURE_DEVICES_PIN, InvalidTrustedNetworks,
|
||||||
|
InvalidTrustedProxies)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -52,7 +53,10 @@ SCHEMA_WS_HOOK_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
|||||||
_CLOUD_ERRORS = {
|
_CLOUD_ERRORS = {
|
||||||
InvalidTrustedNetworks:
|
InvalidTrustedNetworks:
|
||||||
(500, 'Remote UI not compatible with 127.0.0.1/::1'
|
(500, 'Remote UI not compatible with 127.0.0.1/::1'
|
||||||
' as a trusted network.')
|
' as a trusted network.'),
|
||||||
|
InvalidTrustedProxies:
|
||||||
|
(500, 'Remote UI not compatible with 127.0.0.1/::1'
|
||||||
|
' as trusted proxies.'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ from .const import (
|
|||||||
PREF_GOOGLE_SECURE_DEVICES_PIN, PREF_CLOUDHOOKS, PREF_CLOUD_USER,
|
PREF_GOOGLE_SECURE_DEVICES_PIN, PREF_CLOUDHOOKS, PREF_CLOUD_USER,
|
||||||
PREF_GOOGLE_ENTITY_CONFIGS, PREF_OVERRIDE_NAME, PREF_DISABLE_2FA,
|
PREF_GOOGLE_ENTITY_CONFIGS, PREF_OVERRIDE_NAME, PREF_DISABLE_2FA,
|
||||||
PREF_ALIASES, PREF_SHOULD_EXPOSE,
|
PREF_ALIASES, PREF_SHOULD_EXPOSE,
|
||||||
InvalidTrustedNetworks)
|
InvalidTrustedNetworks, InvalidTrustedProxies)
|
||||||
|
|
||||||
STORAGE_KEY = DOMAIN
|
STORAGE_KEY = DOMAIN
|
||||||
STORAGE_VERSION = 1
|
STORAGE_VERSION = 1
|
||||||
@ -59,6 +59,9 @@ class CloudPreferences:
|
|||||||
if remote_enabled is True and self._has_local_trusted_network:
|
if remote_enabled is True and self._has_local_trusted_network:
|
||||||
raise InvalidTrustedNetworks
|
raise InvalidTrustedNetworks
|
||||||
|
|
||||||
|
if remote_enabled is True and self._has_local_trusted_proxies:
|
||||||
|
raise InvalidTrustedProxies
|
||||||
|
|
||||||
await self._store.async_save(self._prefs)
|
await self._store.async_save(self._prefs)
|
||||||
|
|
||||||
async def async_update_google_entity_config(
|
async def async_update_google_entity_config(
|
||||||
@ -112,7 +115,7 @@ class CloudPreferences:
|
|||||||
if not enabled:
|
if not enabled:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self._has_local_trusted_network:
|
if self._has_local_trusted_network or self._has_local_trusted_proxies:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@ -162,3 +165,18 @@ class CloudPreferences:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _has_local_trusted_proxies(self) -> bool:
|
||||||
|
"""Return if we allow localhost to be a proxy and use its data."""
|
||||||
|
if not hasattr(self._hass, 'http'):
|
||||||
|
return False
|
||||||
|
|
||||||
|
local4 = ip_address('127.0.0.1')
|
||||||
|
local6 = ip_address('::1')
|
||||||
|
|
||||||
|
if any(local4 in nwk or local6 in nwk
|
||||||
|
for nwk in self._hass.http.trusted_proxies):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
@ -228,6 +228,7 @@ class HomeAssistantHTTP:
|
|||||||
self.ssl_key = ssl_key
|
self.ssl_key = ssl_key
|
||||||
self.server_host = server_host
|
self.server_host = server_host
|
||||||
self.server_port = server_port
|
self.server_port = server_port
|
||||||
|
self.trusted_proxies = trusted_proxies
|
||||||
self.is_ban_enabled = is_ban_enabled
|
self.is_ban_enabled = is_ban_enabled
|
||||||
self.ssl_profile = ssl_profile
|
self.ssl_profile = ssl_profile
|
||||||
self._handler = None
|
self._handler = None
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Tests for the HTTP API for the cloud component."""
|
"""Tests for the HTTP API for the cloud component."""
|
||||||
import asyncio
|
import asyncio
|
||||||
from unittest.mock import patch, MagicMock
|
from unittest.mock import patch, MagicMock
|
||||||
|
from ipaddress import ip_network
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from jose import jwt
|
from jose import jwt
|
||||||
@ -672,7 +673,7 @@ async def test_enabling_remote_trusted_networks_local6(
|
|||||||
|
|
||||||
async def test_enabling_remote_trusted_networks_other(
|
async def test_enabling_remote_trusted_networks_other(
|
||||||
hass, hass_ws_client, setup_api, mock_cloud_login):
|
hass, hass_ws_client, setup_api, mock_cloud_login):
|
||||||
"""Test we cannot enable remote UI when trusted networks active."""
|
"""Test we can enable remote UI when trusted networks active."""
|
||||||
hass.auth._providers[('trusted_networks', None)] = \
|
hass.auth._providers[('trusted_networks', None)] = \
|
||||||
tn_auth.TrustedNetworksAuthProvider(
|
tn_auth.TrustedNetworksAuthProvider(
|
||||||
hass, None, tn_auth.CONFIG_SCHEMA({
|
hass, None, tn_auth.CONFIG_SCHEMA({
|
||||||
@ -749,3 +750,53 @@ async def test_update_google_entity(
|
|||||||
'aliases': ['lefty', 'righty'],
|
'aliases': ['lefty', 'righty'],
|
||||||
'disable_2fa': False,
|
'disable_2fa': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_enabling_remote_trusted_proxies_local4(
|
||||||
|
hass, hass_ws_client, setup_api, mock_cloud_login):
|
||||||
|
"""Test we cannot enable remote UI when trusted networks active."""
|
||||||
|
hass.http.trusted_proxies.append(ip_network('127.0.0.1'))
|
||||||
|
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
'hass_nabucasa.remote.RemoteUI.connect',
|
||||||
|
side_effect=AssertionError
|
||||||
|
) as mock_connect:
|
||||||
|
await client.send_json({
|
||||||
|
'id': 5,
|
||||||
|
'type': 'cloud/remote/connect',
|
||||||
|
})
|
||||||
|
response = await client.receive_json()
|
||||||
|
|
||||||
|
assert not response['success']
|
||||||
|
assert response['error']['code'] == 500
|
||||||
|
assert response['error']['message'] == \
|
||||||
|
'Remote UI not compatible with 127.0.0.1/::1 as trusted proxies.'
|
||||||
|
|
||||||
|
assert len(mock_connect.mock_calls) == 0
|
||||||
|
|
||||||
|
|
||||||
|
async def test_enabling_remote_trusted_proxies_local6(
|
||||||
|
hass, hass_ws_client, setup_api, mock_cloud_login):
|
||||||
|
"""Test we cannot enable remote UI when trusted networks active."""
|
||||||
|
hass.http.trusted_proxies.append(ip_network('::1'))
|
||||||
|
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
'hass_nabucasa.remote.RemoteUI.connect',
|
||||||
|
side_effect=AssertionError
|
||||||
|
) as mock_connect:
|
||||||
|
await client.send_json({
|
||||||
|
'id': 5,
|
||||||
|
'type': 'cloud/remote/connect',
|
||||||
|
})
|
||||||
|
response = await client.receive_json()
|
||||||
|
|
||||||
|
assert not response['success']
|
||||||
|
assert response['error']['code'] == 500
|
||||||
|
assert response['error']['message'] == \
|
||||||
|
'Remote UI not compatible with 127.0.0.1/::1 as trusted proxies.'
|
||||||
|
|
||||||
|
assert len(mock_connect.mock_calls) == 0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user