diff --git a/docs/bootstrap-recovery-ceremony.md b/docs/bootstrap-recovery-ceremony.md index 7397f07..3acd1e8 100644 --- a/docs/bootstrap-recovery-ceremony.md +++ b/docs/bootstrap-recovery-ceremony.md @@ -375,3 +375,37 @@ Find this functionality through the owning registry entry disaster-recovery, age) federated by reuse-surface, related to `capability.railiance.bootstrap-sequencing`. Maturity remains D3/A2/C1/R0; source fixtures and discoverability do not assert live production acceptance. + + +## Live admission update — 2026-10-16 + +The operator approved the exact packet in decision +`40bfae40-6108-4e51-8613-073339f5345f`. The first owner login succeeded but +Nextcloud returned publisher permission21 for requested5. Setup refused before +publishing any new KV value. Official Nextcloud link-share normalization adds +permission16 (re-sharing) to read-enabled public links when outgoing federation +is enabled. The operator then explicitly approved folder-only publisher21 +(read/create/re-share) and retention25 (read/delete/re-share), recorded in +`5edd5203-fae7-461b-8775-5d22b7873971`. The two CCRs and setup guards now require +that additional explicit approval. Neither credential gains update rights, +other-folder or account administration. New-share metadata and failed-attempt +receipts are retained in the October16 live evidence folder. This supersedes +the initial no-re-sharing provider assumption; it does not infer a native test +pass from approval. + +Node preflight also verified that `railiance01` is an SSH alias whose actual +hostname is `239.62.205.92.host.secureserver.net`. Runtime/installer now require +that exact hostname, the reviewed machine-ID hash and the original cluster UID, +plus root. No server hostname or machine identity was changed.52 focused +checks passed including the real synthetic encrypted/native PG fixture after +this guard repair. Unapproved-CCR negative tests use proposed fixtures even +when the actual owner requests have been approved. + +Both exact guarded DNS-selector patches passed server-dry-run and were applied. +The node has active k3s with network-policy rules present. The source-bound +runtime release was installed and unit validation completed; both timers remain +disabled. Provider admission/readback and actual full capture remain pending; +no periodic capture/expiry is enabled by installation alone. One subsequent +OIDC attempt timed out before owner-command handoff; it did not run mutations, +and its possible session revocation was unconfirmed. Only the contained owner +login lane is retried, without switching to root or inspecting captured bytes. diff --git a/docs/evidence/2026-10-16-identity-backup-live/installed-runtime-packet.json b/docs/evidence/2026-10-16-identity-backup-live/installed-runtime-packet.json new file mode 100644 index 0000000..a2f36ea --- /dev/null +++ b/docs/evidence/2026-10-16-identity-backup-live/installed-runtime-packet.json @@ -0,0 +1,59 @@ +{ + "schema": "netkingdom.identity-runtime-install.v1", + "status": "prepared_not_installed", + "decision_ref": "40bfae40-6108-4e51-8613-073339f5345f", + "release_id": "bf8b38f185037515cfad9eebcddefc9f60ace724c32e10d89ae8307dc95a4eb7", + "release_directory": "/opt/net-kingdom-backup/releases/bf8b38f185037515cfad9eebcddefc9f60ace724c32e10d89ae8307dc95a4eb7", + "runtime_files": [ + { + "path": "tools/bootstrap-recovery/capture-identity.py", + "sha256": "47dca7c4dc96fa7eb9fb02c81fca9ca8dc5281aac918979eb7367148168ba705" + }, + { + "path": "tools/bootstrap-recovery/identity-files.py", + "sha256": "d0063a9eedd3610e3d45bd2d834aae83cff74e4b3504d138b3548f427e89331d" + }, + { + "path": "tools/bootstrap-recovery/identity-bundle.py", + "sha256": "524a03c065db4307fcde83d33af39503f3de19cb6ef3a8ca72c991d4a8c08ee6" + }, + { + "path": "tools/bootstrap-recovery/identity-runtime.py", + "sha256": "97224760f5e21ac9ca1d0d96eeec7427afd7d6eea30a218629a6e409d74fe560" + }, + { + "path": "tools/bootstrap-recovery/identity-offsite.py", + "sha256": "e7c31fa9ee081961af873348ee3b4829a645d40c7dc7f1e22c564aa9ed01bc49" + }, + { + "path": "tools/bootstrap-recovery/identity-retention.py", + "sha256": "a41ea4759f69c35548149fc8d0d5ace547f16cf9fe4c9dac10b119a4d0becdec" + }, + { + "path": "tools/bootstrap-recovery/identity-backup-profile.json", + "sha256": "40f9e5b5bb957e5b23e03b31701702b850502d16a92a0fa0ebf92618cc3f18a1" + }, + { + "path": "tools/bootstrap-recovery/prepare-identity-coverage.py", + "sha256": "cd5f3067d28b4c7cc93ed08b8cf3869218bdecf7c54b5dffed22d697de71dbb1" + }, + { + "path": "tools/bootstrap-recovery/install-identity-backup.py", + "sha256": "5c612a2a51c9b59aad8cc01d418c2f563cbd92c62dc1fa8be994e5fb59df90f1" + }, + { + "path": "sso-mfa/k8s/privacyidea/key-custody-remote.py", + "sha256": "27b2b885c81923d85054e3999c5613e104fe627779b2b50398ed9bed5088f47e" + } + ], + "units": { + "net-kingdom-identity-backup.service": "[Unit]\nDescription=Encrypted full identity backup and verified Storage Share readback\nAfter=network-online.target k3s.service\nConditionPathExists=/var/lib/net-kingdom-backup/accepted-recovery.json\n[Service]\nType=oneshot\nUser=root\nGroup=root\nUMask=0077\nStateDirectory=net-kingdom-backup\nStateDirectoryMode=0700\nNoNewPrivileges=true\nPrivateTmp=true\nPrivateDevices=true\nProtectSystem=strict\nProtectHome=true\nReadWritePaths=/var/lib/net-kingdom-backup\nCapabilityBoundingSet=\nRestrictAddressFamilies=AF_UNIX AF_INET AF_INET6\nProtectKernelTunables=true\nProtectKernelModules=true\nProtectControlGroups=true\nMemoryMax=3G\nCPUQuota=200%\nTasksMax=128\nTimeoutStartSec=45min\nTimeoutStopSec=5min\nExecStartPre=/usr/bin/python3 -I /opt/net-kingdom-backup/releases/bf8b38f185037515cfad9eebcddefc9f60ace724c32e10d89ae8307dc95a4eb7/tools/bootstrap-recovery/identity-runtime.py retention --kubeconfig /etc/rancher/k3s/k3s.yaml --state /var/lib/net-kingdom-backup\nExecStart=/usr/bin/python3 -I /opt/net-kingdom-backup/releases/bf8b38f185037515cfad9eebcddefc9f60ace724c32e10d89ae8307dc95a4eb7/tools/bootstrap-recovery/identity-runtime.py scheduled --kubeconfig /etc/rancher/k3s/k3s.yaml --state /var/lib/net-kingdom-backup --decision-id 40bfae40-6108-4e51-8613-073339f5345f\nExecStartPost=/usr/bin/python3 -I /opt/net-kingdom-backup/releases/bf8b38f185037515cfad9eebcddefc9f60ace724c32e10d89ae8307dc95a4eb7/tools/bootstrap-recovery/identity-runtime.py retention --kubeconfig /etc/rancher/k3s/k3s.yaml --state /var/lib/net-kingdom-backup\n", + "net-kingdom-identity-backup.timer": "[Unit]\nDescription=Daily full identity recovery bundle\n[Timer]\nOnCalendar=*-*-* 03:45:00 UTC\nPersistent=true\nRandomizedDelaySec=5min\nUnit=net-kingdom-identity-backup.service\n[Install]\nWantedBy=timers.target\n", + "net-kingdom-identity-backup-health.service": "[Unit]\nDescription=Identity backup age check with durable failure reporting\nAfter=network-online.target k3s.service\n[Service]\nType=oneshot\nUser=root\nGroup=root\nUMask=0077\nStateDirectory=net-kingdom-backup\nStateDirectoryMode=0700\nNoNewPrivileges=true\nPrivateTmp=true\nPrivateDevices=true\nProtectSystem=strict\nProtectHome=true\nReadWritePaths=/var/lib/net-kingdom-backup\nCapabilityBoundingSet=\nRestrictAddressFamilies=AF_UNIX AF_INET AF_INET6\nProtectKernelTunables=true\nProtectKernelModules=true\nProtectControlGroups=true\nMemoryMax=3G\nCPUQuota=200%\nTasksMax=128\nTimeoutStartSec=45min\nTimeoutStopSec=5min\nExecStart=/usr/bin/python3 -I /opt/net-kingdom-backup/releases/bf8b38f185037515cfad9eebcddefc9f60ace724c32e10d89ae8307dc95a4eb7/tools/bootstrap-recovery/identity-runtime.py status --kubeconfig /etc/rancher/k3s/k3s.yaml --state /var/lib/net-kingdom-backup\n", + "net-kingdom-identity-backup-health.timer": "[Unit]\nDescription=Hourly identity backup freshness check\n[Timer]\nOnCalendar=hourly\nPersistent=true\nUnit=net-kingdom-identity-backup-health.service\n[Install]\nWantedBy=timers.target\n" + }, + "timers_automatically_enabled": false, + "first_capture_command": "/usr/bin/python3 -I /opt/net-kingdom-backup/releases/bf8b38f185037515cfad9eebcddefc9f60ace724c32e10d89ae8307dc95a4eb7/tools/bootstrap-recovery/identity-runtime.py first-capture --kubeconfig /etc/rancher/k3s/k3s.yaml --state /var/lib/net-kingdom-backup --decision-id 40bfae40-6108-4e51-8613-073339f5345f", + "activation_requires_independent_recovery": true, + "source_contains_credentials": false +} diff --git a/docs/evidence/2026-10-16-identity-backup-live/isolation-applied.json b/docs/evidence/2026-10-16-identity-backup-live/isolation-applied.json new file mode 100644 index 0000000..0a8d6c1 --- /dev/null +++ b/docs/evidence/2026-10-16-identity-backup-live/isolation-applied.json @@ -0,0 +1,27 @@ +{ + "status": "capture_dns_exclusions_verified", + "attempted": [ + { + "namespace": "sso", + "name": "allow-egress-dns", + "uid": "d2a1bd49-7035-437b-9b8e-e9e66b203365" + }, + { + "namespace": "mfa", + "name": "allow-egress-dns", + "uid": "1d23ed08-bb6f-4959-b72e-3f03af40f136" + } + ], + "verified": [ + { + "namespace": "sso", + "name": "allow-egress-dns", + "uid": "d2a1bd49-7035-437b-9b8e-e9e66b203365" + }, + { + "namespace": "mfa", + "name": "allow-egress-dns", + "uid": "1d23ed08-bb6f-4959-b72e-3f03af40f136" + } + ] +} diff --git a/docs/evidence/2026-10-16-identity-backup-live/isolation-live-plan.json b/docs/evidence/2026-10-16-identity-backup-live/isolation-live-plan.json new file mode 100644 index 0000000..d2feec8 --- /dev/null +++ b/docs/evidence/2026-10-16-identity-backup-live/isolation-live-plan.json @@ -0,0 +1,132 @@ +{ + "schema": "netkingdom.identity-isolation.v1", + "status": "prepared_not_applied", + "kubeconfig": "/home/worsch/.kube/config", + "source_sha256": "88d1e13112a44c79571825af89d3fe5b01fa490cff2653badef4c62a45c844f1", + "policies": [ + { + "namespace": "sso", + "name": "allow-egress-dns", + "uid": "d2a1bd49-7035-437b-9b8e-e9e66b203365", + "patch": [ + { + "op": "test", + "path": "/metadata/uid", + "value": "d2a1bd49-7035-437b-9b8e-e9e66b203365" + }, + { + "op": "test", + "path": "/metadata/resourceVersion", + "value": "3623833" + }, + { + "op": "test", + "path": "/spec", + "value": { + "egress": [ + { + "ports": [ + { + "port": 53, + "protocol": "UDP" + }, + { + "port": 53, + "protocol": "TCP" + } + ], + "to": [ + { + "namespaceSelector": { + "matchLabels": { + "kubernetes.io/metadata.name": "kube-system" + } + } + } + ] + } + ], + "podSelector": {}, + "policyTypes": [ + "Egress" + ] + } + }, + { + "op": "replace", + "path": "/spec/podSelector", + "value": { + "matchExpressions": [ + { + "key": "net-kingdom/capture", + "operator": "DoesNotExist" + } + ] + } + } + ] + }, + { + "namespace": "mfa", + "name": "allow-egress-dns", + "uid": "1d23ed08-bb6f-4959-b72e-3f03af40f136", + "patch": [ + { + "op": "test", + "path": "/metadata/uid", + "value": "1d23ed08-bb6f-4959-b72e-3f03af40f136" + }, + { + "op": "test", + "path": "/metadata/resourceVersion", + "value": "3623822" + }, + { + "op": "test", + "path": "/spec", + "value": { + "egress": [ + { + "ports": [ + { + "port": 53, + "protocol": "UDP" + }, + { + "port": 53, + "protocol": "TCP" + } + ], + "to": [ + { + "namespaceSelector": { + "matchLabels": { + "kubernetes.io/metadata.name": "kube-system" + } + } + } + ] + } + ], + "podSelector": {}, + "policyTypes": [ + "Egress" + ] + } + }, + { + "op": "replace", + "path": "/spec/podSelector", + "value": { + "matchExpressions": [ + { + "key": "net-kingdom/capture", + "operator": "DoesNotExist" + } + ] + } + } + ] + } + ] +} diff --git a/docs/evidence/2026-10-16-identity-backup-live/provider-metadata-review.json b/docs/evidence/2026-10-16-identity-backup-live/provider-metadata-review.json new file mode 100644 index 0000000..d34be48 --- /dev/null +++ b/docs/evidence/2026-10-16-identity-backup-live/provider-metadata-review.json @@ -0,0 +1,17 @@ +{ + "status": "metadata_reviewed", + "shares": [ + { + "id": "26", + "share_type": 3, + "permissions": 21, + "path": "/railiance-backups/net-kingdom-identity", + "label": "net-kingdom-identity-publisher", + "item_type": "folder", + "public_upload": null, + "hide_download": 0, + "token_present": true, + "token_shape_valid": true + } + ] +} diff --git a/docs/evidence/2026-10-16-identity-backup-live/publisher-attempt-r1.json b/docs/evidence/2026-10-16-identity-backup-live/publisher-attempt-r1.json new file mode 100644 index 0000000..95be34a --- /dev/null +++ b/docs/evidence/2026-10-16-identity-backup-live/publisher-attempt-r1.json @@ -0,0 +1,15 @@ +{ + "status": "refused_provider_permission_scope", + "action": "publisher", + "caller_entity_id": "4a1f663c-0621-0483-edd0-f852274f4b6b", + "caller_auth_path": "auth/netkingdom/oidc/callback", + "quota_before": { + "quota_bytes": 10737418240, + "used_bytes": 6978016729, + "available_bytes": 3759401511 + }, + "folder_created": true, + "share_created": true, + "new_credential_published": false, + "native_delivery_started": false +} diff --git a/docs/evidence/2026-10-16-identity-backup-live/rollout-review.json b/docs/evidence/2026-10-16-identity-backup-live/rollout-review.json new file mode 100644 index 0000000..783f4f6 --- /dev/null +++ b/docs/evidence/2026-10-16-identity-backup-live/rollout-review.json @@ -0,0 +1,42 @@ +{ + "status": "approved_native_provider_delivery_checks_in_progress", + "observed_at": "2026-10-15T23:56:00.293983+00:00", + "approval_decision": "40bfae40-6108-4e51-8613-073339f5345f", + "approved_platform_source_commit": "69a792c", + "first_owner_session": "child_failed_session_revoked", + "second_owner_session": "metadata_review_succeeded_session_revoked", + "new_identity_bundle_created": false, + "identity_job_installed": true, + "new_credentials_published_to_openbao": false, + "existing_backup_changes": false, + "observed_permissions": 21, + "requested_permissions": 5, + "provider_added_permission": 16, + "retention_25_inferred_not_created": true, + "provider_reference": "https://github.com/nextcloud/server/blob/stable31/apps/files_sharing/lib/Controller/ShareAPIController.php#L1011-L1015", + "provider_source_sha256": "f361dcf7138302d72a95148933b2ed7bd69de844dff116a1d2a23c24c0dda53d", + "scope_change_owner_decision_pending": false, + "runtime_host_preflight": { + "ssh_alias": "railiance01", + "hostname": "239.62.205.92.host.secureserver.net", + "machine_id_sha256": "fe7367a60951922122af7ba18ed2cde65c7e0a232bbedfb045cb575de2094c2f", + "sudo_root_verified": true, + "cluster_uid_match": true, + "k3s_active": true, + "free_kib": 126777924 + }, + "source_repairs": [ + "Bind actual runtime host hostname and machine identity rather than SSH alias", + "Keep unapproved-CCR negative tests proposed independently of actual owner approval status" + ], + "identity_timers_enabled": false, + "scope_change_owner_decision": "5edd5203-fae7-461b-8775-5d22b7873971", + "approved_provider_masks": { + "publisher": 21, + "retention": 25 + }, + "focused_tests_passed": 52, + "temporary_dns_selector_exclusions_applied": true, + "network_policy_rule_lines_present": 649, + "installed_runtime_release": "bf8b38f185037515cfad9eebcddefc9f60ace724c32e10d89ae8307dc95a4eb7" +} diff --git a/tests/test_identity_bundle.py b/tests/test_identity_bundle.py index 3b5e94f..f59f1ab 100644 --- a/tests/test_identity_bundle.py +++ b/tests/test_identity_bundle.py @@ -208,3 +208,18 @@ def test_full_new_archive_decrypt_extract_native_database_restore_and_cleanup(st assert r['container_cleanup_verified'] and r['temporary_plaintext_cleanup_verified'] assert not r['full_service_authentication_proven'] finally:subprocess.run(['docker','rm','--force','--volumes',fixture],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,timeout=30) + + +def test_runtime_host_requires_owner_and_exact_machine_binding(monkeypatch): + import hashlib,socket + module=load('identity-bundle') + current=module.profile();current['runtime_host']={'hostname':'verified-node','machine_id_sha256':hashlib.sha256(b'fixture-machine').hexdigest()} + monkeypatch.setattr(module,'profile',lambda:current) + monkeypatch.setattr(module.os,'geteuid',lambda:0);monkeypatch.setattr(socket,'gethostname',lambda:'verified-node') + original=Path.read_bytes + monkeypatch.setattr(Path,'read_bytes',lambda p:b'fixture-machine' if str(p)=='/etc/machine-id' else original(p)) + module.require_runtime_host() + monkeypatch.setattr(socket,'gethostname',lambda:'foreign-node') + with pytest.raises(module.Refused):module.require_runtime_host() + monkeypatch.setattr(socket,'gethostname',lambda:'verified-node');monkeypatch.setattr(module.os,'geteuid',lambda:1000) + with pytest.raises(module.Refused):module.require_runtime_host() diff --git a/tests/test_identity_offsite.py b/tests/test_identity_offsite.py index 448bcd7..4bf89e3 100644 --- a/tests/test_identity_offsite.py +++ b/tests/test_identity_offsite.py @@ -113,6 +113,12 @@ def test_missing_independently_recovered_point_refuses_all_expiration(): @pytest.mark.parametrize('action,number',[('publisher',48),('retention',49)]) def test_proposed_ccr_cannot_create_provider_share_or_reader(monkeypatch,action,number): path=setup.PLATFORM/f'credential-change-requests/CCR-2026-{number:04d}-net-kingdom-backup-{action}.yaml' + from types import SimpleNamespace + import yaml + proposed=yaml.safe_load(path.read_text());proposed['status']='proposed';proposed['review']['comments']=[] + # The negative fixture must stay proposed after the real owner approves a CCR. + original_load=setup.load + monkeypatch.setattr(setup,'load',lambda name,p:SimpleNamespace(validate_ccr=lambda _: (proposed,[],[])) if name=='credential_change' else original_load(name,p)) monkeypatch.setattr(setup.inventory,'caller_credentials',lambda:pytest.fail('unapproved request reached credential read')) with pytest.raises(setup.Refused):setup.run(action,hashlib.sha256(path.read_bytes()).hexdigest(),{}) diff --git a/tools/bootstrap-recovery/identity-backup-profile.json b/tools/bootstrap-recovery/identity-backup-profile.json index c93afa2..8f380f8 100644 --- a/tools/bootstrap-recovery/identity-backup-profile.json +++ b/tools/bootstrap-recovery/identity-backup-profile.json @@ -136,8 +136,8 @@ "initial": "Storage Share Backup/railiance-backups/net-kingdom-identity/", "future_secondary": "s3://railiance-platform-pg-backup/platform-pg/application-archives/net-kingdom/; not implemented/admitted in this rollout", "admitted": false, - "publisher": "CCR-2026-0048: new folder-only read/create public DAV share, delivered by exact ESO reader; no operator password in runtime", - "retention": "CCR-2026-0049: separate folder-only read/delete share and process; newest plus seven daily/two weekly and independently recovered pinned generation; unknown/unindexed points never deleted", + "publisher": "CCR-2026-0048: new folder-only read/create public DAV share, delivered by exact ESO reader; no operator password in runtime; explicitly owner-approved provider-added re-sharing (permissions21)", + "retention": "CCR-2026-0049: separate folder-only read/delete share and process; newest plus seven daily/two weekly and independently recovered pinned generation; unknown/unindexed points never deleted; explicitly owner-approved provider-added re-sharing (permissions25)", "schedule": "source prepared: daily 03:45 UTC plus up to five minutes jitter, hourly 25-hour freshness check; installation leaves timers disabled until independent archive recovery", "quota": "512 MiB ceiling, two GiB fresh provider headroom; initial 10 GiB account quota checked; unsupported public-share quota fails closed" }, @@ -149,5 +149,10 @@ "Attended independent age-key proof: wrong-key denial, complete archive hashes, SQLite/RSA checks, disposable native PostgreSQL restore and verified cleanup before timer activation", "Full NK49 closure separately requires native directory/issuer/MFA authentication, retained token decryption, audit signature continuity, current callers and legacy credential dispositions; archive acceptance does not close those gates", "Host-local health cannot report a dead host; external outage monitoring and second-provider copy remain separate coverage gaps" - ] + ], + "runtime_host": { + "ssh_alias": "railiance01", + "hostname": "239.62.205.92.host.secureserver.net", + "machine_id_sha256": "fe7367a60951922122af7ba18ed2cde65c7e0a232bbedfb045cb575de2094c2f" + } } diff --git a/tools/bootstrap-recovery/identity-bundle.py b/tools/bootstrap-recovery/identity-bundle.py index b09edc5..3a7ea9e 100644 --- a/tools/bootstrap-recovery/identity-bundle.py +++ b/tools/bootstrap-recovery/identity-bundle.py @@ -202,3 +202,10 @@ def restore(archive_path,root): save(root,name,raw,captured) if seen!=set(expected):raise Refused() return manifest,checks(root,captured) + + +def require_runtime_host(): + import socket + host=profile()['runtime_host'] + if (os.geteuid()!=0 or socket.gethostname()!=host['hostname'] + or hashlib.sha256(Path('/etc/machine-id').read_bytes()).hexdigest()!=host['machine_id_sha256']):raise Refused() diff --git a/tools/bootstrap-recovery/identity-credential-setup.py b/tools/bootstrap-recovery/identity-credential-setup.py index 4753a7e..a63eec0 100644 --- a/tools/bootstrap-recovery/identity-credential-setup.py +++ b/tools/bootstrap-recovery/identity-credential-setup.py @@ -33,7 +33,7 @@ def load(name,path): inventory=load('storage_inventory',HERE/'inventory-storage-share.py') offsite=load('offsite',HERE/'identity-offsite.py') FOLDER='/railiance-backups/net-kingdom-identity' -PROFILES={'publisher':(48,5),'retention':(49,9)} +PROFILES={'publisher':(48,21),'retention':(49,25)} class Refused(Exception):pass @@ -57,7 +57,8 @@ def contract(action,expected): or a['policy_name']!=name or a['auth']['role']!=name or a['auth']['bound_claims']!={'service_account_names':[name],'service_account_namespaces':['sso']} or provider['folder']!=FOLDER or provider['share_permissions']!=permission or provider['share_type']!=3 - or provider['label']!='net-kingdom-identity-'+action):raise Refused() + or provider['label']!='net-kingdom-identity-'+action + or provider.get('provider_added_resharing_approved') is not True):raise Refused() if (a.get('metadata_read') is not False or a.get('token_self_lifecycle') is not True or a['auth'].get('audience')!='openbao' or a['auth'].get('token_no_default_policy') is not True or any(a['auth'].get(k)!='15m' for k in ['ttl','token_max_ttl','token_explicit_max_ttl']) @@ -124,35 +125,44 @@ def boundary(client,auth,token,action,receipt): receipt['provider_test_artifacts']=[name] receipt['boundary_fixture_cleanup_verified']=False try: + receipt['boundary_phase']='create_owner_fixture' status,_=request(client,owner,auth,'PUT',payload.stdout,{'If-None-Match':'*'}) if status!=201:raise Refused() created=True + receipt['boundary_phase']='read_public_fixture' status,raw=request(client,public,(token,'')) if status!=200 or raw!=payload.stdout:raise Refused() + receipt['boundary_phase']='deny_public_overwrite' status,_=request(client,public,(token,''),'PUT',payload.stdout) if status not in (401,403):raise Refused() denied_name='nk49-absent-'+uuid.uuid4().hex+'.age' if action=='retention':receipt['provider_test_artifacts'].append(denied_name) status,_=request(client,offsite.BASE+denied_name,(token,''),'PUT',payload.stdout,{'If-None-Match':'*'}) if action=='retention' else (403,b'') if status not in (401,403):raise Refused() + receipt['boundary_phase']='public_delete' status,_=request(client,public,(token,''),'DELETE') if action=='publisher': if status not in (401,403):raise Refused() new=offsite.BASE+'nk49-boundary-'+uuid.uuid4().hex+'.age' receipt['provider_test_artifacts'].append(new.rsplit('/',1)[-1]) + receipt['boundary_phase']='create_public_fixture' code,_=request(client,new,(token,''),'PUT',payload.stdout,{'If-None-Match':'*'}) if code!=201:raise Refused() # This fixture belongs to the current ceremony, not retained data. + receipt['boundary_phase']='cleanup_public_fixture' remove_fixture(client,offsite.HOST+'/remote.php/dav/files/Backup'+FOLDER+'/'+new.rsplit('/',1)[-1],auth,payload.stdout) else: if status!=204:raise Refused() deleted=True + receipt['boundary_phase']='deny_parent_path' status,_=request(client,offsite.BASE+'%2e%2e/r01-net-kingdom-pg/',(token,'')) + receipt['parent_probe_status']=status if status not in (401,403,404):raise Refused() receipt['provider_boundary_verified']=True finally: if created and not deleted: remove_fixture(client,owner,auth,payload.stdout) + receipt['owner_fixture_cleanup_verified']=True # A rejected operation may have had an unexpected side effect. Only a # successful complete boundary test claims cleanup; names remain in the # receipt for owner reconciliation on every failure. diff --git a/tools/bootstrap-recovery/identity-runtime.py b/tools/bootstrap-recovery/identity-runtime.py index 2a2bfb3..07895c0 100644 --- a/tools/bootstrap-recovery/identity-runtime.py +++ b/tools/bootstrap-recovery/identity-runtime.py @@ -171,7 +171,7 @@ def main(): state=Path(a.state).absolute() ready=False try: - if os.geteuid()!=0 or socket.gethostname()!='railiance01':raise offsite.Refused() + capture.bundle.require_runtime_host() capture.KUBE=['kubectl','--kubeconfig',a.kubeconfig] uid=capture.get(None,'namespace','kube-system')['metadata']['uid'] if uid!=capture.prepare.CLUSTER_UID:raise offsite.Refused() diff --git a/tools/bootstrap-recovery/install-identity-backup.py b/tools/bootstrap-recovery/install-identity-backup.py index 8cabf8b..1935996 100644 --- a/tools/bootstrap-recovery/install-identity-backup.py +++ b/tools/bootstrap-recovery/install-identity-backup.py @@ -68,7 +68,7 @@ def module(name): def owner(): - if os.geteuid()!=0 or socket.gethostname()!='railiance01':raise Refused() + module('identity-bundle').require_runtime_host() capture=module('capture-identity');capture.KUBE=['kubectl','--kubeconfig',KUBECONFIG] if capture.get(None,'namespace','kube-system')['metadata']['uid']!=capture.prepare.CLUSTER_UID:raise Refused() diff --git a/workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md b/workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md index 2df3acf..7e9aff1 100644 --- a/workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md +++ b/workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md @@ -530,6 +530,23 @@ indexes. No maturity promotion implies live acceptance. Reviewable rollout and hidden-key steps: `docs/bootstrap-recovery-ceremony.md`; evidence in `docs/evidence/2026-10-16-identity-backup-implementation/`. T02 progress/T03 wait. +2026-10-16 approved live admission in progress: user approved the exact packet +(decision40bfae40) and then explicitly approved Nextcloud-added re-sharing for +publisher21/retention25 (decision5edd5203). The initial5/9 request failed closed +before KV publication when provider returned21. Scoped share26 exists under +Backup's exact new folder; it remains undelivered until native checks pass. +Owner sessions with executed children were self-revoked; a later callback timeout +ran no child and did not confirm possible-session revocation. No root fallback. + +Fixed runtime guard to use railiance01's actual verified hostname, machine-ID +hash and cluster UID, with root required.52 focused checks including actual +synthetic encrypted archive/native PG restore passed. Both guarded DNS helper +exclusions applied with UID/RV/spec tests. Installed the source-bound runtime +with unit validation; timers remain disabled pending actual off-site recovery. +Provider boundary testing and ESO/native login delivery remain in progress. +No identity capture or scheduled retention has occurred. T02 progress/T03 wait. +Evidence: `docs/evidence/2026-10-16-identity-backup-live/`. + ## Verify owner-approved recovery and current native callers ```task