85 lines
4.1 KiB
Python
85 lines
4.1 KiB
Python
#!/usr/bin/python3 -I -S
|
|
"""Re-admit only appointed runtime producers; no RPC or caller argv/paths."""
|
|
import os
|
|
from pathlib import Path
|
|
import stat
|
|
import sys
|
|
import time
|
|
import types
|
|
|
|
if __name__=='__main__':
|
|
for name in ('c01_network','c01_endpoints','runtime_uplink','c01_uplink','c01_network_mdns','c01_retained_producer','c01_network_plan','c01_network_owner','c01_kiosk_endpoints'):
|
|
source=Path(__file__).resolve().with_name(name+'.py')
|
|
for item in (source,*source.parents):
|
|
m=item.lstat()
|
|
if m.st_uid or m.st_mode&0o022 or stat.S_ISLNK(m.st_mode):raise ValueError('unprotected runtime endpoint code')
|
|
module=types.ModuleType(name);module.__file__=str(source);sys.modules[name]=module
|
|
exec(compile(source.read_bytes(),str(source),'exec'),module.__dict__)
|
|
import c01_network_owner as network
|
|
import c01_retained_producer
|
|
from c01_kiosk_endpoints import request,PRODUCERS
|
|
|
|
|
|
def discover(policy,route):
|
|
end=time.monotonic()+2
|
|
group=Path('/sys/fs/cgroup'+route['target_cgroup'])
|
|
projection=policy.manifests[route['projection_sha256']]
|
|
selector=projection.get('producer',{'kind':'exact'})
|
|
try:
|
|
if selector['kind']=='retained-container-v1':groups=[group,group/'runtime',Path('/sys/fs/cgroup'+c01_retained_producer.payload_group(selector,route['target_cgroup']))]
|
|
elif selector['kind']=='exact':groups=[group]
|
|
else:groups=[p for p in group.iterdir() if p.name.startswith('libpod-payload-') and p.is_dir()]
|
|
except FileNotFoundError:return None
|
|
if len(groups)>16:raise ValueError('runtime producer cgroup bound')
|
|
candidates=[]
|
|
for current in groups:
|
|
try:pids=(current/'cgroup.procs').read_text().split()
|
|
except FileNotFoundError:continue
|
|
if len(pids)>256:raise ValueError('runtime producer process bound')
|
|
for text in pids:
|
|
if time.monotonic()>=end:raise TimeoutError('runtime endpoint discovery budget')
|
|
pid=int(text)
|
|
try:entries=list(Path(f'/proc/{pid}/fd').iterdir())
|
|
except FileNotFoundError:continue
|
|
if len(entries)>1024:raise ValueError('runtime descriptor bound')
|
|
for entry in entries:
|
|
if time.monotonic()>=end:raise TimeoutError('runtime endpoint discovery budget')
|
|
try:
|
|
if not os.readlink(entry).startswith('socket:['):continue
|
|
target=network.Target(policy,route,pid,int(entry.name))
|
|
except (OSError,ValueError):continue
|
|
try:candidates.append((pid,int(entry.name),os.fstat(target.listener.fileno()).st_ino,target.identity['start']))
|
|
finally:target.close()
|
|
# A prefork server's master/workers may share one kernel listening socket.
|
|
# Pin the oldest qualified holder, normally its master, not an arbitrary
|
|
# worker. A different socket remains ambiguous and is never substituted.
|
|
identities={inode for _,_,inode,_ in candidates}
|
|
if len(identities)>1:raise ValueError('ambiguous runtime producer')
|
|
return min(candidates,key=lambda item:(item[3],item[0],item[1]))[:2] if candidates else None
|
|
|
|
|
|
def tick(policy):
|
|
policy.check()
|
|
for name,route in policy.routes.items():
|
|
if route['target']!='runtime' or name in PRODUCERS:continue
|
|
status=request(policy,{'method':'inspect','name':name})
|
|
if status.get('name')!=name:raise ValueError('runtime status binding')
|
|
if status.get('producer') is not None:continue
|
|
candidate=discover(policy,route)
|
|
if candidate is None:continue
|
|
response=request(policy,{'method':'register','name':name,'pid':candidate[0],'fd':candidate[1],'previous':status['generation']})
|
|
if set(response)!={'registered','policy_sha256','generation'} or response['registered']!=name:raise ValueError('runtime registration reply')
|
|
|
|
|
|
def main():
|
|
if os.getresuid()!=(0,0,0) or os.getresgid()!=(0,0,0) or len(sys.argv)!=1:raise ValueError('fixed root runtime endpoint owner')
|
|
policy=network.Policy()
|
|
try:
|
|
while True:
|
|
tick(policy)
|
|
time.sleep(.5)
|
|
finally:policy.close()
|
|
|
|
|
|
if __name__=='__main__':main()
|