234 lines
14 KiB
Python
234 lines
14 KiB
Python
"""Offline data-only store coordinator. Fixed native capabilities, no clock grant.
|
|
|
|
Run as an independently appointed root process in a delegated supervisor cgroup.
|
|
It never installs policy, generates keys, admits the application, deletes partial
|
|
stores or retries uncertainty. Inspection recovers existing authenticated storage
|
|
with retained anchors. Legacy import is a separate explicit root appointment,
|
|
requires live cold exclusion, and cannot substitute for encrypted-store recovery.
|
|
"""
|
|
import hashlib
|
|
import itertools
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import socket
|
|
import sys
|
|
|
|
import c01_access_generation as generation
|
|
import c01_children as children
|
|
import c01_supervisor as supervisor
|
|
import c01_views as views
|
|
|
|
|
|
def validate_anchor(anchor, binding, initial):
|
|
"""Validate the independently authenticated native receipt, never reset it."""
|
|
require = children.require
|
|
require(type(anchor) is dict and set(anchor) == {'incarnation', 'revision', 'watermark', 'digest'},
|
|
'authenticated anchor fields')
|
|
for name in ('incarnation', 'digest'):
|
|
value = anchor[name]
|
|
require(type(value) is list and len(value) == 32 and
|
|
all(type(n) is int and 0 <= n <= 255 for n in value) and any(value),
|
|
'authenticated anchor encoding')
|
|
require(anchor['incarnation'] == binding['incarnation'], 'authenticated anchor incarnation differs')
|
|
require(type(anchor['revision']) is int and 1 <= anchor['revision'] < 2**64 and
|
|
type(anchor['watermark']) is int and 0 <= anchor['watermark'] < 2**64,
|
|
'authenticated anchor counters')
|
|
if initial:
|
|
require(anchor['revision'] == 1 and anchor['watermark'] == 0,
|
|
'initial authenticated anchor differs')
|
|
|
|
|
|
def run(inspect=False, epoch_mode=None, wait_for_power=False, legacy_approval=None):
|
|
require = children.require
|
|
require(os.getresuid() == (0, 0, 0) and os.getresgid() == (0, 0, 0), 'root provisioning coordinator required')
|
|
require(epoch_mode in (None, 'provision', 'recover'), 'fixed epoch operation required')
|
|
require(epoch_mode is None or inspect, 'epoch enablement cannot initialize an empty snapshot')
|
|
require(not wait_for_power or epoch_mode == 'recover', 'data-only epoch lifetime mode')
|
|
require(legacy_approval is None or (not inspect and epoch_mode is None and not wait_for_power),
|
|
'legacy import cannot inspect/reinitialize existing state or grant time')
|
|
value, _ = supervisor.root_json(supervisor.CONFIG)
|
|
generation.validate_launch(value) # structural bindings, NOT clock qualification
|
|
app = supervisor.principal(value['application_user'])
|
|
authority = supervisor.principal(value['authority_user'])
|
|
require(len({app.pw_uid, authority.pw_uid, value['runtime_uid'], 0}) == 4, 'provisioning principals overlap')
|
|
binary = os.open('/usr/local/bin/archipelago', os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC)
|
|
descriptors = [binary]
|
|
owner = None
|
|
migration = None
|
|
lock = supervisor.LaunchLock()
|
|
try:
|
|
meta = os.fstat(binary)
|
|
require(meta.st_uid == 0 and not meta.st_mode & 0o022, 'provisioning executable protection')
|
|
with os.fdopen(os.dup(binary), 'rb') as stream:
|
|
require(hashlib.file_digest(stream, 'sha256').hexdigest() == value['native_sha256'], 'provisioning native digest')
|
|
if not inspect and legacy_approval is None:
|
|
# Empty protected directories do not prove an existing manager has
|
|
# no retained history. This initializer supports only a new identity
|
|
# root; existing manager state needs a separately admitted migration.
|
|
manager = views.require_private_backing(value['manager_backing'])
|
|
descriptors.append(manager)
|
|
readable = os.open('.', os.O_RDONLY | os.O_DIRECTORY | os.O_CLOEXEC, dir_fd=manager)
|
|
descriptors.append(readable)
|
|
require(views.identity(manager)[2:] == (value['runtime_uid'], value['runtime_gid'], 0o700),
|
|
'new manager backing ownership')
|
|
with os.scandir(readable) as entries:
|
|
require({entry.name for entry in itertools.islice(entries, 2)} == {'identity'},
|
|
'existing manager state requires separately admitted migration')
|
|
supervisor.private_identity(readable, value['runtime_uid'])
|
|
identity = os.open('identity', os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC, dir_fd=readable)
|
|
descriptors.append(identity)
|
|
with os.scandir(identity) as entries:
|
|
require({entry.name for entry in itertools.islice(entries, 2)} == {'nostr_secret'},
|
|
'existing identity history requires separately admitted migration')
|
|
keys = {}
|
|
sources = {}
|
|
for role in ('snapshot', 'authority'):
|
|
keys[role] = supervisor.root_capability(value[role + '_key'], 32)
|
|
backing = views.require_private_backing(value[role + '_backing'])
|
|
descriptors.extend((keys[role], backing))
|
|
require(views.identity(backing)[2:] == (value['runtime_uid'], value['runtime_gid'], 0o700), 'original store ownership')
|
|
sources[role] = views.PinnedSource.capture(backing, value[role + '_backing'])
|
|
if not inspect:
|
|
require(not os.listdir(value[role + '_backing']), 'new-store provisioning refuses existing state')
|
|
require(supervisor.key_binding(keys['snapshot']) != supervisor.key_binding(keys['authority']), 'provisioning keys alias')
|
|
epoch = None
|
|
if epoch_mode is not None:
|
|
epoch = supervisor.load_epoch_configuration(value)
|
|
keys['epoch'] = supervisor.root_capability(epoch['key'], 32)
|
|
backing = views.require_private_backing(epoch['backing'])
|
|
descriptors.extend((keys['epoch'], backing))
|
|
require(views.identity(backing)[2:] == (value['runtime_uid'], value['runtime_gid'], 0o700), 'epoch backing ownership')
|
|
sources['epoch'] = views.PinnedSource.capture(backing, epoch['backing'])
|
|
require(len({tuple(supervisor.key_binding(fd).values()) for fd in keys.values()}) == 3, 'epoch key alias')
|
|
owner = supervisor.Supervisor(lock)
|
|
if legacy_approval is not None:
|
|
import c01_migration
|
|
migration = c01_migration.LegacyImport(legacy_approval, value)
|
|
migration.check(owner)
|
|
if wait_for_power:
|
|
supervisor.acquire_admission_leases(owner,value,app,authority)
|
|
owner.power_lease = supervisor.c01_power.Lease(value)
|
|
parent = os.getpid()
|
|
address = '\0c01-provision-' + os.urandom(16).hex()
|
|
deadline = supervisor.now() + 30_000_000_000
|
|
|
|
def child(control, role, account):
|
|
mounts = views.PrivateMounts()
|
|
mounts.cover('/run', 0o755)
|
|
target = Path('/run/c01-store')
|
|
target.mkdir(mode=0o700)
|
|
mounts.idmapped_directory(sources[role], target, value['runtime_uid'], account.pw_uid,
|
|
value['runtime_gid'], account.pw_gid)
|
|
keep = {binary, control.fileno()}
|
|
if epoch is not None and role == 'authority':
|
|
Path('/run/c01-epoch').mkdir(mode=0o700)
|
|
mounts.idmapped_directory(sources['epoch'], Path('/run/c01-epoch'), value['runtime_uid'], account.pw_uid,
|
|
value['runtime_gid'], account.pw_gid)
|
|
keep.add(keys['epoch'])
|
|
if epoch is None or role == 'authority': keep.add(keys[role])
|
|
if migration is not None and role == 'snapshot':keep.add(migration.source_fd)
|
|
supervisor.close_except(keep)
|
|
children.drop_credentials(account.pw_uid, account.pw_gid, parent)
|
|
channel = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET)
|
|
if role == 'authority':
|
|
channel.bind(address)
|
|
channel.listen(1)
|
|
supervisor.packet(control, b'LISTENING')
|
|
connected, _ = channel.accept()
|
|
channel.close()
|
|
channel = connected
|
|
else:
|
|
channel.connect(address)
|
|
supervisor.packet(control, b'CONNECTED')
|
|
if epoch is not None and role == 'snapshot':
|
|
alias = channel.dup()
|
|
if wait_for_power:
|
|
require(control.recv(32) == b'VERIFY_REVOKED', 'epoch lifetime peer control')
|
|
else:
|
|
supervisor.child_wait(control, b'VERIFY_REVOKED', deadline)
|
|
channel.settimeout(2); alias.settimeout(2)
|
|
require(channel.recv(1) == b'' and alias.recv(1) == b'', 'epoch socket aliases not revoked')
|
|
supervisor.packet(control, b'REVOKED')
|
|
supervisor.child_wait(control, b'RELEASE', deadline)
|
|
return
|
|
supervisor.child_wait(control, b'EXEC', deadline)
|
|
mapping = {3: control.fileno(), 4: channel.fileno(), 9: keys[role]}
|
|
if migration is not None and role == 'snapshot':mapping[14] = migration.source_fd
|
|
if epoch is not None: mapping[13] = keys['epoch']
|
|
supervisor.native(binary, 'c01-provision-store-v1',
|
|
mapping, {})
|
|
|
|
def guarded(control, role, account):
|
|
try:
|
|
child(control, role, account)
|
|
except Exception as error:
|
|
# This setup path reads no key bytes; preserve the concrete
|
|
# mount/identity failure rather than only a generic EOF barrier.
|
|
print('C01_STORE_SETUP_FAILURE:', str(error), file=sys.stderr, flush=True)
|
|
raise
|
|
service = owner.children.spawn('authority', lambda c: guarded(c, 'authority', authority), descriptors)
|
|
owner.wait(service, b'LISTENING', authority.pw_uid, authority.pw_gid, deadline)
|
|
client = owner.children.spawn('application', lambda c: guarded(c, 'snapshot', app),
|
|
descriptors + ([migration.source_fd] if migration is not None else []))
|
|
for record, account in ((service, authority), (client, app)):
|
|
owner.wait(record, b'CONNECTED', account.pw_uid, account.pw_gid, deadline)
|
|
def grant(record, role, operation, peer, account):
|
|
supervisor.packet(record.channel, b'EXEC')
|
|
request = dict(version=1, process_id=record.pid,
|
|
operation=operation, store_dir='/run/c01-store', binding=value['binding'],
|
|
key_file=supervisor.key_binding(keys[role]),
|
|
peer=dict(pid=peer.pid, uid=account.pw_uid, gid=account.pw_gid))
|
|
if epoch is not None:
|
|
request['epoch'] = dict(store_dir='/run/c01-epoch', key_file=supervisor.key_binding(keys['epoch']), delegation=epoch['delegation'])
|
|
if migration is not None and role == 'snapshot':
|
|
request.update(version=2,legacy=migration.native_appointment())
|
|
supervisor.packet(record.channel, request)
|
|
if epoch is not None:
|
|
grant(service, 'authority', 'epoch_lifetime' if wait_for_power else 'provision_epoch' if epoch_mode == 'provision' else 'recover_epoch', client, app)
|
|
context = json.loads(owner.receive(service, authority.pw_uid, authority.pw_gid, deadline))
|
|
require(context['binding'] == value['binding'] and context['delegation_id'] == epoch['delegation']['id'] and
|
|
bytes(context['boot_id']).hex() == supervisor.recovery.boot_id(), 'native epoch context binding')
|
|
if wait_for_power:
|
|
owner.authority_record=service
|
|
owner.authority_credentials=(service.pid,authority.pw_uid,authority.pw_gid)
|
|
owner.power_command=b'REVOKE'
|
|
print('C01_NON_ADMITTING_EPOCH_LIFETIME_HELD',flush=True)
|
|
while owner.tick():
|
|
supervisor.time.sleep(.02)
|
|
return {'epoch_context':context,'clock_authorized':False}
|
|
supervisor.packet(service.channel, b'REVOKE')
|
|
owner.wait(service, b'EPOCH_REVOKED', authority.pw_uid, authority.pw_gid, deadline)
|
|
supervisor.packet(client.channel, b'VERIFY_REVOKED')
|
|
owner.wait(client, b'REVOKED', app.pw_uid, app.pw_gid, deadline)
|
|
owner.completed_one_shots.update(('application','authority'))
|
|
supervisor.packet(service.channel, b'RELEASE')
|
|
supervisor.packet(client.channel, b'RELEASE')
|
|
return {'epoch_context':context, 'revoked_aliases':True, 'clock_authorized':False}
|
|
grant(service, 'authority', 'recover_authority' if inspect else 'provision_authority', client, app)
|
|
owner.wait(service, b'STORE_READY', authority.pw_uid, authority.pw_gid, deadline)
|
|
grant(client, 'snapshot', 'import_legacy_snapshot' if migration is not None else
|
|
'inspect_snapshot' if inspect else 'provision_snapshot', service, authority)
|
|
anchor = json.loads(owner.receive(client, app.pw_uid, app.pw_gid, deadline))
|
|
validate_anchor(anchor, value['binding'], initial=not inspect and migration is None)
|
|
if migration is not None:
|
|
require(anchor['revision'] == 1 and anchor['watermark'] == migration.value['approved_watermark'],
|
|
'imported authenticated anchor differs from approved floor')
|
|
owner.completed_one_shots.add('application')
|
|
supervisor.packet(client.channel, b'RELEASE')
|
|
if migration is not None:migration.check(owner)
|
|
result = {'operation': 'import-legacy' if migration is not None else 'inspect' if inspect else 'provision',
|
|
'anchor': anchor, 'clock_authorized': False, 'native_sha256': value['native_sha256']}
|
|
if migration is not None:
|
|
result.update(approval_sha256=migration.digest, source_sha256=migration.value['source']['sha256'],
|
|
source_preserved=True, source_has_no_protected_anchor=True)
|
|
return result
|
|
finally:
|
|
if owner is not None:
|
|
owner.retire()
|
|
else:
|
|
lock.close()
|
|
if migration is not None:migration.close()
|
|
for fd in descriptors:
|
|
os.close(fd)
|