diff --git a/hassio/api/__init__.py b/hassio/api/__init__.py index 34af475f2..456ff8e7e 100644 --- a/hassio/api/__init__.py +++ b/hassio/api/__init__.py @@ -89,6 +89,11 @@ class RestAPI(CoreSysAttributes): self.webapp.add_routes( [ + web.get("/os/info", api_hassos.info), + web.post("/os/update", api_hassos.update), + web.post("/os/update/cli", api_hassos.update_cli), + web.post("/os/config/sync", api_hassos.config_sync), + # Remove with old Hass.io fallback web.get("/hassos/info", api_hassos.info), web.post("/hassos/update", api_hassos.update), web.post("/hassos/update/cli", api_hassos.update_cli), @@ -150,6 +155,17 @@ class RestAPI(CoreSysAttributes): self.webapp.add_routes( [ + web.get("/core/info", api_hass.info), + web.get("/core/logs", api_hass.logs), + web.get("/core/stats", api_hass.stats), + web.post("/core/options", api_hass.options), + web.post("/core/update", api_hass.update), + web.post("/core/restart", api_hass.restart), + web.post("/core/stop", api_hass.stop), + web.post("/core/start", api_hass.start), + web.post("/core/check", api_hass.check), + web.post("/core/rebuild", api_hass.rebuild), + # Remove with old Hass.io fallback web.get("/homeassistant/info", api_hass.info), web.get("/homeassistant/logs", api_hass.logs), web.get("/homeassistant/stats", api_hass.stats), @@ -170,6 +186,13 @@ class RestAPI(CoreSysAttributes): self.webapp.add_routes( [ + web.get("/core/api/websocket", api_proxy.websocket), + web.get("/core/websocket", api_proxy.websocket), + web.get("/core/api/stream", api_proxy.stream), + web.post("/core/api/{path:.+}", api_proxy.api), + web.get("/core/api/{path:.+}", api_proxy.api), + web.get("/core/api/", api_proxy.api), + # Remove with old Hass.io fallback web.get("/homeassistant/api/websocket", api_proxy.websocket), web.get("/homeassistant/websocket", api_proxy.websocket), web.get("/homeassistant/api/stream", api_proxy.stream), diff --git a/hassio/api/ingress.py b/hassio/api/ingress.py index a0fe3a480..9d3e12304 100644 --- a/hassio/api/ingress.py +++ b/hassio/api/ingress.py @@ -23,6 +23,7 @@ from ..const import ( ATTR_ENABLE, COOKIE_INGRESS, HEADER_TOKEN, + HEADER_TOKEN_OLD, REQUEST_FROM, ) from ..coresys import CoreSysAttributes @@ -212,6 +213,7 @@ def _init_header( hdrs.SEC_WEBSOCKET_VERSION, hdrs.SEC_WEBSOCKET_KEY, istr(HEADER_TOKEN), + istr(HEADER_TOKEN_OLD), ): continue headers[name] = value diff --git a/hassio/api/proxy.py b/hassio/api/proxy.py index 59e5db03e..cf8a8abe3 100644 --- a/hassio/api/proxy.py +++ b/hassio/api/proxy.py @@ -9,7 +9,6 @@ from aiohttp.web_exceptions import HTTPBadGateway, HTTPUnauthorized from aiohttp.client_exceptions import ClientConnectorError from aiohttp.hdrs import CONTENT_TYPE, AUTHORIZATION -from ..const import HEADER_HA_ACCESS from ..coresys import CoreSysAttributes from ..exceptions import HomeAssistantAuthError, HomeAssistantAPIError, APIError @@ -17,6 +16,7 @@ _LOGGER: logging.Logger = logging.getLogger(__name__) FORWARD_HEADERS = ("X-Speech-Content",) +HEADER_HA_ACCESS = "X-Ha-Access" class APIProxy(CoreSysAttributes): diff --git a/hassio/api/security.py b/hassio/api/security.py index 63d930197..9ce4661dd 100644 --- a/hassio/api/security.py +++ b/hassio/api/security.py @@ -3,16 +3,16 @@ import logging import re from aiohttp.web import middleware -from aiohttp.web_exceptions import HTTPUnauthorized, HTTPForbidden +from aiohttp.web_exceptions import HTTPForbidden, HTTPUnauthorized +from .utils import excract_supervisor_token from ..const import ( - HEADER_TOKEN, REQUEST_FROM, ROLE_ADMIN, + ROLE_BACKUP, ROLE_DEFAULT, ROLE_HOMEASSISTANT, ROLE_MANAGER, - ROLE_BACKUP, ) from ..coresys import CoreSysAttributes @@ -24,6 +24,7 @@ _LOGGER: logging.Logger = logging.getLogger(__name__) BLACKLIST = re.compile( r"^(?:" r"|/homeassistant/api/hassio/.*" + r"|/core/api/hassio/.*" r")$" ) @@ -32,6 +33,8 @@ NO_SECURITY_CHECK = re.compile( r"^(?:" r"|/homeassistant/api/.*" r"|/homeassistant/websocket" + r"|/core/api/.*" + r"|/core/websocket" r"|/supervisor/ping" r")$" ) @@ -59,6 +62,7 @@ ADDONS_ROLE_ACCESS = { ), ROLE_HOMEASSISTANT: re.compile( r"^(?:" + r"|/core/.+" r"|/homeassistant/.+" r")$" ), @@ -70,9 +74,11 @@ ADDONS_ROLE_ACCESS = { ROLE_MANAGER: re.compile( r"^(?:" r"|/dns/.*" + r"|/core/.+" r"|/homeassistant/.+" r"|/host/.+" r"|/hardware/.+" + r"|/os/.+" r"|/hassos/.+" r"|/supervisor/.+" r"|/addons(?:/[^/]+/(?!security).+|/reload)?" @@ -98,7 +104,7 @@ class SecurityMiddleware(CoreSysAttributes): async def token_validation(self, request, handler): """Check security access of this layer.""" request_from = None - hassio_token = request.headers.get(HEADER_TOKEN) + supervisor_token = excract_supervisor_token(request) # Blacklist if BLACKLIST.match(request.path): @@ -111,24 +117,24 @@ class SecurityMiddleware(CoreSysAttributes): return await handler(request) # Not token - if not hassio_token: + if not supervisor_token: _LOGGER.warning("No API token provided for %s", request.path) raise HTTPUnauthorized() # Home-Assistant - if hassio_token == self.sys_homeassistant.hassio_token: + if supervisor_token == self.sys_homeassistant.hassio_token: _LOGGER.debug("%s access from Home Assistant", request.path) request_from = self.sys_homeassistant # Host - if hassio_token == self.sys_machine_id: + if supervisor_token == self.sys_machine_id: _LOGGER.debug("%s access from Host", request.path) request_from = self.sys_host # Add-on addon = None - if hassio_token and not request_from: - addon = self.sys_addons.from_token(hassio_token) + if supervisor_token and not request_from: + addon = self.sys_addons.from_token(supervisor_token) # Check Add-on API access if addon and ADDONS_API_BYPASS.match(request.path): diff --git a/hassio/api/utils.py b/hassio/api/utils.py index 7bf05497f..5a978b5e9 100644 --- a/hassio/api/utils.py +++ b/hassio/api/utils.py @@ -4,11 +4,14 @@ import logging from typing import Any, Dict, List, Optional from aiohttp import web +from aiohttp.hdrs import AUTHORIZATION import voluptuous as vol from voluptuous.humanize import humanize_error from ..const import ( CONTENT_TYPE_BINARY, + HEADER_TOKEN, + HEADER_TOKEN_OLD, JSON_DATA, JSON_MESSAGE, JSON_RESULT, @@ -20,6 +23,22 @@ from ..exceptions import APIError, APIForbidden, HassioError _LOGGER: logging.Logger = logging.getLogger(__name__) +def excract_supervisor_token(request: web.Request) -> Optional[str]: + """Extract Supervisor token from request.""" + supervisor_token = request.headers.get(AUTHORIZATION) + if supervisor_token: + return supervisor_token.split(" ")[-1] + + # Header token handling + supervisor_token = request.headers.get(HEADER_TOKEN) + + # Remove with old Hass.io fallback + if not supervisor_token: + supervisor_token = request.headers.get(HEADER_TOKEN_OLD) + + return supervisor_token + + def json_loads(data: Any) -> Dict[str, Any]: """Extract json from string with support for '' and None.""" if not data: diff --git a/hassio/const.py b/hassio/const.py index dde7baa99..0cd5fab4c 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -58,11 +58,13 @@ CONTENT_TYPE_JSON = "application/json" CONTENT_TYPE_TEXT = "text/plain" CONTENT_TYPE_TAR = "application/tar" CONTENT_TYPE_URL = "application/x-www-form-urlencoded" -HEADER_HA_ACCESS = "X-Ha-Access" -HEADER_TOKEN = "X-Hassio-Key" COOKIE_INGRESS = "ingress_session" -ENV_TOKEN = "HASSIO_TOKEN" +HEADER_TOKEN = "X-Supervisor-Token" +HEADER_TOKEN_OLD = "X-Hassio-Key" + +ENV_TOKEN_OLD = "HASSIO_TOKEN" +ENV_TOKEN = "SUPERVISOR_TOKEN" ENV_TIME = "TZ" REQUEST_FROM = "HASSIO_FROM" diff --git a/hassio/docker/addon.py b/hassio/docker/addon.py index c753ba783..a220492e5 100644 --- a/hassio/docker/addon.py +++ b/hassio/docker/addon.py @@ -6,7 +6,7 @@ from ipaddress import IPv4Address, ip_address import logging import os from pathlib import Path -from typing import TYPE_CHECKING, Dict, List, Optional, Union, Awaitable +from typing import TYPE_CHECKING, Awaitable, Dict, List, Optional, Union import docker import requests @@ -15,6 +15,7 @@ from ..addons.build import AddonBuild from ..const import ( ENV_TIME, ENV_TOKEN, + ENV_TOKEN_OLD, MAP_ADDONS, MAP_BACKUP, MAP_CONFIG, @@ -118,6 +119,7 @@ class DockerAddon(DockerInterface): **addon_env, ENV_TIME: self.sys_timezone, ENV_TOKEN: self.addon.hassio_token, + ENV_TOKEN_OLD: self.addon.hassio_token, } @property @@ -189,7 +191,10 @@ class DockerAddon(DockerInterface): @property def network_mapping(self) -> Dict[str, str]: """Return hosts mapping.""" - return {"hassio": self.sys_docker.network.supervisor} + return { + "supervisor": self.sys_docker.network.supervisor, + "hassio": self.sys_docker.network.supervisor, + } @property def network_mode(self) -> Optional[str]: diff --git a/hassio/docker/homeassistant.py b/hassio/docker/homeassistant.py index 3c4ad1c32..d011452af 100644 --- a/hassio/docker/homeassistant.py +++ b/hassio/docker/homeassistant.py @@ -6,7 +6,7 @@ from typing import Awaitable, Optional import docker -from ..const import ENV_TIME, ENV_TOKEN, LABEL_MACHINE +from ..const import ENV_TIME, ENV_TOKEN, ENV_TOKEN_OLD, LABEL_MACHINE from ..exceptions import DockerAPIError from .interface import CommandReturn, DockerInterface @@ -69,8 +69,10 @@ class DockerHomeAssistant(DockerInterface): network_mode="host", environment={ "HASSIO": self.sys_docker.network.supervisor, + "SUPERVISOR": self.sys_docker.network.supervisor, ENV_TIME: self.sys_timezone, ENV_TOKEN: self.sys_homeassistant.hassio_token, + ENV_TOKEN_OLD: self.sys_homeassistant.hassio_token, }, volumes={ str(self.sys_config.path_extern_homeassistant): {