Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
236 lines
13 KiB
Python
236 lines
13 KiB
Python
"""Private, digest-bound Playwright registry/Deployment patch preparation.
|
|
|
|
No CLI or Kubernetes client: the existing protected owner execution path must
|
|
provide fresh objects, an independently approved proposal digest, admitted
|
|
custody, exclusive maintenance, and readiness/login verification. Proposal and
|
|
patch bodies contain private registry data; never log or serialize them to chat.
|
|
Only summary() is an output receipt. This library grants no execution authority.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
import copy
|
|
import hashlib
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
import subprocess
|
|
|
|
spec = importlib.util.spec_from_file_location('playwright_registration', Path(__file__).with_name('playwright-registration.py'))
|
|
registration = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(registration)
|
|
require = registration.require
|
|
Refused = registration.Refused
|
|
|
|
|
|
def digest(value):
|
|
return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(',', ':'),
|
|
allow_nan=False).encode()).hexdigest()
|
|
|
|
|
|
def target(obj, kind, name):
|
|
require(obj.get('kind') == kind and obj.get('metadata', {}).get('name') == name
|
|
and obj['metadata'].get('namespace') == 'sso', 'unexpected_target')
|
|
require(not obj['metadata'].get('deletionTimestamp') and
|
|
not obj['metadata'].get('ownerReferences'), 'target_not_independently_available')
|
|
return registration.registry.pin.safe_metadata(obj)
|
|
|
|
|
|
def container(specification):
|
|
items = specification['template']['spec']['containers']
|
|
matches = [item for item in items if item.get('name') == 'keycape']
|
|
require(len(matches) == 1, 'keycape_container_required')
|
|
return matches[0]
|
|
|
|
|
|
def stopped(specification):
|
|
result = copy.deepcopy(specification)
|
|
result['replicas'] = 0
|
|
return result
|
|
|
|
|
|
def build(secret, deployment, identities, source_revision):
|
|
secret_meta = target(secret, 'Secret', 'keycape-config')
|
|
deploy_meta = target(deployment, 'Deployment', 'keycape')
|
|
require(not secret.get('immutable'), 'mutable_registry_required')
|
|
require(re.fullmatch(r'[0-9a-f]{40}', source_revision or '') is not None, 'exact_source_revision_required')
|
|
original = deployment['spec']
|
|
require(original.get('replicas') == 1 and original.get('strategy', {}).get('type') == 'Recreate'
|
|
and not original.get('paused'), 'single_recreate_issuer_required')
|
|
current = container(original)
|
|
require(re.fullmatch(r'forgejo\.coulomb\.social/coulomb/key-cape@sha256:[0-9a-f]{64}',
|
|
current.get('image', '')) is not None, 'immutable_owner_image_required')
|
|
require(not current.get('envFrom'), 'explicit_env_bindings_required')
|
|
env = current.get('env', [])
|
|
require(isinstance(env, list) and all(isinstance(e, dict) and isinstance(e.get('name'), str) for e in env),
|
|
'env_shape_invalid')
|
|
names = [e['name'] for e in env]
|
|
require(len(names) == len(set(names)), 'duplicate_env_name')
|
|
require([e for e in env if e['name'] == 'KEYCAPE_CONFIG'] ==
|
|
[{'name': 'KEYCAPE_CONFIG', 'value': '/etc/keycape/config.yaml'}], 'config_path_drift')
|
|
volumes = original['template']['spec'].get('volumes', [])
|
|
mounts = current.get('volumeMounts', [])
|
|
expected = [v['name'] for v in volumes if v.get('secret', {}).get('secretName') == 'keycape-config']
|
|
require(len(expected) == 1 and any(m.get('name') == expected[0] and
|
|
m.get('mountPath') == '/etc/keycape' and m.get('readOnly') is True and
|
|
not m.get('subPath') and not m.get('subPathExpr') for m in mounts), 'registry_mount_drift')
|
|
encoded, created = registration.prepare_registry(secret, identities)
|
|
proposed_data = dict(secret['data'], **{'config.yaml': encoded})
|
|
proposed_spec = copy.deepcopy(original)
|
|
proposed_env = container(proposed_spec).setdefault('env', [])
|
|
added_env = []
|
|
records = registration.clients(identities)
|
|
for record in records[1:]:
|
|
binding = registration.registry.env_binding(record)
|
|
target_ref = binding['valueFrom']['secretKeyRef']
|
|
for entry in proposed_env:
|
|
reference = entry.get('valueFrom', {}).get('secretKeyRef', {})
|
|
require(entry['name'] == binding['name'] or
|
|
(reference.get('name'), reference.get('key')) != (target_ref['name'], target_ref['key']),
|
|
'delivery_secret_already_bound')
|
|
existing = [e for e in proposed_env if e['name'] == binding['name']]
|
|
require(not existing or existing == [binding], 'existing_binding_requires_reconciliation')
|
|
if not existing:
|
|
proposed_env.append(binding)
|
|
added_env.append(binding['name'])
|
|
return {'schema': 'netkingdom.playwright-activation.v1', 'source_revision': source_revision,
|
|
'secret_identity': secret_meta, 'deployment_identity': deploy_meta,
|
|
'before_data': copy.deepcopy(secret['data']), 'after_data': proposed_data,
|
|
'before_spec': copy.deepcopy(original), 'after_spec': proposed_spec,
|
|
'identity_inventory': copy.deepcopy(identities), 'created_client_ids': created,
|
|
'created_env_names': added_env}
|
|
|
|
|
|
def summary(proposal):
|
|
changed = proposal['before_data'] != proposal['after_data'] or proposal['before_spec'] != proposal['after_spec']
|
|
return {'schema': proposal['schema'], 'proposal_digest': digest(proposal),
|
|
'source_revision': proposal['source_revision'],
|
|
'created_client_ids': proposal['created_client_ids'],
|
|
'created_env_names': proposal['created_env_names'],
|
|
'change_needed': changed, 'maintenance_required': changed,
|
|
'live_activated': False, 'custody_admitted': False}
|
|
|
|
|
|
def check_registry(proposal, binary, expected_binary_sha256):
|
|
"""Run the exact admitted native verifier, no ambient env or raw diagnostics."""
|
|
path = Path(binary)
|
|
require(path.is_file() and not path.is_symlink() and
|
|
hashlib.sha256(path.read_bytes()).hexdigest() == expected_binary_sha256,
|
|
'native_verifier_digest_mismatch')
|
|
for stage in ('before', 'after'):
|
|
env_names = [e['name'] for e in container(proposal[stage + '_spec']).get('env', [])]
|
|
command = [str(path), 'check-config', '--config', '/dev/stdin', '--env-names',
|
|
','.join(env_names) or '__NO_ENV_NAMES__']
|
|
if 'KEYCAPE_POLICY_PATH' in env_names:
|
|
command.append('--policy-store')
|
|
try:
|
|
result = subprocess.run(command, input=base64.b64decode(proposal[stage + '_data']['config.yaml'], validate=True),
|
|
capture_output=True, timeout=60, env={'PATH': '/usr/bin:/bin'})
|
|
report = json.loads(result.stdout)
|
|
valid = result.returncode == 0 and report.get('valid') is True and not report.get('errors')
|
|
except Exception:
|
|
valid = False
|
|
require(valid, 'native_registry_check_failed')
|
|
return {'original_registry_valid': True, 'proposed_registry_valid': True,
|
|
'verifier_sha256': expected_binary_sha256}
|
|
|
|
|
|
def quiescent(deployment, pods):
|
|
"""Caller supplies an unfiltered complete sso PodList from the same cluster."""
|
|
status = deployment.get('status', {})
|
|
require(status.get('observedGeneration') == deployment['metadata'].get('generation')
|
|
and deployment['spec'].get('replicas') == 0 and
|
|
all(status.get(k, 0) == 0 for k in ['replicas', 'readyReplicas', 'availableReplicas', 'updatedReplicas']),
|
|
'issuer_not_stopped')
|
|
require(pods.get('kind') == 'PodList' and not pods.get('metadata', {}).get('continue')
|
|
and isinstance(pods.get('items'), list), 'complete_namespace_pod_list_required')
|
|
# Terminating pods are still consumers. Do not infer quiescence from readiness.
|
|
for pod in pods['items']:
|
|
require(pod.get('metadata', {}).get('namespace') == 'sso', 'pod_inventory_namespace_mismatch')
|
|
pod_spec = pod.get('spec', {})
|
|
uses_registry = any(v.get('secret', {}).get('secretName') == 'keycape-config' or
|
|
any(s.get('secret', {}).get('name') == 'keycape-config'
|
|
for s in v.get('projected', {}).get('sources', []))
|
|
for v in pod_spec.get('volumes', []))
|
|
for c in pod_spec.get('containers', []) + pod_spec.get('initContainers', []) + pod_spec.get('ephemeralContainers', []):
|
|
uses_registry = uses_registry or any(e.get('secretRef', {}).get('name') == 'keycape-config'
|
|
for e in c.get('envFrom', [])) or any(
|
|
e.get('valueFrom', {}).get('secretKeyRef', {}).get('name') == 'keycape-config'
|
|
for e in c.get('env', []))
|
|
require(not uses_registry, 'registry_consumer_still_present')
|
|
|
|
|
|
def propose_step(proposal, approved_digest, phase, secret, deployment, pods):
|
|
"""Return one private CAS patch or None for an exact no-op; never execute it.
|
|
|
|
The caller must freshly read BOTH objects before EACH phase, verify every
|
|
response, and stop on unknown outcomes. The digest binds content, not authority.
|
|
Kubernetes has no cross-object transaction; exclusive maintenance is required.
|
|
"""
|
|
require(digest(proposal) == approved_digest, 'proposal_revision_changed')
|
|
secret_meta = target(secret, 'Secret', 'keycape-config')
|
|
deploy_meta = target(deployment, 'Deployment', 'keycape')
|
|
require(secret_meta['uid'] == proposal['secret_identity']['uid'] and
|
|
deploy_meta['uid'] == proposal['deployment_identity']['uid'], 'target_identity_changed')
|
|
old_data, new_data = proposal['before_data'], proposal['after_data']
|
|
old_spec, new_spec = proposal['before_spec'], proposal['after_spec']
|
|
data, current = secret['data'], deployment['spec']
|
|
require(data == old_data or data == new_data, 'registry_drift_stop_without_stale_replay')
|
|
require(any(current == item for item in [old_spec, new_spec, stopped(old_spec), stopped(new_spec)]),
|
|
'deployment_drift_stop_without_stale_replay')
|
|
require(phase in ('stop', 'registry', 'bindings', 'resume', 'rollback-stop',
|
|
'rollback-registry', 'rollback-bindings', 'rollback-resume'), 'phase_invalid')
|
|
if phase.startswith('rollback-') and data == old_data and current == old_spec:
|
|
return None
|
|
if old_data == new_data and old_spec == new_spec:
|
|
require(data == old_data and current == old_spec, 'noop_state_changed')
|
|
return None
|
|
if phase == 'stop':
|
|
require(secret_meta == proposal['secret_identity'] and deploy_meta == proposal['deployment_identity']
|
|
and data == old_data and current == old_spec, 'initial_revision_changed')
|
|
status = deployment.get('status', {})
|
|
require(status.get('observedGeneration') == deployment['metadata'].get('generation') and
|
|
all(status.get(k) == 1 for k in ['replicas', 'readyReplicas', 'availableReplicas', 'updatedReplicas']),
|
|
'initial_issuer_not_ready')
|
|
obj, field, wanted = deployment, 'spec', stopped(old_spec)
|
|
elif phase == 'rollback-stop':
|
|
obj, field, wanted = deployment, 'spec', stopped(current)
|
|
else:
|
|
quiescent(deployment, pods)
|
|
if phase == 'registry':
|
|
require(current == stopped(old_spec) and data == old_data, 'registry_phase_out_of_order')
|
|
obj, field, wanted = secret, 'data', new_data
|
|
elif phase == 'bindings':
|
|
require(current == stopped(old_spec) and data == new_data, 'bindings_phase_out_of_order')
|
|
obj, field, wanted = deployment, 'spec', stopped(new_spec)
|
|
elif phase == 'resume':
|
|
require(current == stopped(new_spec) and data == new_data, 'resume_phase_out_of_order')
|
|
obj, field, wanted = deployment, 'spec', new_spec
|
|
elif phase == 'rollback-registry':
|
|
obj, field, wanted = secret, 'data', old_data
|
|
elif phase == 'rollback-bindings':
|
|
require(data == old_data, 'restore_registry_before_bindings')
|
|
obj, field, wanted = deployment, 'spec', stopped(old_spec)
|
|
else:
|
|
require(data == old_data and current == stopped(old_spec), 'rollback_resume_out_of_order')
|
|
obj, field, wanted = deployment, 'spec', old_spec
|
|
if obj[field] == wanted:
|
|
return None
|
|
metadata = registration.registry.pin.safe_metadata(obj)
|
|
patch = [{'op': 'test', 'path': '/metadata/uid', 'value': metadata['uid']},
|
|
{'op': 'test', 'path': '/metadata/resourceVersion', 'value': metadata['resource_version']},
|
|
{'op': 'test', 'path': '/' + field, 'value': copy.deepcopy(obj[field])},
|
|
{'op': 'replace', 'path': '/' + field, 'value': copy.deepcopy(wanted)}]
|
|
return {'kind': obj['kind'], 'namespace': 'sso', 'name': obj['metadata']['name'],
|
|
'uid': metadata['uid'], 'field': field, 'expected': copy.deepcopy(wanted),
|
|
'proposal_digest': approved_digest, 'phase': phase, 'patch': patch}
|
|
|
|
|
|
def verify_response(operation, obj):
|
|
"""Also use on a fresh GET after an acknowledged patch; failures stop the caller."""
|
|
metadata = target(obj, operation['kind'], operation['name'])
|
|
require(metadata['uid'] == operation['uid'] and obj.get(operation['field']) == operation['expected'],
|
|
'mutation_readback_mismatch')
|
|
return {'phase': operation['phase'], 'proposal_digest': operation['proposal_digest'],
|
|
'uid': metadata['uid'], 'resource_version': metadata['resource_version'], 'readback_matches': True}
|