fix(recovery): bind admitted host and record live provider scope approval
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
This commit is contained in:
parent
c31d21ba7d
commit
a17aa7edc1
15 changed files with 393 additions and 7 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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(),{})
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue