1
0
mirror of https://github.com/home-assistant/core.git synced 2026-07-10 16:19:29 +01:00

Fix hassio auth IndexError on Supervisor Unix socket requests (#169911)

This commit is contained in:
Stefan Agner
2026-05-06 17:48:35 +02:00
committed by Franck Nijhof
parent aac49a567f
commit a4227ef1bc
2 changed files with 59 additions and 9 deletions
+13 -8
View File
@@ -12,6 +12,7 @@ import voluptuous as vol
from homeassistant.auth.models import User
from homeassistant.auth.providers import homeassistant as auth_ha
from homeassistant.components.http import KEY_HASS, KEY_HASS_USER, HomeAssistantView
from homeassistant.components.http.const import is_supervisor_unix_socket_request
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
@@ -41,14 +42,18 @@ class HassIOBaseAuth(HomeAssistantView):
def _check_access(self, request: web.Request) -> None:
"""Check if this call is from Supervisor."""
# Check caller IP
hassio_ip = os.environ["SUPERVISOR"].split(":")[0]
assert request.transport
if ip_address(request.transport.get_extra_info("peername")[0]) != ip_address(
hassio_ip
):
_LOGGER.error("Invalid auth request from %s", request.remote)
raise HTTPUnauthorized
# Requests over the Supervisor Unix socket are authenticated by the
# http auth middleware as the Supervisor user, so the caller-IP check
# below does not apply (and would crash, since `peername` is empty for
# Unix sockets). The user-ID check still runs to ensure only the
# Supervisor user can reach this endpoint.
if not is_supervisor_unix_socket_request(request):
hassio_ip = os.environ["SUPERVISOR"].split(":")[0]
assert request.transport
peername = request.transport.get_extra_info("peername")
if not peername or ip_address(peername[0]) != ip_address(hassio_ip):
_LOGGER.error("Invalid auth request from %s", request.remote)
raise HTTPUnauthorized
# Check caller token
if request[KEY_HASS_USER].id != self.user.id:
+46 -1
View File
@@ -1,11 +1,17 @@
"""The tests for the hassio component."""
from contextlib import AbstractContextManager, ExitStack as DefaultContext
from http import HTTPStatus
from unittest.mock import Mock, patch
from unittest.mock import MagicMock, Mock, patch
from aiohttp.test_utils import TestClient
from aiohttp.web_exceptions import HTTPUnauthorized
import pytest
from homeassistant.auth.providers.homeassistant import InvalidAuth
from homeassistant.components.hassio.auth import HassIOBaseAuth
from homeassistant.components.hassio.const import DATA_CONFIG_STORE
from homeassistant.core import HomeAssistant
async def test_auth_success(hassio_client_supervisor: TestClient) -> None:
@@ -162,6 +168,45 @@ async def test_password_fails_no_auth(hassio_noauth_client: TestClient) -> None:
assert resp.status == HTTPStatus.UNAUTHORIZED
@pytest.mark.parametrize(
("peername", "unix_socket", "expectation"),
[
# Unix socket transports report an empty string for peername. Before
# the fix this raised IndexError on `peername[0]`.
("", True, DefaultContext()),
# Defensive: a TCP transport with no peername at all should be
# rejected, not crash.
(None, False, pytest.raises(HTTPUnauthorized)),
],
)
@pytest.mark.usefixtures("hassio_stubs")
async def test_check_access_unix_socket_or_missing_peername(
hass: HomeAssistant,
peername: str | None,
unix_socket: bool,
expectation: AbstractContextManager,
) -> None:
"""Test _check_access handles Unix socket requests and missing peername."""
hassio_user_id = hass.data[DATA_CONFIG_STORE].data.hassio_user
assert hassio_user_id is not None
user = await hass.auth.async_get_user(hassio_user_id)
assert user is not None
auth_view = HassIOBaseAuth(hass, user)
request = MagicMock()
request.transport.get_extra_info.return_value = peername
request.__getitem__.return_value = user
with (
patch(
"homeassistant.components.hassio.auth.is_supervisor_unix_socket_request",
return_value=unix_socket,
),
expectation,
):
auth_view._check_access(request)
async def test_password_no_user(hassio_client_supervisor: TestClient) -> None:
"""Test changing password for invalid user."""
resp = await hassio_client_supervisor.post(