Implement connectivity store services (wireguard-ext, openvpn-client, tor)
Unit Tests / test (push) Successful in 11m31s
Unit Tests / test (push) Successful in 11m31s
- ConnectivityManager: move config dirs to data_dir/services/<id>/config so Docker can bind-mount them into store-service containers (Docker resolves bind-mount paths on the host, not inside the API container). Add _migrate_legacy_configs to copy existing files from the old config_dir location on first boot. - manifest_validator: add allow_host_network parameter to validate_rendered_compose. When True, waives the external-network requirement, permits network_mode: host, and allows devices: — all needed by VPN/Tor containers that must share the host network namespace to create tun/wg interfaces. Non-host services are unaffected. - service_composer: read requires_host_network from the manifest and pass allow_host_network=True to validate_rendered_compose for connectivity services. - Tests: update file-path assertions to new data_dir layout; add TestMigrateLegacyConfigs, TestValidateRenderedComposeHostNetwork, and two TestWriteCompose cases for the host-network path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -967,6 +967,127 @@ class TestValidateRenderedCompose(unittest.TestCase):
|
||||
self.assertTrue(any('bad' in e for e in errs))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TestValidateRenderedComposeHostNetwork
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestValidateRenderedComposeHostNetwork(unittest.TestCase):
|
||||
"""Tests for allow_host_network=True — connectivity services."""
|
||||
|
||||
_HOST_NET_COMPOSE = (
|
||||
'services:\n'
|
||||
' wireguard-ext:\n'
|
||||
' image: git.pic.ngo/roof/svc-wireguard-ext:latest\n'
|
||||
' container_name: cell-wg-ext\n'
|
||||
' restart: unless-stopped\n'
|
||||
' network_mode: host\n'
|
||||
' cap_add:\n'
|
||||
' - NET_ADMIN\n'
|
||||
' volumes:\n'
|
||||
' - /app/data/services/wireguard-ext/config:/etc/wireguard\n'
|
||||
)
|
||||
|
||||
def test_host_network_compose_passes_with_flag(self):
|
||||
ok, errs = validate_rendered_compose(
|
||||
self._HOST_NET_COMPOSE,
|
||||
allowed_data_dir='/app/data',
|
||||
allow_host_network=True,
|
||||
)
|
||||
self.assertTrue(ok, errs)
|
||||
|
||||
def test_host_network_compose_fails_without_flag(self):
|
||||
ok, errs = validate_rendered_compose(
|
||||
self._HOST_NET_COMPOSE,
|
||||
allowed_data_dir='/app/data',
|
||||
allow_host_network=False,
|
||||
)
|
||||
self.assertFalse(ok)
|
||||
|
||||
def test_network_mode_host_rejected_without_flag(self):
|
||||
yaml_text = (
|
||||
'services:\n'
|
||||
' svc:\n'
|
||||
' image: git.pic.ngo/roof/foo:latest\n'
|
||||
' network_mode: host\n'
|
||||
'networks:\n'
|
||||
' cell-network:\n'
|
||||
' external: true\n'
|
||||
)
|
||||
ok, errs = validate_rendered_compose(yaml_text)
|
||||
self.assertFalse(ok)
|
||||
self.assertTrue(any('network_mode' in e for e in errs))
|
||||
|
||||
def test_devices_allowed_with_flag(self):
|
||||
yaml_text = (
|
||||
'services:\n'
|
||||
' openvpn-client:\n'
|
||||
' image: git.pic.ngo/roof/svc-openvpn-client:latest\n'
|
||||
' container_name: cell-openvpn\n'
|
||||
' network_mode: host\n'
|
||||
' cap_add:\n'
|
||||
' - NET_ADMIN\n'
|
||||
' devices:\n'
|
||||
' - /dev/net/tun\n'
|
||||
' volumes:\n'
|
||||
' - /app/data/services/openvpn-client/config:/etc/openvpn\n'
|
||||
)
|
||||
ok, errs = validate_rendered_compose(
|
||||
yaml_text,
|
||||
allowed_data_dir='/app/data',
|
||||
allow_host_network=True,
|
||||
)
|
||||
self.assertTrue(ok, errs)
|
||||
|
||||
def test_devices_rejected_without_flag(self):
|
||||
yaml_text = (
|
||||
'services:\n'
|
||||
' svc:\n'
|
||||
' image: git.pic.ngo/roof/foo:latest\n'
|
||||
' devices:\n'
|
||||
' - /dev/net/tun\n'
|
||||
'networks:\n'
|
||||
' cell-network:\n'
|
||||
' external: true\n'
|
||||
)
|
||||
ok, errs = validate_rendered_compose(yaml_text)
|
||||
self.assertFalse(ok)
|
||||
self.assertTrue(any('devices' in e for e in errs))
|
||||
|
||||
def test_no_external_network_ok_with_flag(self):
|
||||
yaml_text = (
|
||||
'services:\n'
|
||||
' tor:\n'
|
||||
' image: git.pic.ngo/roof/svc-tor:latest\n'
|
||||
' network_mode: host\n'
|
||||
)
|
||||
ok, errs = validate_rendered_compose(yaml_text, allow_host_network=True)
|
||||
self.assertTrue(ok, errs)
|
||||
|
||||
def test_privileged_still_rejected_with_flag(self):
|
||||
yaml_text = (
|
||||
'services:\n'
|
||||
' svc:\n'
|
||||
' image: git.pic.ngo/roof/foo:latest\n'
|
||||
' network_mode: host\n'
|
||||
' privileged: true\n'
|
||||
)
|
||||
ok, errs = validate_rendered_compose(yaml_text, allow_host_network=True)
|
||||
self.assertFalse(ok)
|
||||
self.assertTrue(any('privileged' in e for e in errs))
|
||||
|
||||
def test_non_host_network_mode_rejected_with_flag(self):
|
||||
"""When allow_host_network=True, only 'host' is accepted as network_mode."""
|
||||
yaml_text = (
|
||||
'services:\n'
|
||||
' svc:\n'
|
||||
' image: git.pic.ngo/roof/foo:latest\n'
|
||||
' network_mode: none\n'
|
||||
)
|
||||
ok, errs = validate_rendered_compose(yaml_text, allow_host_network=True)
|
||||
self.assertFalse(ok)
|
||||
self.assertTrue(any('network_mode' in e for e in errs))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TestValidateProvisionHook
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user