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:
@@ -253,12 +253,12 @@ class TestUploadWireguardExt(unittest.TestCase):
|
||||
|
||||
def test_valid_conf_writes_file_to_correct_path(self):
|
||||
self.mgr.upload_wireguard_ext(self._valid_conf())
|
||||
expected = os.path.join(self.tmp, 'connectivity', 'wireguard_ext', 'wg_ext0.conf')
|
||||
expected = os.path.join(self.tmp, 'services', 'wireguard-ext', 'config', 'wg_ext0.conf')
|
||||
self.assertTrue(os.path.isfile(expected), f'Expected file at {expected}')
|
||||
|
||||
def test_valid_conf_file_has_mode_0600(self):
|
||||
self.mgr.upload_wireguard_ext(self._valid_conf())
|
||||
path = os.path.join(self.tmp, 'connectivity', 'wireguard_ext', 'wg_ext0.conf')
|
||||
path = os.path.join(self.tmp, 'services', 'wireguard-ext', 'config', 'wg_ext0.conf')
|
||||
mode = stat.S_IMODE(os.stat(path).st_mode)
|
||||
self.assertEqual(mode, 0o600, f'Expected 0600, got {oct(mode)}')
|
||||
|
||||
@@ -272,7 +272,7 @@ class TestUploadWireguardExt(unittest.TestCase):
|
||||
def test_file_content_has_hooks_stripped(self):
|
||||
conf = "[Interface]\nPrivateKey = abc\nPostUp = evil\n"
|
||||
self.mgr.upload_wireguard_ext(conf)
|
||||
path = os.path.join(self.tmp, 'connectivity', 'wireguard_ext', 'wg_ext0.conf')
|
||||
path = os.path.join(self.tmp, 'services', 'wireguard-ext', 'config', 'wg_ext0.conf')
|
||||
with open(path) as f:
|
||||
content = f.read()
|
||||
self.assertNotIn('PostUp', content)
|
||||
@@ -301,12 +301,12 @@ class TestUploadOpenvpn(unittest.TestCase):
|
||||
|
||||
def test_valid_conf_writes_file_at_correct_path(self):
|
||||
self.mgr.upload_openvpn(self._valid_ovpn(), name='my-vpn')
|
||||
expected = os.path.join(self.tmp, 'connectivity', 'openvpn', 'my-vpn.ovpn')
|
||||
expected = os.path.join(self.tmp, 'services', 'openvpn-client', 'config', 'my-vpn.ovpn')
|
||||
self.assertTrue(os.path.isfile(expected), f'Expected file at {expected}')
|
||||
|
||||
def test_valid_conf_file_has_mode_0600(self):
|
||||
self.mgr.upload_openvpn(self._valid_ovpn(), name='my-vpn')
|
||||
path = os.path.join(self.tmp, 'connectivity', 'openvpn', 'my-vpn.ovpn')
|
||||
path = os.path.join(self.tmp, 'services', 'openvpn-client', 'config', 'my-vpn.ovpn')
|
||||
mode = stat.S_IMODE(os.stat(path).st_mode)
|
||||
self.assertEqual(mode, 0o600, f'Expected 0600, got {oct(mode)}')
|
||||
|
||||
@@ -339,19 +339,77 @@ class TestUploadOpenvpn(unittest.TestCase):
|
||||
def test_default_name_default_passes(self):
|
||||
result = self.mgr.upload_openvpn(self._valid_ovpn())
|
||||
self.assertTrue(result['ok'])
|
||||
expected = os.path.join(self.tmp, 'connectivity', 'openvpn', 'default.ovpn')
|
||||
expected = os.path.join(self.tmp, 'services', 'openvpn-client', 'config', 'default.ovpn')
|
||||
self.assertTrue(os.path.isfile(expected))
|
||||
|
||||
def test_hooks_stripped_from_stored_file(self):
|
||||
conf = "client\ndev tun\nup /sbin/bad.sh\nproto udp\n"
|
||||
self.mgr.upload_openvpn(conf, name='clean')
|
||||
path = os.path.join(self.tmp, 'connectivity', 'openvpn', 'clean.ovpn')
|
||||
path = os.path.join(self.tmp, 'services', 'openvpn-client', 'config', 'clean.ovpn')
|
||||
with open(path) as f:
|
||||
content = f.read()
|
||||
self.assertNotIn('up /sbin/bad.sh', content)
|
||||
self.assertIn('proto udp', content)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _migrate_legacy_configs
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestMigrateLegacyConfigs(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.mkdtemp()
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tmp, ignore_errors=True)
|
||||
|
||||
def test_no_op_when_legacy_dir_absent(self):
|
||||
"""No errors when legacy connectivity/ dir does not exist."""
|
||||
mgr = _make_manager(tmp_dir=self.tmp)
|
||||
# Should not raise; legacy dir simply doesn't exist
|
||||
mgr._migrate_legacy_configs(os.path.join(self.tmp, 'nonexistent'))
|
||||
|
||||
def test_wg_conf_copied_to_new_location(self):
|
||||
legacy_wg = os.path.join(self.tmp, 'connectivity', 'wireguard_ext')
|
||||
os.makedirs(legacy_wg)
|
||||
src = os.path.join(legacy_wg, 'wg_ext0.conf')
|
||||
with open(src, 'w') as f:
|
||||
f.write('[Interface]\nPrivateKey = abc\n')
|
||||
|
||||
mgr = _make_manager(tmp_dir=self.tmp)
|
||||
dst = os.path.join(self.tmp, 'services', 'wireguard-ext', 'config', 'wg_ext0.conf')
|
||||
self.assertTrue(os.path.isfile(dst), f'Expected migrated file at {dst}')
|
||||
|
||||
def test_ovpn_copied_to_new_location(self):
|
||||
legacy_ovpn = os.path.join(self.tmp, 'connectivity', 'openvpn')
|
||||
os.makedirs(legacy_ovpn)
|
||||
src = os.path.join(legacy_ovpn, 'default.ovpn')
|
||||
with open(src, 'w') as f:
|
||||
f.write('client\ndev tun\n')
|
||||
|
||||
mgr = _make_manager(tmp_dir=self.tmp)
|
||||
dst = os.path.join(self.tmp, 'services', 'openvpn-client', 'config', 'default.ovpn')
|
||||
self.assertTrue(os.path.isfile(dst), f'Expected migrated file at {dst}')
|
||||
|
||||
def test_existing_dst_not_overwritten(self):
|
||||
legacy_wg = os.path.join(self.tmp, 'connectivity', 'wireguard_ext')
|
||||
os.makedirs(legacy_wg)
|
||||
with open(os.path.join(legacy_wg, 'wg_ext0.conf'), 'w') as f:
|
||||
f.write('legacy\n')
|
||||
|
||||
# Pre-create the destination with different content
|
||||
dst_dir = os.path.join(self.tmp, 'services', 'wireguard-ext', 'config')
|
||||
os.makedirs(dst_dir, exist_ok=True)
|
||||
dst = os.path.join(dst_dir, 'wg_ext0.conf')
|
||||
with open(dst, 'w') as f:
|
||||
f.write('existing\n')
|
||||
|
||||
_make_manager(tmp_dir=self.tmp)
|
||||
with open(dst) as f:
|
||||
self.assertEqual(f.read(), 'existing\n')
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_status
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user