Recognize verified preserved relay ownership during later updates
This commit is contained in:
@@ -523,3 +523,23 @@ Source review found byte-download progress unconditionally changes Updating to
|
|||||||
Installing; failure cleanup only releases Updating. The narrow progress-state
|
Installing; failure cleanup only releases Updating. The narrow progress-state
|
||||||
fix and regression are pending. This is not full target rollback acceptance.
|
fix and regression are pending. This is not full target rollback acceptance.
|
||||||
The recovered guest is QMP-paused without reboot for serialized validation.
|
The recovered guest is QMP-paused without reboot for serialized validation.
|
||||||
|
|
||||||
|
The progress-state fix at 105454bd passed the full isolated suite: **2,026
|
||||||
|
tests**, zero failures, five existing ignores, all 532 inputs unchanged. Its
|
||||||
|
matching executable retained all seven IDs across manager startup. Actual RPC
|
||||||
|
`2d4c8fc9-ee90-4167-a2d3-90647e756df0` safely restored before target startup
|
||||||
|
and **returned package state to Running with progress cleared**, accepting the
|
||||||
|
state fix on the actual manager path.
|
||||||
|
|
||||||
|
That transaction exposed a preserved-relay ownership edge: native pre-target
|
||||||
|
recovery publishes the latest operation as installed-recipe owner even when the
|
||||||
|
relay container/image is preserved. The helper incorrectly required that owner
|
||||||
|
to be the historical image-producing operation. The correction separately
|
||||||
|
validates a unique terminal schema-2 preservation owner against exact running
|
||||||
|
intent, live container ID, raw configuration hash, image and unit body, then
|
||||||
|
retains the existing unique image ancestry to the qualified original. It adds
|
||||||
|
no image edge or binary-only fallback. **59 controller tests pass**, and a
|
||||||
|
separate candidate verifier passed against the actual preserved guest relay and
|
||||||
|
record chain without replacing the installed helper or modifying journals.
|
||||||
|
Matching embedded-helper build and full target rollback/cutover remain required;
|
||||||
|
the 2,026-test receipt predates this helper-only correction.
|
||||||
|
|||||||
@@ -86,7 +86,25 @@ elif [ "$1" = signal ]; then
|
|||||||
else exit 1; fi
|
else exit 1; fi
|
||||||
'''
|
'''
|
||||||
LEGACY_RELAY_IMAGE = '061516573b143b44e331f960036a6a3dc43c9b256ef8ca71afedbeb2cf797a4b'
|
LEGACY_RELAY_IMAGE = '061516573b143b44e331f960036a6a3dc43c9b256ef8ca71afedbeb2cf797a4b'
|
||||||
def verify_relay_image_lineage(image, unit_sha256, records, installed=None):
|
def verify_relay_image_lineage(image, unit_sha256, records, installed=None, current=None):
|
||||||
|
preserved_owner=False
|
||||||
|
if image.removeprefix('sha256:')!=LEGACY_RELAY_IMAGE:
|
||||||
|
require(isinstance(installed,dict) and installed.get('schema')==1 and installed.get('name')=='indeedhub-relay','Relay installed recipe missing')
|
||||||
|
owners=[r for r in records if r.get('id')==installed.get('operation')]
|
||||||
|
require(len(owners)==1,'Relay installed owner is missing or ambiguous')
|
||||||
|
owner=owners[0]
|
||||||
|
try:require(str(uuid.UUID(owner['id']))==owner['id'],'Invalid relay installed operation')
|
||||||
|
except (KeyError,ValueError,AttributeError):raise RuntimeError('Invalid relay installed operation')
|
||||||
|
require(owner.get('schema') in (1,2) and owner.get('package')=='indeedhub' and owner.get('phase')=='Restored' and owner.get('cleanup_done') is True,'Relay installed owner is not a completed recovery')
|
||||||
|
members=[m for m in owner.get('members',[]) if m.get('original',{}).get('name')=='indeedhub-relay']
|
||||||
|
require(len(members)==1,'Relay installed owner has ambiguous members')
|
||||||
|
member=members[0];original=member['original']
|
||||||
|
if member.get('preserve_original') is True:
|
||||||
|
require(owner['schema']==2 and owner.get('target_startup_began') is False and original.get('running') is True,'Relay preservation ownership changed')
|
||||||
|
require(isinstance(current,dict) and current.get('name')=='indeedhub-relay' and original.get('container_id')==current.get('container_id') and original.get('config_sha256')==current.get('config_sha256'),'Relay preserved live identity changed')
|
||||||
|
require(re.fullmatch('[0-9a-f]{64}',original.get('container_id','')) is not None and re.fullmatch('[0-9a-f]{64}',original.get('config_sha256','')) is not None,'Invalid preserved relay identity')
|
||||||
|
require(original.get('image','').removeprefix('sha256:')==image.removeprefix('sha256:') and installed.get('body')==original.get('body') and isinstance(original.get('body'),str) and hashlib.sha256(original['body'].encode()).hexdigest()==unit_sha256,'Relay preserved image or recipe changed')
|
||||||
|
preserved_owner=True
|
||||||
seen=set()
|
seen=set()
|
||||||
for _ in range(16):
|
for _ in range(16):
|
||||||
image=image.removeprefix('sha256:')
|
image=image.removeprefix('sha256:')
|
||||||
@@ -107,7 +125,7 @@ def verify_relay_image_lineage(image, unit_sha256, records, installed=None):
|
|||||||
require((record['schema']==1 and (member.get('preserve_original') is None or member.get('preserve_original') is False) or record['schema']==2 and (record.get('target_startup_began') is True and member.get('preserve_original') is None or record.get('target_startup_began') is False and member.get('preserve_original') is False)) and recovery.get('operation_id')==record['id'] and recovery.get('source_container_id')==original.get('container_id'),'Relay recovery ownership changed')
|
require((record['schema']==1 and (member.get('preserve_original') is None or member.get('preserve_original') is False) or record['schema']==2 and (record.get('target_startup_began') is True and member.get('preserve_original') is None or record.get('target_startup_began') is False and member.get('preserve_original') is False)) and recovery.get('operation_id')==record['id'] and recovery.get('source_container_id')==original.get('container_id'),'Relay recovery ownership changed')
|
||||||
require(hashlib.sha256(member['pinned_original_body'].encode()).hexdigest()==unit_sha256,'Relay recovery unit lineage changed')
|
require(hashlib.sha256(member['pinned_original_body'].encode()).hexdigest()==unit_sha256,'Relay recovery unit lineage changed')
|
||||||
require(re.fullmatch('[0-9a-f]{64}',original.get('container_id','')) is not None,'Invalid relay source identity')
|
require(re.fullmatch('[0-9a-f]{64}',original.get('container_id','')) is not None,'Invalid relay source identity')
|
||||||
if len(seen)==1:require(isinstance(installed,dict) and installed.get('schema')==1 and installed.get('name')=='indeedhub-relay' and installed.get('operation')==record['id'] and installed.get('body')==member['pinned_original_body'],'Relay installed recipe does not own recovery lineage')
|
if len(seen)==1:require(preserved_owner and installed['operation']!=record['id'] or installed.get('operation')==record['id'] and installed.get('body')==member['pinned_original_body'],'Relay installed recipe does not own recovery lineage')
|
||||||
matches.append((original['image'],hashlib.sha256(original['body'].encode()).hexdigest()))
|
matches.append((original['image'],hashlib.sha256(original['body'].encode()).hexdigest()))
|
||||||
require(len(matches)==1,'Relay image lacks unique completed owned recovery lineage')
|
require(len(matches)==1,'Relay image lacks unique completed owned recovery lineage')
|
||||||
image,unit_sha256=matches[0]
|
image,unit_sha256=matches[0]
|
||||||
@@ -356,7 +374,7 @@ class Controller:
|
|||||||
directory=self.data/'update-transactions'/'installed-units';path=directory/'indeedhub-relay.json'
|
directory=self.data/'update-transactions'/'installed-units';path=directory/'indeedhub-relay.json'
|
||||||
require(directory.is_dir() and not directory.is_symlink() and directory.stat().st_uid==os.getuid() and directory.stat().st_mode & 0o077==0 and path.is_file() and not path.is_symlink() and path.stat().st_uid==os.getuid() and path.stat().st_mode & 0o077==0 and path.stat().st_size<=1024*1024,'Unsafe relay installed recipe')
|
require(directory.is_dir() and not directory.is_symlink() and directory.stat().st_uid==os.getuid() and directory.stat().st_mode & 0o077==0 and path.is_file() and not path.is_symlink() and path.stat().st_uid==os.getuid() and path.stat().st_mode & 0o077==0 and path.stat().st_size<=1024*1024,'Unsafe relay installed recipe')
|
||||||
installed=json.loads(path.read_text())
|
installed=json.loads(path.read_text())
|
||||||
verify_relay_image_lineage(member['image_id'],member['unit_sha256'],records,installed)
|
verify_relay_image_lineage(member['image_id'],member['unit_sha256'],records,installed,member)
|
||||||
image=images[0];expected=(LEGACY_API_CMD,['docker-entrypoint.sh']) if role=='api' else (LEGACY_RELAY_CMD,None)
|
image=images[0];expected=(LEGACY_API_CMD,['docker-entrypoint.sh']) if role=='api' else (LEGACY_RELAY_CMD,None)
|
||||||
require((image['Config'].get('Cmd'),image['Config'].get('Entrypoint'))==expected,'Unrecognized legacy signal command')
|
require((image['Config'].get('Cmd'),image['Config'].get('Entrypoint'))==expected,'Unrecognized legacy signal command')
|
||||||
source=pathlib.Path(self.run(['systemctl','--user','show',member['name']+'.service','--property=SourcePath','--value']).decode().strip())
|
source=pathlib.Path(self.run(['systemctl','--user','show',member['name']+'.service','--property=SourcePath','--value']).decode().strip())
|
||||||
|
|||||||
@@ -562,6 +562,31 @@ console.log('process identity cases passed');'''
|
|||||||
if change=='wrong-body':bad['members'][0]['pinned_original_body']='changed'
|
if change=='wrong-body':bad['members'][0]['pinned_original_body']='changed'
|
||||||
if change=='cycle':bad['members'][0]['original']['image']=image
|
if change=='cycle':bad['members'][0]['original']['image']=image
|
||||||
with self.assertRaises(RuntimeError):module.verify_relay_image_lineage(image,digest,[bad],installed)
|
with self.assertRaises(RuntimeError):module.verify_relay_image_lineage(image,digest,[bad],installed)
|
||||||
|
def test_preserved_relay_owner_attests_identity_without_replacing_image_ancestry(self):
|
||||||
|
import copy,hashlib
|
||||||
|
image='d'*64;body='[Container]\nImage='+image+'\n';digest=hashlib.sha256(body.encode()).hexdigest()
|
||||||
|
producer={'schema':2,'id':self.operation,'package':'indeedhub','phase':'Restored','cleanup_done':True,'target_startup_began':False,'members':[{'original':{'name':'indeedhub-relay','container_id':'e'*64,'image':module.LEGACY_RELAY_IMAGE,'body':'base'},'pinned_original_body':body,'preserve_original':False,'recovery_image':{'image':image,'operation_id':self.operation,'source_container_id':'e'*64}}]}
|
||||||
|
current={'name':'indeedhub-relay','container_id':'c'*64,'config_sha256':'a'*64}
|
||||||
|
owner={'schema':2,'id':str(uuid.uuid4()),'package':'indeedhub','phase':'Restored','cleanup_done':True,'target_startup_began':False,'members':[{'original':dict(current,image=image,body=body,running=True),'preserve_original':True,'recovery_image':{'image':'f'*64}}]}
|
||||||
|
installed={'schema':1,'name':'indeedhub-relay','operation':owner['id'],'body':body}
|
||||||
|
module.verify_relay_image_lineage(image,digest,[producer,owner],installed,current)
|
||||||
|
newer=copy.deepcopy(owner);newer['id']=str(uuid.uuid4());newer['members'][0]['recovery_image']['image']='b'*64
|
||||||
|
module.verify_relay_image_lineage(image,digest,[producer,owner,newer],dict(installed,operation=newer['id']),current)
|
||||||
|
for records in ([owner],[producer],[producer,owner,owner]):
|
||||||
|
with self.assertRaises(RuntimeError):module.verify_relay_image_lineage(image,digest,records,installed,current)
|
||||||
|
for key,value in [('container_id','b'*64),('config_sha256','b'*64),('name','indeedhub-api')]:
|
||||||
|
with self.assertRaises(RuntimeError):module.verify_relay_image_lineage(image,digest,[producer,owner],installed,dict(current,**{key:value}))
|
||||||
|
for field,value in [('schema',1),('target_startup_began',True),('target_startup_began',0),('cleanup_done',False),('phase','Restoring')]:
|
||||||
|
bad=copy.deepcopy(owner);bad[field]=value
|
||||||
|
with self.assertRaises(RuntimeError):module.verify_relay_image_lineage(image,digest,[producer,bad],installed,current)
|
||||||
|
for field,value in [('preserve_original',False),('preserve_original',1),('preserve_original',None)]:
|
||||||
|
bad=copy.deepcopy(owner);bad['members'][0][field]=value
|
||||||
|
with self.assertRaises(RuntimeError):module.verify_relay_image_lineage(image,digest,[producer,bad],installed,current)
|
||||||
|
for field,value in [('image','b'*64),('body','changed'),('running',False),('running',1),('container_id','bad'),('config_sha256','bad')]:
|
||||||
|
bad=copy.deepcopy(owner);bad['members'][0]['original'][field]=value
|
||||||
|
with self.assertRaises(RuntimeError):module.verify_relay_image_lineage(image,digest,[producer,bad],installed,current)
|
||||||
|
with self.assertRaises(RuntimeError):module.verify_relay_image_lineage(image,digest,[producer,owner],dict(installed,body='changed'),current)
|
||||||
|
with self.assertRaises(RuntimeError):module.verify_relay_image_lineage('b'*64,digest,[producer,owner],installed,current)
|
||||||
def test_column_commitments_retain_logical_order_and_every_semantic_field(self):
|
def test_column_commitments_retain_logical_order_and_every_semantic_field(self):
|
||||||
import copy
|
import copy
|
||||||
columns=[['first','integer',True,'','',''],['last','text',False,'','','']]
|
columns=[['first','integer',True,'','',''],['last','text',False,'','','']]
|
||||||
|
|||||||
Reference in New Issue
Block a user