mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-19 07:06:30 +00:00
Add API role system (#703)
* Add API role system * Finish * Simplify * Fix lint * Fix rights * Fix lint * Fix spell * Fix log
This commit is contained in:
parent
c2299ef8da
commit
9f8ad05471
1
API.md
1
API.md
@ -483,6 +483,7 @@ Get all available addons.
|
|||||||
"logo": "bool",
|
"logo": "bool",
|
||||||
"changelog": "bool",
|
"changelog": "bool",
|
||||||
"hassio_api": "bool",
|
"hassio_api": "bool",
|
||||||
|
"hassio_role": "default|homeassistant|manager|admin",
|
||||||
"homeassistant_api": "bool",
|
"homeassistant_api": "bool",
|
||||||
"full_access": "bool",
|
"full_access": "bool",
|
||||||
"protected": "bool",
|
"protected": "bool",
|
||||||
|
@ -26,7 +26,7 @@ from ..const import (
|
|||||||
ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_HOST_IPC,
|
ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_HOST_IPC,
|
||||||
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_DISCOVERY, ATTR_SERVICES,
|
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_DISCOVERY, ATTR_SERVICES,
|
||||||
ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_FULL_ACCESS,
|
ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_FULL_ACCESS,
|
||||||
ATTR_PROTECTED, ATTR_ACCESS_TOKEN, ATTR_HOST_PID,
|
ATTR_PROTECTED, ATTR_ACCESS_TOKEN, ATTR_HOST_PID, ATTR_HASSIO_ROLE,
|
||||||
SECURITY_PROFILE, SECURITY_DISABLE, SECURITY_DEFAULT)
|
SECURITY_PROFILE, SECURITY_DISABLE, SECURITY_DEFAULT)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..docker.addon import DockerAddon
|
from ..docker.addon import DockerAddon
|
||||||
@ -376,6 +376,11 @@ class Addon(CoreSysAttributes):
|
|||||||
"""Return True if the add-on access to Home-Assistant api proxy."""
|
"""Return True if the add-on access to Home-Assistant api proxy."""
|
||||||
return self._mesh[ATTR_HOMEASSISTANT_API]
|
return self._mesh[ATTR_HOMEASSISTANT_API]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hassio_role(self):
|
||||||
|
"""Return Hass.io role for API."""
|
||||||
|
return self._mesh[ATTR_HASSIO_ROLE]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def with_stdin(self):
|
def with_stdin(self):
|
||||||
"""Return True if the add-on access use stdin input."""
|
"""Return True if the add-on access use stdin input."""
|
||||||
|
@ -6,7 +6,8 @@ import re
|
|||||||
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
SECURITY_DISABLE, SECURITY_PROFILE, PRIVILEGED_NET_ADMIN,
|
SECURITY_DISABLE, SECURITY_PROFILE, PRIVILEGED_NET_ADMIN,
|
||||||
PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO, PRIVILEGED_SYS_PTRACE)
|
PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO, PRIVILEGED_SYS_PTRACE,
|
||||||
|
ROLE_ADMIN, ROLE_MANAGER)
|
||||||
|
|
||||||
RE_SHA1 = re.compile(r"[a-f0-9]{8}")
|
RE_SHA1 = re.compile(r"[a-f0-9]{8}")
|
||||||
|
|
||||||
@ -36,6 +37,12 @@ def rating_security(addon):
|
|||||||
PRIVILEGED_SYS_RAWIO, PRIVILEGED_SYS_PTRACE):
|
PRIVILEGED_SYS_RAWIO, PRIVILEGED_SYS_PTRACE):
|
||||||
rating += -1
|
rating += -1
|
||||||
|
|
||||||
|
# API Hass.io role
|
||||||
|
if addon.hassio_role == ROLE_MANAGER:
|
||||||
|
rating += -1
|
||||||
|
elif addon.hassio_role == ROLE_ADMIN:
|
||||||
|
rating += -2
|
||||||
|
|
||||||
# Not secure Networking
|
# Not secure Networking
|
||||||
if addon.host_network:
|
if addon.host_network:
|
||||||
rating += -1
|
rating += -1
|
||||||
|
@ -19,10 +19,11 @@ from ..const import (
|
|||||||
ATTR_ARGS, ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY,
|
ATTR_ARGS, ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY,
|
||||||
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_SERVICES, ATTR_DISCOVERY,
|
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_SERVICES, ATTR_DISCOVERY,
|
||||||
ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_PROTECTED,
|
ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_PROTECTED,
|
||||||
ATTR_FULL_ACCESS, ATTR_ACCESS_TOKEN, ATTR_HOST_PID,
|
ATTR_FULL_ACCESS, ATTR_ACCESS_TOKEN, ATTR_HOST_PID, ATTR_HASSIO_ROLE,
|
||||||
PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO,
|
PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO,
|
||||||
PRIVILEGED_IPC_LOCK, PRIVILEGED_SYS_TIME, PRIVILEGED_SYS_NICE,
|
PRIVILEGED_IPC_LOCK, PRIVILEGED_SYS_TIME, PRIVILEGED_SYS_NICE,
|
||||||
PRIVILEGED_SYS_RESOURCE, PRIVILEGED_SYS_PTRACE)
|
PRIVILEGED_SYS_RESOURCE, PRIVILEGED_SYS_PTRACE,
|
||||||
|
ROLE_DEFAULT, ROLE_HOMEASSISTANT, ROLE_MANAGER, ROLE_ADMIN)
|
||||||
from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_DEVICE
|
from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_DEVICE
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -72,6 +73,13 @@ PRIVILEGED_ALL = [
|
|||||||
PRIVILEGED_SYS_PTRACE,
|
PRIVILEGED_SYS_PTRACE,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
ROLE_ALL = [
|
||||||
|
ROLE_DEFAULT,
|
||||||
|
ROLE_HOMEASSISTANT,
|
||||||
|
ROLE_MANAGER,
|
||||||
|
ROLE_ADMIN,
|
||||||
|
]
|
||||||
|
|
||||||
BASE_IMAGE = {
|
BASE_IMAGE = {
|
||||||
ARCH_ARMHF: "homeassistant/armhf-base:latest",
|
ARCH_ARMHF: "homeassistant/armhf-base:latest",
|
||||||
ARCH_AARCH64: "homeassistant/aarch64-base:latest",
|
ARCH_AARCH64: "homeassistant/aarch64-base:latest",
|
||||||
@ -121,6 +129,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
|
|||||||
vol.Optional(ATTR_GPIO, default=False): vol.Boolean(),
|
vol.Optional(ATTR_GPIO, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_DEVICETREE, default=False): vol.Boolean(),
|
vol.Optional(ATTR_DEVICETREE, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_HASSIO_API, default=False): vol.Boolean(),
|
vol.Optional(ATTR_HASSIO_API, default=False): vol.Boolean(),
|
||||||
|
vol.Optional(ATTR_HASSIO_ROLE, default=ROLE_DEFAULT): vol.In(ROLE_ALL),
|
||||||
vol.Optional(ATTR_HOMEASSISTANT_API, default=False): vol.Boolean(),
|
vol.Optional(ATTR_HOMEASSISTANT_API, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_STDIN, default=False): vol.Boolean(),
|
vol.Optional(ATTR_STDIN, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_LEGACY, default=False): vol.Boolean(),
|
vol.Optional(ATTR_LEGACY, default=False): vol.Boolean(),
|
||||||
|
@ -20,8 +20,8 @@ from ..const import (
|
|||||||
ATTR_NETWORK_RX, ATTR_BLK_READ, ATTR_BLK_WRITE, ATTR_ICON, ATTR_SERVICES,
|
ATTR_NETWORK_RX, ATTR_BLK_READ, ATTR_BLK_WRITE, ATTR_ICON, ATTR_SERVICES,
|
||||||
ATTR_DISCOVERY, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API,
|
ATTR_DISCOVERY, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API,
|
||||||
ATTR_FULL_ACCESS, ATTR_PROTECTED, ATTR_RATING, ATTR_HOST_PID,
|
ATTR_FULL_ACCESS, ATTR_PROTECTED, ATTR_RATING, ATTR_HOST_PID,
|
||||||
CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT,
|
ATTR_HASSIO_ROLE,
|
||||||
REQUEST_FROM)
|
CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT, REQUEST_FROM)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..validate import DOCKER_PORTS, ALSA_DEVICE
|
from ..validate import DOCKER_PORTS, ALSA_DEVICE
|
||||||
from ..exceptions import APINotSupportedError
|
from ..exceptions import APINotSupportedError
|
||||||
@ -153,6 +153,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_WEBUI: addon.webui,
|
ATTR_WEBUI: addon.webui,
|
||||||
ATTR_STDIN: addon.with_stdin,
|
ATTR_STDIN: addon.with_stdin,
|
||||||
ATTR_HASSIO_API: addon.access_hassio_api,
|
ATTR_HASSIO_API: addon.access_hassio_api,
|
||||||
|
ATTR_HASSIO_ROLE: addon.hassio_role,
|
||||||
ATTR_HOMEASSISTANT_API: addon.access_homeassistant_api,
|
ATTR_HOMEASSISTANT_API: addon.access_homeassistant_api,
|
||||||
ATTR_GPIO: addon.with_gpio,
|
ATTR_GPIO: addon.with_gpio,
|
||||||
ATTR_DEVICETREE: addon.with_devicetree,
|
ATTR_DEVICETREE: addon.with_devicetree,
|
||||||
@ -197,6 +198,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
|
|
||||||
# Have Access
|
# Have Access
|
||||||
|
# REMOVE: don't needed anymore
|
||||||
if addon.slug == request[REQUEST_FROM]:
|
if addon.slug == request[REQUEST_FROM]:
|
||||||
_LOGGER.error("Can't self modify his security!")
|
_LOGGER.error("Can't self modify his security!")
|
||||||
raise APINotSupportedError()
|
raise APINotSupportedError()
|
||||||
|
@ -25,7 +25,7 @@ class APIProxy(CoreSysAttributes):
|
|||||||
hassio_token = request.headers.get(HEADER_HA_ACCESS)
|
hassio_token = request.headers.get(HEADER_HA_ACCESS)
|
||||||
addon = self.sys_addons.from_token(hassio_token)
|
addon = self.sys_addons.from_token(hassio_token)
|
||||||
|
|
||||||
# Need removed with 131
|
# REMOVE 132
|
||||||
if not addon:
|
if not addon:
|
||||||
addon = self.sys_addons.from_uuid(hassio_token)
|
addon = self.sys_addons.from_uuid(hassio_token)
|
||||||
|
|
||||||
@ -184,7 +184,7 @@ class APIProxy(CoreSysAttributes):
|
|||||||
response.get('access_token'))
|
response.get('access_token'))
|
||||||
addon = self.sys_addons.from_token(hassio_token)
|
addon = self.sys_addons.from_token(hassio_token)
|
||||||
|
|
||||||
# Need removed with 131
|
# REMOVE 132
|
||||||
if not addon:
|
if not addon:
|
||||||
addon = self.sys_addons.from_uuid(hassio_token)
|
addon = self.sys_addons.from_uuid(hassio_token)
|
||||||
|
|
||||||
|
@ -5,27 +5,61 @@ import re
|
|||||||
from aiohttp.web import middleware
|
from aiohttp.web import middleware
|
||||||
from aiohttp.web_exceptions import HTTPUnauthorized, HTTPForbidden
|
from aiohttp.web_exceptions import HTTPUnauthorized, HTTPForbidden
|
||||||
|
|
||||||
from ..const import HEADER_TOKEN, REQUEST_FROM
|
from ..const import (
|
||||||
|
HEADER_TOKEN, REQUEST_FROM, ROLE_ADMIN, ROLE_DEFAULT, ROLE_HOMEASSISTANT,
|
||||||
|
ROLE_MANAGER)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Free to call or have own security concepts
|
||||||
NO_SECURITY_CHECK = re.compile(
|
NO_SECURITY_CHECK = re.compile(
|
||||||
r"^(?:"
|
r"^(?:"
|
||||||
r"|/homeassistant/api/.*$"
|
r"|/homeassistant/api/.*"
|
||||||
r"|/homeassistant/websocket$"
|
r"|/homeassistant/websocket"
|
||||||
r"|/supervisor/ping$"
|
r"|/supervisor/ping"
|
||||||
|
r"|/services.*"
|
||||||
r")$"
|
r")$"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Can called by every add-on
|
||||||
ADDONS_API_BYPASS = re.compile(
|
ADDONS_API_BYPASS = re.compile(
|
||||||
r"^(?:"
|
r"^(?:"
|
||||||
r"|/homeassistant/info$"
|
r"|/homeassistant/info"
|
||||||
r"|/supervisor/info$"
|
r"|/supervisor/info"
|
||||||
r"|/addons(?:/self/[^/]+)?$"
|
r"|/addons(?:/self/(?!security)[^/]+)?"
|
||||||
r")$"
|
r")$"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Policy role add-on API access
|
||||||
|
ADDONS_ROLE_ACCESS = {
|
||||||
|
ROLE_DEFAULT: re.compile(
|
||||||
|
r"^(?:"
|
||||||
|
r"|/[^/]+/info"
|
||||||
|
r")$"
|
||||||
|
),
|
||||||
|
ROLE_HOMEASSISTANT: re.compile(
|
||||||
|
r"^(?:"
|
||||||
|
r"|/homeassistant/.+"
|
||||||
|
r")$"
|
||||||
|
),
|
||||||
|
ROLE_MANAGER: re.compile(
|
||||||
|
r"^(?:"
|
||||||
|
r"|/homeassistant/.+"
|
||||||
|
r"|/host/.+"
|
||||||
|
r"|/hardware/.+"
|
||||||
|
r"|/hassos/.+"
|
||||||
|
r"|/supervisor/.+"
|
||||||
|
r"|/addons/.+/(?!security|options).+"
|
||||||
|
r"|/addons(?:/self/(?!security).+)"
|
||||||
|
r"|/snapshots.*"
|
||||||
|
r")$"
|
||||||
|
),
|
||||||
|
ROLE_ADMIN: re.compile(
|
||||||
|
r".+"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class SecurityMiddleware(CoreSysAttributes):
|
class SecurityMiddleware(CoreSysAttributes):
|
||||||
"""Security middleware functions."""
|
"""Security middleware functions."""
|
||||||
@ -66,17 +100,22 @@ class SecurityMiddleware(CoreSysAttributes):
|
|||||||
addon = None
|
addon = None
|
||||||
if hassio_token and not request_from:
|
if hassio_token and not request_from:
|
||||||
addon = self.sys_addons.from_token(hassio_token)
|
addon = self.sys_addons.from_token(hassio_token)
|
||||||
# Need removed with 131
|
# REMOVE 132
|
||||||
if not addon:
|
if not addon:
|
||||||
addon = self.sys_addons.from_uuid(hassio_token)
|
addon = self.sys_addons.from_uuid(hassio_token)
|
||||||
|
|
||||||
# Check Add-on API access
|
# Check Add-on API access
|
||||||
if addon and addon.access_hassio_api:
|
if addon and ADDONS_API_BYPASS.match(request.path):
|
||||||
_LOGGER.info("%s access from %s", request.path, addon.slug)
|
|
||||||
request_from = addon.slug
|
|
||||||
elif addon and ADDONS_API_BYPASS.match(request.path):
|
|
||||||
_LOGGER.debug("Passthrough %s from %s", request.path, addon.slug)
|
_LOGGER.debug("Passthrough %s from %s", request.path, addon.slug)
|
||||||
request_from = addon.slug
|
request_from = addon.slug
|
||||||
|
elif addon and addon.access_hassio_api:
|
||||||
|
# Check Role
|
||||||
|
if ADDONS_ROLE_ACCESS[addon.hassio_role].match(request.path):
|
||||||
|
_LOGGER.info("%s access from %s", request.path, addon.slug)
|
||||||
|
request_from = addon.slug
|
||||||
|
else:
|
||||||
|
_LOGGER.warning("%s no role for %s", request.path, addon.slug)
|
||||||
|
request_from = addon.slug # REMOVE: 132
|
||||||
|
|
||||||
if request_from:
|
if request_from:
|
||||||
request[REQUEST_FROM] = request_from
|
request[REQUEST_FROM] = request_from
|
||||||
|
@ -184,6 +184,7 @@ ATTR_DOCKER_API = 'docker_api'
|
|||||||
ATTR_FULL_ACCESS = 'full_access'
|
ATTR_FULL_ACCESS = 'full_access'
|
||||||
ATTR_PROTECTED = 'protected'
|
ATTR_PROTECTED = 'protected'
|
||||||
ATTR_RATING = 'rating'
|
ATTR_RATING = 'rating'
|
||||||
|
ATTR_HASSIO_ROLE = 'hassio_role'
|
||||||
|
|
||||||
SERVICE_MQTT = 'mqtt'
|
SERVICE_MQTT = 'mqtt'
|
||||||
|
|
||||||
@ -246,3 +247,8 @@ FEATURES_REBOOT = 'reboot'
|
|||||||
FEATURES_HASSOS = 'hassos'
|
FEATURES_HASSOS = 'hassos'
|
||||||
FEATURES_HOSTNAME = 'hostname'
|
FEATURES_HOSTNAME = 'hostname'
|
||||||
FEATURES_SERVICES = 'services'
|
FEATURES_SERVICES = 'services'
|
||||||
|
|
||||||
|
ROLE_DEFAULT = 'default'
|
||||||
|
ROLE_HOMEASSISTANT = 'homeassistant'
|
||||||
|
ROLE_MANAGER = 'manager'
|
||||||
|
ROLE_ADMIN = 'admin'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user