Check cloud trusted proxies (#24395)

This commit is contained in:
Paulus Schoutsen 2019-06-07 23:08:55 -07:00 committed by GitHub
parent 7887d6d6e4
commit f77514c6f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 83 additions and 5 deletions

View File

@ -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."""

View File

@ -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.'),
} }

View File

@ -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

View File

@ -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

View File

@ -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