feat: add manifest_validator.py — security chokepoint for compose and manifest validation
Unit Tests / test (push) Successful in 11m18s
Unit Tests / test (push) Successful in 11m18s
Rejects privileged compose configs (network_mode:host, pid:host, ipc:host, userns_mode:host, cap_add:ALL, string commands, missing cell-network, reserved container names). Validates manifest schema_version=3, image digest pinning (sha256 required, :tag-only rejected), and provision hook format. Wired into ServiceComposer.write_compose() and ServiceStoreManager.install() as a single enforcement point. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,12 @@ def _make_manager(tmp_dir=None, installed=None, identity=None):
|
||||
return mgr
|
||||
|
||||
|
||||
_VALID_IMAGE = (
|
||||
'git.pic.ngo/roof/myapp@sha256:'
|
||||
+ 'a' * 64
|
||||
)
|
||||
|
||||
|
||||
def _valid_manifest(**overrides):
|
||||
"""Return a minimal valid manifest, with optional field overrides."""
|
||||
m = {
|
||||
@@ -65,7 +71,7 @@ def _valid_manifest(**overrides):
|
||||
'name': 'My App',
|
||||
'version': '1.0.0',
|
||||
'author': 'Test Author',
|
||||
'image': 'git.pic.ngo/roof/myapp:latest',
|
||||
'image': _VALID_IMAGE,
|
||||
'container_name': 'cell-myapp',
|
||||
}
|
||||
m.update(overrides)
|
||||
@@ -143,16 +149,24 @@ class TestValidateManifestImage(unittest.TestCase):
|
||||
self.assertFalse(ok)
|
||||
self.assertTrue(any('image must match' in e for e in errs))
|
||||
|
||||
def test_image_matching_git_pic_ngo_roof_with_tag_passes(self):
|
||||
m = _valid_manifest(image='git.pic.ngo/roof/something:1.2.3')
|
||||
def test_image_matching_git_pic_ngo_roof_with_digest_passes(self):
|
||||
digest = 'a' * 64
|
||||
m = _valid_manifest(image=f'git.pic.ngo/roof/something@sha256:{digest}')
|
||||
ok, errs = ServiceStoreManager._validate_manifest(m)
|
||||
self.assertTrue(ok)
|
||||
self.assertEqual(errs, [])
|
||||
|
||||
def test_image_git_pic_ngo_roof_no_tag_passes(self):
|
||||
def test_image_tag_only_rejected(self):
|
||||
# Digest pinning is required; tag-only images are rejected.
|
||||
m = _valid_manifest(image='git.pic.ngo/roof/something:1.2.3')
|
||||
ok, errs = ServiceStoreManager._validate_manifest(m)
|
||||
self.assertFalse(ok)
|
||||
|
||||
def test_image_git_pic_ngo_roof_no_tag_rejected(self):
|
||||
# No tag and no digest — rejected because digest pin is required.
|
||||
m = _valid_manifest(image='git.pic.ngo/roof/myservice')
|
||||
ok, errs = ServiceStoreManager._validate_manifest(m)
|
||||
self.assertTrue(ok)
|
||||
self.assertFalse(ok)
|
||||
|
||||
def test_image_wrong_registry_rejected(self):
|
||||
m = _valid_manifest(image='ghcr.io/roof/myapp:latest')
|
||||
|
||||
Reference in New Issue
Block a user