From 3bf446cbdb257b0a3226f35007cff48d9cd3c784 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Sun, 11 Feb 2018 00:05:20 +0100 Subject: [PATCH] Improve security layer (#352) * Improve security layer * Update logger * Fix access * Validate token * fix * fix some bugs * fix lint --- hassio/addons/__init__.py | 9 ++++++++- hassio/api/proxy.py | 30 +++++++++++++++++++++++++++--- hassio/api/security.py | 37 ++++++++++++++++++++++++++----------- 3 files changed, 61 insertions(+), 15 deletions(-) diff --git a/hassio/addons/__init__.py b/hassio/addons/__init__.py index 90cbeaf7c..7dd78d788 100644 --- a/hassio/addons/__init__.py +++ b/hassio/addons/__init__.py @@ -34,9 +34,16 @@ class AddonManager(CoreSysAttributes): return list(self.repositories_obj.values()) def get(self, addon_slug): - """Return a adddon from slug.""" + """Return a add-on from slug.""" return self.addons_obj.get(addon_slug) + def from_uuid(self, uuid): + """Return a add-on from uuid.""" + for addon in self.list_addons: + if addon.is_installed and uuid == addon.uuid: + return addon + return None + async def load(self): """Startup addon management.""" self.data.reload() diff --git a/hassio/api/proxy.py b/hassio/api/proxy.py index 3cbc35cf5..5929b771f 100644 --- a/hassio/api/proxy.py +++ b/hassio/api/proxy.py @@ -17,6 +17,16 @@ _LOGGER = logging.getLogger(__name__) class APIProxy(CoreSysAttributes): """API Proxy for Home-Assistant.""" + def _check_access(self, request): + """Check the Hass.io token.""" + hassio_token = request.headers.get(HEADER_HA_ACCESS) + addon = self._addons.from_uuid(hassio_token) + + if not addon: + _LOGGER.warning("Unknown Home-Assistant API access!") + else: + _LOGGER.info("%s access from %s", request.path, addon.slug) + async def _api_client(self, request, path, timeout=300): """Return a client request with proxy origin for Home-Assistant.""" url = f"{self._homeassistant.api_url}/api/{path}" @@ -59,6 +69,8 @@ class APIProxy(CoreSysAttributes): async def stream(self, request): """Proxy HomeAssistant EventStream Requests.""" + self._check_access(request) + _LOGGER.info("Home-Assistant EventStream start") client = await self._api_client(request, 'stream', timeout=None) @@ -83,12 +95,14 @@ class APIProxy(CoreSysAttributes): client.close() _LOGGER.info("Home-Assistant EventStream close") + return response + async def api(self, request): """Proxy HomeAssistant API Requests.""" - path = request.match_info.get('path', '') + self._check_access(request) # Normal request - _LOGGER.info("Home-Assistant /api/%s request", path) + path = request.match_info.get('path', '') client = await self._api_client(request, path) data = await client.read() @@ -138,7 +152,17 @@ class APIProxy(CoreSysAttributes): 'type': 'auth_required', 'ha_version': self._homeassistant.version, }) - await server.receive_json() # get internal token + + # Check API access + response = await server.receive_json() + hassio_token = response.get('api_password') + addon = self._addons.from_uuid(hassio_token) + + if not addon: + _LOGGER.warning("Unauthorized websocket access!") + else: + _LOGGER.info("Websocket access from %s", addon.slug) + await server.send_json({ 'type': 'auth_ok', 'ha_version': self._homeassistant.version, diff --git a/hassio/api/security.py b/hassio/api/security.py index 8de4cd3be..fd867ca4b 100644 --- a/hassio/api/security.py +++ b/hassio/api/security.py @@ -1,12 +1,19 @@ """Handle security part of this API.""" import logging +import re from aiohttp.web import middleware +from aiohttp.web_exceptions import HTTPUnauthorized from ..const import HEADER_TOKEN, REQUEST_FROM _LOGGER = logging.getLogger(__name__) +NO_SECURITY_CHECK = set(( + re.compile(r"^/homeassistant/api/.*$"), + re.compile(r"^/homeassistant/websocket$") +)) + @middleware async def security_layer(request, handler): @@ -14,21 +21,29 @@ async def security_layer(request, handler): coresys = request.app['coresys'] hassio_token = request.headers.get(HEADER_TOKEN) + # Ignore security check + for rule in NO_SECURITY_CHECK: + if rule.match(request.path): + _LOGGER.debug("Passthrough %s", request.path) + return await handler(request) + # Need to be removed later if not hassio_token: - _LOGGER.warning("No valid hassio token for API access!") + _LOGGER.warning("No valid Hass.io token for API access!") request[REQUEST_FROM] = 'UNKNOWN' + return await handler(request) - # From Home-Assistant - elif hassio_token == coresys.homeassistant.uuid: + # Home-Assistant + if hassio_token == coresys.homeassistant.uuid: + _LOGGER.debug("%s access from Home-Assistant", request.path) request[REQUEST_FROM] = 'homeassistant' + return await handler(request) - # From Add-on - else: - for addon in coresys.addons.list_addons: - if hassio_token != addon.uuid: - continue - request[REQUEST_FROM] = addon.slug - break + # Add-on + addon = coresys.addons.from_uuid(hassio_token) + if addon: + _LOGGER.info("%s access from %s", request.path, addon.slug) + request[REQUEST_FROM] = addon.slug + return await handler(request) - return await handler(request) + raise HTTPUnauthorized()