fix: allow first-party store service subdomains and registry images
Unit Tests / test (push) Successful in 11m25s

Two manifest validation bugs blocked all store service installs:

1. service_store_manager.RESERVED_SUBDOMAINS included 'mail', which
   prevented the email service from using its required subdomain.
   Removed mail/calendar/files/webmail — they belong to official PIC
   store services and must be claimable by them.

2. manifest_validator required @sha256 digest pins on ALL images,
   including first-party git.pic.ngo/roof/* images that the PIC team
   builds and controls. service_store_manager._validate_manifest already
   only warned for first-party images; the secondary validator was
   stricter than intended, causing a hard reject on :latest tags.
   Aligned to warn-not-reject for first-party; malformed digests (when
   provided) are still a hard error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-30 03:09:41 -04:00
parent c493630bb5
commit f7bb2cc962
4 changed files with 39 additions and 21 deletions
+14 -11
View File
@@ -162,12 +162,13 @@ class TestValidateManifest(unittest.TestCase):
)
self.assertTrue(ok)
def test_image_tag_only_rejected(self):
def test_image_tag_only_first_party_allowed(self):
# First-party images without a digest pin are allowed (warning only).
ok, errs = validate_manifest(
_minimal_manifest(image='git.pic.ngo/roof/myapp:latest')
)
self.assertFalse(ok)
self.assertTrue(any('image' in e for e in errs))
self.assertTrue(ok)
self.assertEqual(errs, [])
def test_image_wrong_registry_rejected(self):
digest = 'a' * 64
@@ -190,11 +191,13 @@ class TestValidateManifest(unittest.TestCase):
)
self.assertFalse(ok)
def test_image_no_tag_no_digest_rejected(self):
def test_image_no_tag_no_digest_first_party_allowed(self):
# No tag and no digest — Docker defaults to :latest; treated as tag-only, allowed.
ok, errs = validate_manifest(
_minimal_manifest(image='git.pic.ngo/roof/myapp')
)
self.assertFalse(ok)
self.assertTrue(ok)
self.assertEqual(errs, [])
def test_image_absent_passes(self):
m = _minimal_manifest()
@@ -1261,12 +1264,12 @@ class TestServiceStoreManagerSecurityIntegration(unittest.TestCase):
ok, errs = ServiceStoreManager._validate_manifest(m)
self.assertTrue(ok)
def test_validate_manifest_rejects_image_tag_only(self):
"""Image without digest pin must be rejected even for git.pic.ngo/roof/* images."""
def test_validate_manifest_allows_image_tag_only_first_party(self):
"""First-party images without a digest pin are allowed (warning only)."""
from service_store_manager import ServiceStoreManager
m = self._valid_manifest(image='git.pic.ngo/roof/myapp:latest')
ok, errs = ServiceStoreManager._validate_manifest(m)
self.assertFalse(ok)
self.assertTrue(ok)
def test_validate_manifest_accepts_image_with_digest(self):
from service_store_manager import ServiceStoreManager
@@ -1309,7 +1312,8 @@ class TestInstallManifestValidation(unittest.TestCase):
))
return ssm
def test_install_returns_error_when_image_tag_only(self):
def test_install_succeeds_when_image_tag_only_first_party(self):
# First-party images without a digest pin are allowed (warning, not error).
manifest = {
'id': 'myapp',
'name': 'My App',
@@ -1320,8 +1324,7 @@ class TestInstallManifestValidation(unittest.TestCase):
}
ssm = self._make_ssm(manifest)
result = ssm.install('myapp')
self.assertFalse(result['ok'])
self.assertIn('errors', result)
self.assertTrue(result['ok'])
def test_install_returns_error_when_kind_is_builtin(self):
digest = 'c' * 64