From 9650fd2ba1e60b953a07aed09cf31038722587c1 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Thu, 10 Aug 2023 18:58:33 +0200 Subject: [PATCH] Extend container image name validator (#4480) * Extend container image name validator The current validator allows certain invalid names (e.g. upper case), but disallows valid cases (such as ttl.sh/myimage). Improve the container image validator to support more valid options and at the same time disallow some of the invalid options. Note that this is not a complete/perfect validation still. A much much more sophisticated regex would be necessary to be 100% accurate. Also we format the string and replace {machine}/{arch} using Python format strings. In that regard the image format in Supervisor deviates from the Docker/OCI container image name format. * Use an actual invalid image name in config validation --- supervisor/validate.py | 4 +++- tests/addons/test_config.py | 2 +- tests/test_validate.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/supervisor/validate.py b/supervisor/validate.py index 329a34f3e..3fc081d85 100644 --- a/supervisor/validate.py +++ b/supervisor/validate.py @@ -49,7 +49,9 @@ RE_REGISTRY = re.compile(r"^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$") # pylint: disable=invalid-name network_port = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)) wait_boot = vol.All(vol.Coerce(int), vol.Range(min=1, max=60)) -docker_image = vol.Match(r"^([a-zA-Z\-\.:\d{}]+/)*?([\-\w{}]+)/([\-\w{}]+)$") +docker_image = vol.Match( + r"^([a-z0-9][a-z0-9.\-]*(:[0-9]+)?/)*?([a-z0-9{][a-z0-9.\-_{}]*/)*?([a-z0-9{][a-z0-9.\-_{}]*)$" +) uuid_match = vol.Match(r"^[0-9a-f]{32}$") sha256 = vol.Match(r"^[0-9a-f]{64}$") token = vol.Match(r"^[0-9a-f]{32,256}$") diff --git a/tests/addons/test_config.py b/tests/addons/test_config.py index a83a9f995..24dde598b 100644 --- a/tests/addons/test_config.py +++ b/tests/addons/test_config.py @@ -110,7 +110,7 @@ def test_invalid_repository(): """Validate basic config with invalid repositories.""" config = load_json_fixture("basic-addon-config.json") - config["image"] = "something" + config["image"] = "-invalid-something" with pytest.raises(vol.Invalid): vd.SCHEMA_ADDON_CONFIG(config) diff --git a/tests/test_validate.py b/tests/test_validate.py index c02f43f5e..70743f0b6 100644 --- a/tests/test_validate.py +++ b/tests/test_validate.py @@ -16,6 +16,24 @@ DNS_GOOD_V6 = [ "DNS://2606:4700:4700::1001", # cloudflare ] DNS_BAD = ["hello world", "https://foo.bar", "", "dns://example.com"] +IMAGE_NAME_GOOD = [ + "ghcr.io/home-assistant/{machine}-homeassistant", + "ghcr.io/home-assistant/{arch}-homeassistant", + "homeassistant/{arch}-homeassistant", + "doocker.io/homeassistant/{arch}-homeassistant", + "ghcr.io/home-assistant/amd64-homeassistant", + "homeassistant/amd64-homeassistant", + "ttl.sh/homeassistant", + "myreg.local:8080/homeassistant", +] +IMAGE_NAME_BAD = [ + "ghcr.io/home-assistant/homeassistant:123", + ".ghcr.io/home-assistant/homeassistant", + "HOMEASSISTANT/homeassistant", + "homeassistant/HOMEASSISTANT", + "homeassistant/_homeassistant", + "homeassistant/-homeassistant", +] async def test_dns_url_v4_good(): @@ -72,6 +90,19 @@ def test_dns_server_list_bad_combined(): assert validate.dns_server_list(combined) +def test_image_name_good(): + """Test container image names validator with known-good image names.""" + for image_name in IMAGE_NAME_GOOD: + assert validate.docker_image(image_name) + + +def test_image_name_bad(): + """Test container image names validator with known-bad image names.""" + for image_name in IMAGE_NAME_BAD: + with pytest.raises(vol.error.Invalid): + assert validate.docker_image(image_name) + + def test_version_complex(): """Test version simple with good version.""" for version in (