mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-02 19:02:31 +00:00
fix(oci): validate managed routes during transaction recovery
This commit is contained in:
@@ -1352,6 +1352,22 @@ def complete_recovery(root, journal, state):
|
|||||||
msg_info2(translate('Shared host files are kept as they are; the backup does not restore their content.'))
|
msg_info2(translate('Shared host files are kept as they are; the backup does not restore their content.'))
|
||||||
|
|
||||||
|
|
||||||
|
def check_recovery_runtime(config, state):
|
||||||
|
"""Validate owned route assets before excluding their include from GPU/sysctl checks."""
|
||||||
|
error = None
|
||||||
|
for key in ('record', 'candidate_contract'):
|
||||||
|
saved = state.get(key) or {}
|
||||||
|
try:
|
||||||
|
public_routes.check(config, saved.get('deployment', {}), state['vmid'],
|
||||||
|
state['record']['installation_id'])
|
||||||
|
break
|
||||||
|
except public_routes.RouteError as failure:
|
||||||
|
error = failure
|
||||||
|
else:
|
||||||
|
raise error
|
||||||
|
gpu_devices.check_recovery_entries(without_public_routes(config, state['vmid']), state)
|
||||||
|
|
||||||
|
|
||||||
def recover(root, journal):
|
def recover(root, journal):
|
||||||
state = json.loads(journal.read_text())
|
state = json.loads(journal.read_text())
|
||||||
if state.get('kind') == 'native-modify':
|
if state.get('kind') == 'native-modify':
|
||||||
@@ -1418,7 +1434,7 @@ def recover(root, journal):
|
|||||||
if current_path.exists():
|
if current_path.exists():
|
||||||
stop(vmid)
|
stop(vmid)
|
||||||
current = owned(vmid, record['installation_id'])
|
current = owned(vmid, record['installation_id'])
|
||||||
gpu_devices.check_recovery_entries(current, state)
|
check_recovery_runtime(current, state)
|
||||||
if unused_volumes(current).keys() - state.get('retained_unused', {}).keys():
|
if unused_volumes(current).keys() - state.get('retained_unused', {}).keys():
|
||||||
raise ValueError(translate('There are extra disks or bind mounts outside the journal; the rootfs is not replaced'))
|
raise ValueError(translate('There are extra disks or bind mounts outside the journal; the rootfs is not replaced'))
|
||||||
move_retained_unused(state, to_stage=True)
|
move_retained_unused(state, to_stage=True)
|
||||||
|
|||||||
@@ -59,6 +59,41 @@ class ManagedRouteLifecycleTests(unittest.TestCase):
|
|||||||
with self.assertRaisesRegex(ValueError,'Invalid stack contract'):
|
with self.assertRaisesRegex(ValueError,'Invalid stack contract'):
|
||||||
plans.validate_public_route_members(4900,bad)
|
plans.validate_public_route_members(4900,bad)
|
||||||
|
|
||||||
|
def test_recovery_accepts_validated_owned_routes_and_keeps_unknown_include_guard(self):
|
||||||
|
with tempfile.TemporaryDirectory() as folder,self.assets(folder):
|
||||||
|
plan=deployment();routes.install(plan,4901,WORKER)
|
||||||
|
config=f'lxc.include: {routes.include_path(4901)}\n'.encode()
|
||||||
|
state={'vmid':4901,'record':{'installation_id':WORKER,'deployment':plan},
|
||||||
|
'candidate_contract':{'installation_id':WORKER,'deployment':copy.deepcopy(plan)}}
|
||||||
|
tx.check_recovery_runtime(config,state)
|
||||||
|
with self.assertRaisesRegex(ValueError,'not part of the journal'):
|
||||||
|
tx.check_recovery_runtime(config+b'lxc.include: /unknown/profile.conf\n',state)
|
||||||
|
|
||||||
|
def test_recovery_refuses_tampered_owned_route_assets(self):
|
||||||
|
with tempfile.TemporaryDirectory() as folder,self.assets(folder):
|
||||||
|
plan=deployment();routes.install(plan,4901,WORKER)
|
||||||
|
config=f'lxc.include: {routes.include_path(4901)}\n'.encode()
|
||||||
|
state={'vmid':4901,'record':{'installation_id':WORKER,'deployment':plan},
|
||||||
|
'candidate_contract':{'installation_id':WORKER,'deployment':copy.deepcopy(plan)}}
|
||||||
|
routes.include_path(4901).write_text('lxc.hook.start-host: /untrusted\n')
|
||||||
|
with self.assertRaisesRegex(routes.RouteError,'was changed'):
|
||||||
|
tx.check_recovery_runtime(config,state)
|
||||||
|
|
||||||
|
def test_recovery_refuses_an_owned_include_not_declared_by_either_contract(self):
|
||||||
|
with tempfile.TemporaryDirectory() as folder,self.assets(folder):
|
||||||
|
routes.install(deployment(),4901,WORKER)
|
||||||
|
state={'vmid':4901,'record':{'installation_id':WORKER,'deployment':{}},
|
||||||
|
'candidate_contract':{'installation_id':WORKER,'deployment':{}}}
|
||||||
|
with self.assertRaisesRegex(routes.RouteError,'differs from the contract'):
|
||||||
|
tx.check_recovery_runtime(f'lxc.include: {routes.include_path(4901)}\n'.encode(),state)
|
||||||
|
|
||||||
|
def test_recovery_can_validate_the_candidate_route_contract(self):
|
||||||
|
with tempfile.TemporaryDirectory() as folder,self.assets(folder):
|
||||||
|
plan=deployment();routes.install(plan,4901,WORKER)
|
||||||
|
state={'vmid':4901,'record':{'installation_id':WORKER,'deployment':{}},
|
||||||
|
'candidate_contract':{'installation_id':WORKER,'deployment':plan}}
|
||||||
|
tx.check_recovery_runtime(f'lxc.include: {routes.include_path(4901)}\n'.encode(),state)
|
||||||
|
|
||||||
def test_plan_rejects_changed_target_and_stack_identities(self):
|
def test_plan_rejects_changed_target_and_stack_identities(self):
|
||||||
for field in ('target_installation_id','stack_id'):
|
for field in ('target_installation_id','stack_id'):
|
||||||
bad=records();spec=bad[4901]['deployment']['stack_public_routes']
|
bad=records();spec=bad[4901]['deployment']['stack_public_routes']
|
||||||
|
|||||||
Reference in New Issue
Block a user