1
0
mirror of https://github.com/home-assistant/supervisor.git synced 2026-04-02 00:07:16 +01:00
Files
supervisor/tests/docker/test_credentials.py
Stefan Agner 1fd78dfc4e Fix Docker Hub registry auth for containerd image store (#6677)
aiodocker derives ServerAddress for X-Registry-Auth by doing
image.partition("/"). For Docker Hub images like
"homeassistant/amd64-supervisor", this extracts "homeassistant"
(the namespace) instead of "docker.io" (the registry).

With the classic graphdriver image store, ServerAddress was never
checked and credentials were sent regardless. With the containerd
image store (default since Docker v29 / HAOS 15), the resolver
compares ServerAddress against the actual registry host and silently
drops credentials on mismatch, falling back to anonymous access.

Fix by prefixing Docker Hub images with "docker.io/" when registry
credentials are configured, so aiodocker sets ServerAddress correctly.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 14:43:18 +02:00

137 lines
5.2 KiB
Python

"""Test docker login."""
import pytest
# pylint: disable=protected-access
from supervisor.coresys import CoreSys
from supervisor.docker.const import DOCKER_HUB, DOCKER_HUB_LEGACY
from supervisor.docker.interface import DockerInterface
from supervisor.docker.utils import get_registry_from_image
@pytest.mark.parametrize(
("image_ref", "expected_registry"),
[
# No registry - Docker Hub images
("nginx", None),
("nginx:latest", None),
("library/nginx", None),
("library/nginx:latest", None),
("homeassistant/amd64-supervisor", None),
("homeassistant/amd64-supervisor:1.2.3", None),
# Registry with dot
("ghcr.io/homeassistant/amd64-supervisor", "ghcr.io"),
("ghcr.io/homeassistant/amd64-supervisor:latest", "ghcr.io"),
("myregistry.com/nginx", "myregistry.com"),
("registry.example.com/org/image:v1", "registry.example.com"),
("127.0.0.1/myimage", "127.0.0.1"),
# Registry with port
("myregistry:5000/myimage", "myregistry:5000"),
("localhost:5000/myimage", "localhost:5000"),
("registry.io:5000/org/app:v1", "registry.io:5000"),
# localhost special case
("localhost/myimage", "localhost"),
("localhost/myimage:tag", "localhost"),
# IPv6
("[::1]:5000/myimage", "[::1]:5000"),
("[2001:db8::1]:5000/myimage:tag", "[2001:db8::1]:5000"),
],
)
def test_get_registry_from_image(image_ref: str, expected_registry: str | None):
"""Test get_registry_from_image extracts registry from image reference.
Based on Docker's reference implementation:
vendor/github.com/distribution/reference/normalize.go
"""
assert get_registry_from_image(image_ref) == expected_registry
def test_no_credentials(coresys: CoreSys, test_docker_interface: DockerInterface):
"""Test no credentials."""
coresys.docker.config._data["registries"] = {
DOCKER_HUB: {"username": "Spongebob Squarepants", "password": "Password1!"}
}
credentials, image = test_docker_interface._get_credentials("ghcr.io/homeassistant")
assert not credentials
assert image == "ghcr.io/homeassistant"
credentials, image = test_docker_interface._get_credentials(
"ghcr.io/homeassistant/amd64-supervisor"
)
assert not credentials
assert image == "ghcr.io/homeassistant/amd64-supervisor"
def test_no_matching_credentials(
coresys: CoreSys, test_docker_interface: DockerInterface
):
"""Test no matching credentials."""
coresys.docker.config._data["registries"] = {
DOCKER_HUB: {"username": "Spongebob Squarepants", "password": "Password1!"}
}
credentials, image = test_docker_interface._get_credentials("ghcr.io/homeassistant")
assert not credentials
assert image == "ghcr.io/homeassistant"
credentials, image = test_docker_interface._get_credentials(
"ghcr.io/homeassistant/amd64-supervisor"
)
assert not credentials
assert image == "ghcr.io/homeassistant/amd64-supervisor"
def test_matching_credentials(coresys: CoreSys, test_docker_interface: DockerInterface):
"""Test matching credentials."""
coresys.docker.config._data["registries"] = {
"ghcr.io": {"username": "Octocat", "password": "Password1!"},
DOCKER_HUB: {"username": "Spongebob Squarepants", "password": "Password1!"},
}
credentials, image = test_docker_interface._get_credentials(
"ghcr.io/homeassistant/amd64-supervisor"
)
assert credentials["registry"] == "ghcr.io"
assert image == "ghcr.io/homeassistant/amd64-supervisor"
credentials, image = test_docker_interface._get_credentials(
"homeassistant/amd64-supervisor"
)
assert credentials["username"] == "Spongebob Squarepants"
assert credentials["registry"] == DOCKER_HUB
# Docker Hub images should be prefixed with docker.io/ for correct ServerAddress
assert image == f"{DOCKER_HUB}/homeassistant/amd64-supervisor"
def test_legacy_docker_hub_credentials(
coresys: CoreSys, test_docker_interface: DockerInterface
):
"""Test legacy hub.docker.com credentials are used for Docker Hub images."""
coresys.docker.config._data["registries"] = {
DOCKER_HUB_LEGACY: {"username": "LegacyUser", "password": "Password1!"},
}
credentials, image = test_docker_interface._get_credentials(
"homeassistant/amd64-supervisor"
)
assert credentials["username"] == "LegacyUser"
assert credentials["registry"] == DOCKER_HUB_LEGACY
assert image == f"{DOCKER_HUB}/homeassistant/amd64-supervisor"
def test_docker_hub_preferred_over_legacy(
coresys: CoreSys, test_docker_interface: DockerInterface
):
"""Test docker.io is preferred over legacy hub.docker.com when both exist."""
coresys.docker.config._data["registries"] = {
DOCKER_HUB: {"username": "NewUser", "password": "Password1!"},
DOCKER_HUB_LEGACY: {"username": "LegacyUser", "password": "Password2!"},
}
credentials, image = test_docker_interface._get_credentials(
"homeassistant/amd64-supervisor"
)
# docker.io should be preferred
assert credentials["username"] == "NewUser"
assert credentials["registry"] == DOCKER_HUB
assert image == f"{DOCKER_HUB}/homeassistant/amd64-supervisor"