Inventory off-host recovery assets through existing bounded backup routes
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
This commit is contained in:
parent
d9557ba90e
commit
c262470089
12 changed files with 1069 additions and 6 deletions
|
|
@ -41,7 +41,11 @@ owner custody and pass the decryption key through attended temporary unlock.
|
|||
| Current consumer source | Reconcile the deployed revisions with the repaired caller/provider source. The October 14 reserved platform-group fix must be deployed and independently checked before claiming live enforcement. |
|
||||
| Legacy credential disposition | IAM owner identifies the exact historical personal-token/trigger principals, actual consumers and revocation method before any deletion. Deleting a Secret does not revoke an issued provider credential. |
|
||||
|
||||
The current independent key availability and exact artifact/manifest are pending.
|
||||
The operator subsequently reported holding the `railiance-backup` age key.
|
||||
Key material has not been received or read. Exact artifact/manifest selection
|
||||
and verification that this independently held key matches the selected
|
||||
ciphertext remain pending. The backup lane recipient and bootstrap
|
||||
`keys/age.pub` must not be assumed to be the same.
|
||||
Do not draft a blanket restore or grant approval while these remain unknown.
|
||||
Once fixed, bind the source revisions, target identities, allowed operations,
|
||||
recovery/cleanup sequence and maintenance window to the reviewed live packet.
|
||||
|
|
@ -116,3 +120,25 @@ Emergent Security 0.2.6, package digest
|
|||
start/root cards withheld, orientation only. Current SECURITY-COMPANION,
|
||||
Platform backup/custody procedures, owner runtime contracts and the workplan
|
||||
were reviewed as sources. No execution authority is derived from the cards.
|
||||
|
||||
|
||||
## Off-host inventory return — 2026-10-15
|
||||
|
||||
Both bounded metadata inventories completed. Storage Share's inspected
|
||||
`Backup/railiance-backups` scope includes the current privacyIDEA database
|
||||
ciphertext and the October14 OpenBao snapshot. It did not reveal a matching
|
||||
bootstrap/identity-file bundle in the inspected folders. Scaleway's inspected
|
||||
`railiance-platform-pg-backup/platform-pg/` tree contains database backup trees
|
||||
and an application archive subtree, with no identity-file bundle in this bounded
|
||||
scope. Other accounts/buckets/prefixes are unassessed. See
|
||||
[the discovery summary](evidence/2026-10-15-bootstrap-recovery/offsite-discovery-summary.json).
|
||||
No object contents or age keys were read; the operator envelope exited0 and
|
||||
self-revoked. Existing scoped backup credentials were reused without changes;
|
||||
the Scaleway bootstrap API key was not used.
|
||||
|
||||
The next human act is to run the existing custody helper in their own terminal
|
||||
with `--expected-recipient` set to the public recipient in the discovery summary,
|
||||
`--prompt --check-only`. Paste private material only into the hidden prompt.
|
||||
This verifies the independently held key against the backup owner's declared
|
||||
recipient and removes its temporary copy. It does not by itself prove that the
|
||||
selected backup decrypts or includes the full matching identity recovery set.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"schema": "netkingdom.bootstrap-caller-inventory.v1",
|
||||
"observed_at": "2026-10-15T20:54:09.856047+00:00",
|
||||
"scope": "all current .sh/.py non-test files under sso-mfa/bootstrap and sso-mfa/k8s, Makefile, console, delivery copier and recovery transport tool",
|
||||
"entry_count": 87,
|
||||
"scope": "all current .sh/.py non-test files under sso-mfa/bootstrap and sso-mfa/k8s, Makefile, console, delivery copier and recovery transport tool plus two bounded provider metadata inventory helpers",
|
||||
"entry_count": 89,
|
||||
"entries": [
|
||||
{
|
||||
"path": "Makefile",
|
||||
|
|
@ -424,6 +424,16 @@
|
|||
"sha256": "7efe14c0d71f2c846434d19f8d05702d69d3c40a617a700b08fe13b3764d0650",
|
||||
"classification": "diagnostic; structural observations do not prove recovery or revoke credentials"
|
||||
},
|
||||
{
|
||||
"path": "tools/bootstrap-recovery/inventory-scaleway.py",
|
||||
"sha256": "a219df205708fa4ed44b763a418585d63c820c25434bc0fd14e805eb5c2f79d3",
|
||||
"classification": "read-only bounded owner backup metadata; existing caller/custody only; no age key or backup content read"
|
||||
},
|
||||
{
|
||||
"path": "tools/bootstrap-recovery/inventory-storage-share.py",
|
||||
"sha256": "fe56454e5ca740395e6d6a2a0f8185abdac1d8ca6d0eedb31273db409dee4954",
|
||||
"classification": "read-only bounded owner backup metadata; existing caller/custody only; no age key or backup content read"
|
||||
},
|
||||
{
|
||||
"path": "tools/bootstrap-recovery/verify-bundle.py",
|
||||
"sha256": "885b05a2011e711228e42e316bda70bbe0d97d481383d61aa8469d0f10452370",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"observed_at": "2026-10-15T21:58:20.278564+00:00",
|
||||
"operator_reports_holding_railiance_backup_age_key": true,
|
||||
"private_key_received_or_read": false,
|
||||
"candidate_key_match_verified": false,
|
||||
"storage_share": {
|
||||
"inventory": "storage-share-inventory-r1.json",
|
||||
"status": "metadata_inventory_complete",
|
||||
"attended_envelope_exit_code": 0,
|
||||
"session_self_revocation_and_cleanup_confirmed": true,
|
||||
"selected_candidate": "r01-net-kingdom-pg/net-kingdom-pg-privacyidea_db-20261015T023000Z.dump.age",
|
||||
"candidate_bytes": 89129309,
|
||||
"candidate_contents_downloaded": false,
|
||||
"bootstrap_identity_file_bundle_found_in_inspected_scope": false
|
||||
},
|
||||
"scaleway": {
|
||||
"inventory": "scaleway-inventory-r1.json",
|
||||
"status": "metadata_inventory_complete",
|
||||
"bucket": "railiance-platform-pg-backup",
|
||||
"scope": "platform-pg/ and at most three child levels; WAL/base object trees and other buckets/prefixes not enumerated",
|
||||
"bootstrap_identity_file_bundle_found_in_inspected_scope": false,
|
||||
"bootstrap_api_key_used": false
|
||||
},
|
||||
"key_check": {
|
||||
"source": "railiance-platform/lib/railiance-backup-common.sh",
|
||||
"source_sha256": "09f349ae815339732db505d6896fb1ca384be7c66b40574750a3ba30d4d922e0",
|
||||
"declared_default_public_recipient": "age1zvryunvjhvpkmasskauga2heeg0ztnte9ymgppvjge36ekumk50syr3tsz",
|
||||
"operator_hidden_terminal_check_requested": true,
|
||||
"scope": "A successful key match checks the declared public recipient, not actual ciphertext decryption or whole-stack recovery."
|
||||
},
|
||||
"source_tests": {
|
||||
"passed": 13,
|
||||
"command": "python3 -m pytest -q tests/test_scaleway_backup_inventory.py tests/test_storage_share_inventory.py"
|
||||
},
|
||||
"native_recovery_proven": false,
|
||||
"production_mutations_performed": false
|
||||
}
|
||||
|
|
@ -18,9 +18,16 @@
|
|||
"verification": "source-verification.json",
|
||||
"ceremony": "../../bootstrap-recovery-ceremony.md",
|
||||
"outstanding": [
|
||||
"Exact off-host artifact/version/digest and complete matching data/key/config coverage",
|
||||
"Matching off-host identity data/key/config set is not located in inspected provider scopes; a recent PI database ciphertext is identified, not downloaded/decrypted",
|
||||
"Independent age key availability/custody confirmation",
|
||||
"Owner admitted isolated recovery target/procedure and native service continuity/cleanup evidence",
|
||||
"T02 current caller/provider source admission and exact legacy provider credential disposition; retain existing deferred-rotation boundary"
|
||||
]
|
||||
],
|
||||
"operator_reports_holding_railiance_backup_age_key": true,
|
||||
"operator_report_recorded_at": "2026-10-15T21:52:22.549430+00:00",
|
||||
"key_material_received": false,
|
||||
"key_matches_selected_ciphertext_verified": false,
|
||||
"offsite_discovery": "offsite-discovery-summary.json",
|
||||
"privacyidea_database_ciphertext_identified": true,
|
||||
"required_full_asset_coverage_confirmed": false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
"schema": "netkingdom.scaleway-backup-inventory.v1",
|
||||
"status": "metadata_inventory_complete",
|
||||
"started_at": "2026-10-15T21:54:28.925951+00:00",
|
||||
"read_only": true,
|
||||
"provider": "Scaleway",
|
||||
"bucket": "railiance-platform-pg-backup",
|
||||
"prefix": "platform-pg/",
|
||||
"source_sha256": "a219df205708fa4ed44b763a418585d63c820c25434bc0fd14e805eb5c2f79d3",
|
||||
"bootstrap_api_key_used": false,
|
||||
"age_keys_read": false,
|
||||
"object_contents_downloaded": false,
|
||||
"secret_values_exported": false,
|
||||
"scope": "Bounded prefix metadata, at most three child levels; WAL/base trees not enumerated; other buckets/prefixes unassessed",
|
||||
"prefixes": {
|
||||
"platform-pg/": {
|
||||
"directories": [
|
||||
"platform-pg/application-archives/",
|
||||
"platform-pg/apps-pg/",
|
||||
"platform-pg/forgejo-db/",
|
||||
"platform-pg/platform-pg-2/",
|
||||
"platform-pg/platform-pg/"
|
||||
],
|
||||
"files": []
|
||||
},
|
||||
"platform-pg/application-archives/": {
|
||||
"directories": [
|
||||
"platform-pg/application-archives/email-connect/"
|
||||
],
|
||||
"files": []
|
||||
},
|
||||
"platform-pg/apps-pg/": {
|
||||
"directories": [
|
||||
"platform-pg/apps-pg/apps-pg/"
|
||||
],
|
||||
"files": []
|
||||
},
|
||||
"platform-pg/forgejo-db/": {
|
||||
"directories": [
|
||||
"platform-pg/forgejo-db/forgejo-db/"
|
||||
],
|
||||
"files": []
|
||||
},
|
||||
"platform-pg/platform-pg-2/": {
|
||||
"directories": [
|
||||
"platform-pg/platform-pg-2/platform-pg-2/"
|
||||
],
|
||||
"files": []
|
||||
},
|
||||
"platform-pg/platform-pg/": {
|
||||
"directories": [
|
||||
"platform-pg/platform-pg/base/",
|
||||
"platform-pg/platform-pg/wals/"
|
||||
],
|
||||
"files": []
|
||||
},
|
||||
"platform-pg/application-archives/email-connect/": {
|
||||
"directories": [
|
||||
"platform-pg/application-archives/email-connect/2026/"
|
||||
],
|
||||
"files": []
|
||||
},
|
||||
"platform-pg/apps-pg/apps-pg/": {
|
||||
"directories": [
|
||||
"platform-pg/apps-pg/apps-pg/base/",
|
||||
"platform-pg/apps-pg/apps-pg/wals/"
|
||||
],
|
||||
"files": []
|
||||
},
|
||||
"platform-pg/forgejo-db/forgejo-db/": {
|
||||
"directories": [
|
||||
"platform-pg/forgejo-db/forgejo-db/base/",
|
||||
"platform-pg/forgejo-db/forgejo-db/wals/"
|
||||
],
|
||||
"files": []
|
||||
},
|
||||
"platform-pg/platform-pg-2/platform-pg-2/": {
|
||||
"directories": [
|
||||
"platform-pg/platform-pg-2/platform-pg-2/base/",
|
||||
"platform-pg/platform-pg-2/platform-pg-2/wals/"
|
||||
],
|
||||
"files": []
|
||||
},
|
||||
"platform-pg/application-archives/email-connect/2026/": {
|
||||
"directories": [
|
||||
"platform-pg/application-archives/email-connect/2026/10/"
|
||||
],
|
||||
"files": []
|
||||
}
|
||||
},
|
||||
"runtime_delivery_uid": "30c4f6fc-8eb7-4cdc-b692-0f74c4a5b3ce",
|
||||
"finished_at": "2026-10-15T21:54:39.109262+00:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,562 @@
|
|||
{
|
||||
"schema": "netkingdom.storage-share-inventory.v1",
|
||||
"status": "metadata_inventory_complete",
|
||||
"started_at": "2026-10-15T21:52:20.452797+00:00",
|
||||
"read_only": true,
|
||||
"age_keys_read": false,
|
||||
"backup_contents_downloaded": false,
|
||||
"secret_values_exported": false,
|
||||
"source_sha256": "fe56454e5ca740395e6d6a2a0f8185abdac1d8ca6d0eedb31273db409dee4954",
|
||||
"owner_sources": [
|
||||
{
|
||||
"path": "/home/worsch/railiance-platform/scripts/state_hub_preflight_lane.py",
|
||||
"sha256": "8bf63beda028de7c2375084afb7c22fb00a731c54ebf0b3ba369a699d920e0f5"
|
||||
},
|
||||
{
|
||||
"path": "/home/worsch/railiance-platform/scripts/migrate_nextcloud_backup_account.py",
|
||||
"sha256": "d3285f403f3c947f75c3288d989152e59ab9a8f5542bcf901bd0a06b0f3e050c"
|
||||
}
|
||||
],
|
||||
"root": [
|
||||
{
|
||||
"name": "core-apps-pg",
|
||||
"directory": true,
|
||||
"bytes": null
|
||||
},
|
||||
{
|
||||
"name": "core-gitea-db",
|
||||
"directory": true,
|
||||
"bytes": null
|
||||
},
|
||||
{
|
||||
"name": "core-net-kingdom-pg",
|
||||
"directory": true,
|
||||
"bytes": null
|
||||
},
|
||||
{
|
||||
"name": "core-state-hub-db",
|
||||
"directory": true,
|
||||
"bytes": null
|
||||
},
|
||||
{
|
||||
"name": "forgejo",
|
||||
"directory": true,
|
||||
"bytes": null
|
||||
},
|
||||
{
|
||||
"name": "openbao",
|
||||
"directory": true,
|
||||
"bytes": null
|
||||
},
|
||||
{
|
||||
"name": "r01-forgejo-db",
|
||||
"directory": true,
|
||||
"bytes": null
|
||||
},
|
||||
{
|
||||
"name": "r01-net-kingdom-pg",
|
||||
"directory": true,
|
||||
"bytes": null
|
||||
},
|
||||
{
|
||||
"name": "r01-platform-pg",
|
||||
"directory": true,
|
||||
"bytes": null
|
||||
},
|
||||
{
|
||||
"name": "r01-state-hub-db",
|
||||
"directory": true,
|
||||
"bytes": null
|
||||
},
|
||||
{
|
||||
"name": "r01-user-engine-pg",
|
||||
"directory": true,
|
||||
"bytes": null
|
||||
}
|
||||
],
|
||||
"folders": {
|
||||
"core-apps-pg": [],
|
||||
"core-gitea-db": [],
|
||||
"core-net-kingdom-pg": [],
|
||||
"core-state-hub-db": [],
|
||||
"openbao": [
|
||||
{
|
||||
"name": "openbao-raft-snapshot-RPF-WP-0015-T03-REBOOT-20261014-01-20261014T181053Z.snap.age",
|
||||
"directory": false,
|
||||
"bytes": 375618
|
||||
}
|
||||
],
|
||||
"r01-forgejo-db": [
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20260906T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 7515593
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20260907T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 7320543
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20260908T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 7413040
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20260909T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 7663338
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20260910T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 8079045
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20260911T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 8404583
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20260912T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 8631188
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20260913T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 8472109
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20260914T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 8523610
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20261005T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 11745885
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20261006T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 11481436
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20261007T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 11693800
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20261008T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 11784032
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20261009T023003Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 12210534
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20261010T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 12378726
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20261011T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 12369466
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20261012T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 12701177
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20261013T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 12761857
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20261014T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 13110850
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-20261015T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 13349242
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-weekly-20260906T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 7515593
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-weekly-20260913T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 8472109
|
||||
},
|
||||
{
|
||||
"name": "forgejo-db-forgejo-weekly-20261011T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 12369466
|
||||
}
|
||||
],
|
||||
"r01-net-kingdom-pg": [
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20260906T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20260907T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20260908T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20260909T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20260910T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20260911T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20260912T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20260913T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20260914T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20261005T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20261006T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20261007T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20261008T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20261009T023003Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20261010T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20261011T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20261012T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20261013T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20261014T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-20261015T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-weekly-20260906T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-weekly-20260913T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-interhub-weekly-20261011T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 215541
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20260906T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 271941
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20260907T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 272271
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20260908T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 272599
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20260909T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 274660
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20260910T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 274660
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20260911T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 274996
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20260912T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 276353
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20260913T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 277468
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20260914T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 1008342
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20261005T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 58987550
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20261006T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 61979707
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20261007T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 64976206
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20261008T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 68001818
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20261009T023003Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 71016444
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20261010T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 74006618
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20261011T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 77020445
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20261012T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 80037505
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20261013T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 83066939
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20261014T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 86103429
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-20261015T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 89129309
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-weekly-20260906T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 271941
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-weekly-20260913T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 277468
|
||||
},
|
||||
{
|
||||
"name": "net-kingdom-pg-privacyidea_db-weekly-20261011T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 77020445
|
||||
}
|
||||
],
|
||||
"r01-platform-pg": [],
|
||||
"r01-state-hub-db": [
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20260906T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 6924847
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20260907T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 7141118
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20260908T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 7180575
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20260909T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 7390311
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20260910T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 7542258
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20260911T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 7687798
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20260912T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 7748393
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20260913T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 7765176
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20260914T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 7839953
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20261005T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 9407746
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20261006T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 9464763
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20261007T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 9549640
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20261008T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 9602097
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20261009T023003Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 9693941
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20261010T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 9766236
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20261011T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 9864346
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20261012T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 9973489
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20261013T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 10036580
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20261014T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 10265536
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-20261015T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 10388491
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-weekly-20260906T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 6924847
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-weekly-20260913T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 7765176
|
||||
},
|
||||
{
|
||||
"name": "state-hub-db-state_hub-weekly-20261011T023000Z.dump.age",
|
||||
"directory": false,
|
||||
"bytes": 9864346
|
||||
}
|
||||
],
|
||||
"r01-user-engine-pg": []
|
||||
},
|
||||
"caller_auth_path": "auth/netkingdom/oidc/callback",
|
||||
"caller_entity_id": "4a1f663c-0621-0483-edd0-f852274f4b6b",
|
||||
"scope": "Backup/railiance-backups root and bounded identity/database subfolders; other accounts/folders unassessed",
|
||||
"finished_at": "2026-10-15T21:52:28.155972+00:00"
|
||||
}
|
||||
37
tests/test_scaleway_backup_inventory.py
Normal file
37
tests/test_scaleway_backup_inventory.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
"""Scoped S3 list-only fixtures; no credentials, live cluster or cloud."""
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
spec=importlib.util.spec_from_file_location('s3_inventory',ROOT/'tools/bootstrap-recovery/inventory-scaleway.py')
|
||||
module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module)
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self,pages):self.pages=iter(pages);self.calls=[]
|
||||
def list_objects_v2(self,**kw):self.calls.append(kw);return next(self.pages)
|
||||
|
||||
|
||||
def test_scoped_list_metadata_follows_continuation_without_reading_contents():
|
||||
client=Client([{'IsTruncated':True,'NextContinuationToken':'next'},
|
||||
{'Contents':[{'Key':'platform-pg/identity/backup.tar.age','Size':256}]}])
|
||||
r=module.list_prefix(client,'platform-pg/identity/')
|
||||
assert r['files']==[{'key':'platform-pg/identity/backup.tar.age','bytes':256}]
|
||||
assert all(x['Bucket']==module.BUCKET and x['Prefix']=='platform-pg/identity/' for x in client.calls)
|
||||
assert client.calls[1]['ContinuationToken']=='next'
|
||||
|
||||
|
||||
def test_wal_and_base_presence_is_recorded_without_traversal():
|
||||
client=Client([{'CommonPrefixes':[{'Prefix':'platform-pg/base/'},{'Prefix':'platform-pg/wals/'}]}])
|
||||
r=module.inventory(client)
|
||||
assert len(client.calls)==1 and len(r['platform-pg/']['directories'])==2
|
||||
|
||||
|
||||
@pytest.mark.parametrize('key',['elsewhere/backup.age','platform-pg/../backup.age','platform-pg/private\nmarker.age'])
|
||||
def test_foreign_or_unsafe_metadata_refuses(key):
|
||||
with pytest.raises(module.Refused):module.list_prefix(Client([{'Contents':[{'Key':key,'Size':1}]}]),module.PREFIX)
|
||||
|
||||
|
||||
def test_missing_pagination_state_refuses_incomplete_inventory():
|
||||
with pytest.raises(module.Refused):module.list_prefix(Client([{'IsTruncated':True}]),module.PREFIX)
|
||||
43
tests/test_storage_share_inventory.py
Normal file
43
tests/test_storage_share_inventory.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
"""Read-only DAV metadata parser tests; no provider or credential access."""
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
spec=importlib.util.spec_from_file_location('storage_inventory',ROOT/'tools/bootstrap-recovery/inventory-storage-share.py')
|
||||
module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module)
|
||||
|
||||
|
||||
def body(path, kind='file', size='16', origin=''):
|
||||
collection='<d:collection/>' if kind=='dir' else ''
|
||||
return f'''<d:multistatus xmlns:d="DAV:"><d:response><d:href>{origin}{path}</d:href>
|
||||
<d:propstat><d:status>HTTP/1.1 200 OK</d:status><d:prop><d:resourcetype>{collection}</d:resourcetype>
|
||||
<d:getcontentlength>{size}</d:getcontentlength></d:prop></d:propstat></d:response></d:multistatus>'''.encode()
|
||||
|
||||
|
||||
def test_only_closed_safe_metadata_is_returned():
|
||||
raw=body(module.BASE+'bootstrap.tar.age')
|
||||
assert module.parse_listing(raw,'')==[{'name':'bootstrap.tar.age','directory':False,'bytes':16}]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('path,origin',[(module.BASE+'%2e%2e/else.age',''),
|
||||
('/other/account/backup.age',''),(module.BASE+'backup.age','https://unapproved.invalid')])
|
||||
def test_foreign_and_escaping_references_refuse(path,origin):
|
||||
with pytest.raises(module.Refused):module.parse_listing(body(path,origin=origin),'')
|
||||
|
||||
|
||||
def test_non_numeric_size_and_oversized_response_refuse():
|
||||
with pytest.raises(module.Refused):module.parse_listing(body(module.BASE+'backup.age',size='not-a-size'),'')
|
||||
with pytest.raises(module.Refused):module.parse_listing(b'X'*(module.LIMIT+1),'')
|
||||
|
||||
|
||||
def test_unrecognized_plaintext_files_are_not_exported():
|
||||
assert module.parse_listing(body(module.BASE+'private-password.txt'),'')==[]
|
||||
|
||||
|
||||
def test_unrelated_authenticated_role_cannot_read_retrieval_credential(monkeypatch):
|
||||
calls=[]
|
||||
monkeypatch.setattr(module,'bao',lambda args:calls.append(args) or object())
|
||||
monkeypatch.setattr(module,'data',lambda result:{'data':{'policies':['default'],'path':'auth/netkingdom/oidc/callback'}})
|
||||
with pytest.raises(module.Refused):module.inventory()
|
||||
assert calls==[['token','lookup','-format=json']]
|
||||
|
|
@ -21,3 +21,17 @@ archive fixtures, never production credentials:
|
|||
```bash
|
||||
python3 -m pytest -q tests/test_bootstrap_recovery_transport.py
|
||||
```
|
||||
|
||||
|
||||
`inventory-storage-share.py` is a silent read-only child of the existing
|
||||
attended Platform Backup-account procedure. It checks the caller's OIDC/admin
|
||||
scope, reuses the owner custody accessor, and exports only bounded DAV names and
|
||||
sizes. Warden revokes and cleans up the session. It never reads an age key or
|
||||
object content and contains provider errors/redirects/proxies.
|
||||
|
||||
`inventory-scaleway.py` lists bounded metadata in the fixed backup bucket/prefix
|
||||
using the existing scoped runtime delivery and pinned primary cluster. It uses
|
||||
neither the bootstrap API key nor object GET/write/delete operations. Run through
|
||||
the already admitted caller with cached boto3 1.40.18; it emits only its safe
|
||||
receipt. Both inventories explicitly state their bounds and cannot prove asset
|
||||
absence outside them, ciphertext integrity, decryption or native recovery.
|
||||
|
|
|
|||
104
tools/bootstrap-recovery/inventory-scaleway.py
Normal file
104
tools/bootstrap-recovery/inventory-scaleway.py
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Read-only bounded S3 backup metadata through existing scoped runtime delivery.
|
||||
|
||||
No bootstrap API key, new grant, object content read, age key, provider write or
|
||||
retention action. The caller must already have admitted cluster read authority.
|
||||
"""
|
||||
import argparse
|
||||
import base64
|
||||
from datetime import datetime, timezone
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
BUCKET='railiance-platform-pg-backup'
|
||||
ENDPOINT='https://s3.nl-ams.scw.cloud'
|
||||
PREFIX='platform-pg/'
|
||||
CLUSTER='a553c742-0115-43d4-99a4-a5ca56fe0786'
|
||||
|
||||
|
||||
class Refused(Exception):pass
|
||||
|
||||
|
||||
def kube(args):
|
||||
p=subprocess.run(['kubectl',*args,'-o','json'],stdout=subprocess.PIPE,stderr=subprocess.DEVNULL,timeout=30)
|
||||
if p.returncode:raise Refused()
|
||||
return json.loads(p.stdout)
|
||||
|
||||
|
||||
def list_prefix(client,prefix):
|
||||
if not prefix.startswith(PREFIX) or not re.fullmatch(r'[A-Za-z0-9._/-]+',prefix) or '..' in prefix.split('/'):raise Refused()
|
||||
directories=[];files=[];continuation=None
|
||||
for _ in range(20):
|
||||
args={'Bucket':BUCKET,'Prefix':prefix,'Delimiter':'/','MaxKeys':1000}
|
||||
if continuation:args['ContinuationToken']=continuation
|
||||
page=client.list_objects_v2(**args)
|
||||
for row in page.get('CommonPrefixes',[]):
|
||||
value=row['Prefix']
|
||||
if not value.startswith(prefix) or not value.endswith('/') or not re.fullmatch(r'[A-Za-z0-9._/-]+',value) or '..' in value.split('/'):raise Refused()
|
||||
directories.append(value)
|
||||
for row in page.get('Contents',[]):
|
||||
name=row['Key']
|
||||
if not name.startswith(prefix) or not re.fullmatch(r'[A-Za-z0-9._/-]+',name) or '..' in name.split('/'):raise Refused()
|
||||
if name.endswith(('.age','.json','.yaml')):
|
||||
size=row['Size']
|
||||
if type(size) is not int or size<0:raise Refused()
|
||||
files.append({'key':name,'bytes':size})
|
||||
if not page.get('IsTruncated'):break
|
||||
continuation=page.get('NextContinuationToken')
|
||||
if not continuation:raise Refused()
|
||||
else:raise Refused()
|
||||
return {'directories':sorted(set(directories)),'files':sorted(files,key=lambda x:x['key'])}
|
||||
|
||||
|
||||
def inventory(client):
|
||||
pending=[(PREFIX,0)];result={}
|
||||
while pending:
|
||||
prefix,depth=pending.pop(0)
|
||||
if prefix in result:raise Refused()
|
||||
if len(result)>=64:raise Refused()
|
||||
row=list_prefix(client,prefix);result[prefix]=row
|
||||
if depth<3:
|
||||
for child in row['directories']:
|
||||
# Native database WAL/base trees are not application/key-file
|
||||
# archives. Record their presence without enumerating every WAL.
|
||||
if any(x in child.split('/') for x in ['wals','base']):continue
|
||||
pending.append((child,depth+1))
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
p=argparse.ArgumentParser(description=__doc__);p.add_argument('--receipt',required=True)
|
||||
a=p.parse_args();fd=os.open(a.receipt,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
|
||||
r={'schema':'netkingdom.scaleway-backup-inventory.v1','status':'failed',
|
||||
'started_at':datetime.now(timezone.utc).isoformat(),'read_only':True,
|
||||
'provider':'Scaleway','bucket':BUCKET,'prefix':PREFIX,'source_sha256':hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
|
||||
'bootstrap_api_key_used':False,'age_keys_read':False,'object_contents_downloaded':False,
|
||||
'secret_values_exported':False,'scope':'Bounded prefix metadata, at most three child levels; WAL/base trees not enumerated; other buckets/prefixes unassessed'}
|
||||
try:
|
||||
if kube(['get','namespace','kube-system'])['metadata']['uid']!=CLUSTER:raise Refused()
|
||||
delivered=kube(['-n','databases','get','secret','platform-pg-backup-s3'])
|
||||
if delivered['metadata']['namespace']!='databases' or delivered['metadata']['name']!='platform-pg-backup-s3':raise Refused()
|
||||
values=delivered['data']
|
||||
# Same governed runtime delivery used by Platform's archive verifier;
|
||||
# explicit credentials avoid SDK ambient/profile/metadata discovery.
|
||||
import boto3
|
||||
from botocore.config import Config
|
||||
client=boto3.client('s3',endpoint_url=ENDPOINT,region_name='nl-ams',
|
||||
aws_access_key_id=base64.b64decode(values['ACCESS_KEY_ID'],validate=True).decode(),
|
||||
aws_secret_access_key=base64.b64decode(values['ACCESS_SECRET_KEY'],validate=True).decode(),
|
||||
config=Config(signature_version='s3v4',proxies={},connect_timeout=15,read_timeout=30,
|
||||
retries={'max_attempts':1},s3={'addressing_style':'path'}))
|
||||
r['prefixes']=inventory(client);r['status']='metadata_inventory_complete'
|
||||
r['runtime_delivery_uid']=delivered['metadata']['uid']
|
||||
except Exception:r['error']='inventory_failed_diagnostics_suppressed'
|
||||
finally:
|
||||
r['finished_at']=datetime.now(timezone.utc).isoformat()
|
||||
with os.fdopen(fd,'w') as out:json.dump(r,out,indent=2);out.write('\n')
|
||||
return int(r['status']=='failed')
|
||||
|
||||
|
||||
if __name__=='__main__':raise SystemExit(main())
|
||||
109
tools/bootstrap-recovery/inventory-storage-share.py
Normal file
109
tools/bootstrap-recovery/inventory-storage-share.py
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Silent read-only inventory in the existing Platform Backup-account procedure.
|
||||
|
||||
Runs only inside the attended platform-admin envelope. Reuses Platform's owner
|
||||
custody accessor and fixed provider/account contract; no age key is read and no
|
||||
backup is downloaded, uploaded, deleted, rotated or granted.
|
||||
"""
|
||||
import argparse
|
||||
import base64
|
||||
from datetime import datetime, timezone
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
ROOT=Path(__file__).resolve().parents[2]
|
||||
OWNER=ROOT.parent/'railiance-platform/scripts'
|
||||
sys.path.insert(0,str(OWNER))
|
||||
from state_hub_preflight_lane import bao, data
|
||||
from migrate_nextcloud_backup_account import HOST, OPERATOR_PATH
|
||||
BASE='/remote.php/dav/files/Backup/railiance-backups/'
|
||||
LIMIT=4*1024*1024
|
||||
NAME=re.compile(r'[A-Za-z0-9._-]{1,160}\Z')
|
||||
|
||||
|
||||
class Refused(Exception):pass
|
||||
|
||||
|
||||
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
||||
def redirect_request(self,*args,**kwargs):raise Refused()
|
||||
|
||||
|
||||
def parse_listing(raw, relative):
|
||||
if len(raw)>LIMIT:raise Refused()
|
||||
rows=[]
|
||||
for item in ET.fromstring(raw).findall('{DAV:}response'):
|
||||
href=urllib.parse.urlsplit(item.findtext('{DAV:}href') or '')
|
||||
if href.netloc and href.netloc!=urllib.parse.urlsplit(HOST).netloc:raise Refused()
|
||||
path=urllib.parse.unquote(href.path)
|
||||
if not path.startswith(BASE+relative):raise Refused()
|
||||
name=path[len(BASE+relative):].rstrip('/')
|
||||
if not name:continue
|
||||
if not NAME.fullmatch(name) or name in {'.','..'}:raise Refused()
|
||||
for row in item.findall('{DAV:}propstat'):
|
||||
if ' 200 ' not in (row.findtext('{DAV:}status') or ''):continue
|
||||
prop=row.find('{DAV:}prop')
|
||||
if prop is None:continue
|
||||
folder=prop.find('{DAV:}resourcetype/{DAV:}collection') is not None
|
||||
size=prop.findtext('{DAV:}getcontentlength')
|
||||
if size is not None and not size.isdigit():raise Refused()
|
||||
# Export only known-safe operational names/size, not arbitrary DAV
|
||||
# properties, URLs, account metadata or raw provider responses.
|
||||
if folder or name.endswith(('.age','.json','.yaml')):
|
||||
rows.append({'name':name,'directory':folder,'bytes':int(size) if size else None})
|
||||
if len(rows)>10000 or len({r['name'] for r in rows})!=len(rows):raise Refused()
|
||||
return sorted(rows,key=lambda x:x['name'])
|
||||
|
||||
|
||||
def listing(relative,credentials):
|
||||
if relative and (not relative.endswith('/') or not NAME.fullmatch(relative[:-1])):raise Refused()
|
||||
auth=base64.b64encode((credentials['BACKUP_USERNAME']+':'+credentials['BACKUP_PASSWORD']).encode()).decode()
|
||||
req=urllib.request.Request(HOST+BASE+relative,
|
||||
data=b'<d:propfind xmlns:d="DAV:"><d:prop><d:resourcetype/><d:getcontentlength/></d:prop></d:propfind>',
|
||||
method='PROPFIND',headers={'Authorization':'Basic '+auth,'Depth':'1','Content-Type':'application/xml'})
|
||||
opener=urllib.request.build_opener(urllib.request.ProxyHandler({}),NoRedirect())
|
||||
with opener.open(req,timeout=30) as response:
|
||||
if response.status!=207 or response.url!=HOST+BASE+relative:raise Refused()
|
||||
raw=response.read(LIMIT+1)
|
||||
return parse_listing(raw,relative)
|
||||
|
||||
|
||||
def inventory():
|
||||
# Lookup payload contains a token: retain only safe caller/role facts.
|
||||
identity=data(bao(['token','lookup','-format=json']))['data']
|
||||
if 'platform-admin' not in identity.get('policies',[]) or 'root' in identity.get('policies',[]):raise Refused()
|
||||
if not str(identity.get('path','')).startswith('auth/netkingdom/'):raise Refused()
|
||||
credentials=data(bao(['read','-format=json',OPERATOR_PATH]))['data']['data']
|
||||
if credentials.get('BACKUP_USERNAME')!='Backup' or not isinstance(credentials.get('BACKUP_PASSWORD'),str) or not credentials['BACKUP_PASSWORD']:raise Refused()
|
||||
root=listing('',credentials)
|
||||
folders={}
|
||||
for row in root:
|
||||
if row['directory'] and re.match(r'^(?:r01-|core-|identity|sso|bootstrap|net-kingdom|openbao)',row['name']):
|
||||
if len(folders)>=32:raise Refused()
|
||||
folders[row['name']]=listing(row['name']+'/',credentials)
|
||||
return {'root':root,'folders':folders,'caller_auth_path':identity['path'],
|
||||
'caller_entity_id':identity.get('entity_id'),'scope':'Backup/railiance-backups root and bounded identity/database subfolders; other accounts/folders unassessed'}
|
||||
|
||||
|
||||
def main():
|
||||
p=argparse.ArgumentParser(description=__doc__);p.add_argument('--receipt',required=True)
|
||||
a=p.parse_args();fd=os.open(a.receipt,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
|
||||
r={'schema':'netkingdom.storage-share-inventory.v1','status':'failed','started_at':datetime.now(timezone.utc).isoformat(),
|
||||
'read_only':True,'age_keys_read':False,'backup_contents_downloaded':False,'secret_values_exported':False,
|
||||
'source_sha256':hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
|
||||
'owner_sources':[{'path':str(OWNER/name),'sha256':hashlib.sha256((OWNER/name).read_bytes()).hexdigest()} for name in ['state_hub_preflight_lane.py','migrate_nextcloud_backup_account.py']]}
|
||||
try:r.update(inventory());r['status']='metadata_inventory_complete'
|
||||
except Exception:r['error']='inventory_failed_diagnostics_suppressed'
|
||||
finally:
|
||||
r['finished_at']=datetime.now(timezone.utc).isoformat()
|
||||
with os.fdopen(fd,'w') as out:json.dump(r,out,indent=2);out.write('\n')
|
||||
return int(r['status']=='failed')
|
||||
|
||||
|
||||
if __name__=='__main__':raise SystemExit(main())
|
||||
|
|
@ -438,6 +438,27 @@ reviewed isolated target/procedure and actual owner/native acceptance. Evidence:
|
|||
`docs/evidence/2026-10-15-bootstrap-recovery/`; full verification results are
|
||||
recorded there. No workplan completion is asserted.
|
||||
|
||||
2026-10-15 off-host discovery follow-through: operator reports holding the
|
||||
`railiance-backup` age key independently; no private key was received/read.
|
||||
The read-only Storage Share inventory completed through the existing attended
|
||||
Platform Backup-account custody procedure; Warden exited 0, confirming session
|
||||
self-revocation/cleanup. The inspected account has a current privacyIDEA database
|
||||
ciphertext (`r01-net-kingdom-pg/net-kingdom-pg-privacyidea_db-20261015T023000Z.dump.age`,
|
||||
89,129,309 bytes) and the October14 OpenBao snapshot. The inspected folders do
|
||||
not contain a bootstrap/identity-file bundle. Bounded Scaleway metadata inventory
|
||||
under the existing scoped backup delivery also completed; it found database
|
||||
base/WAL trees and an application archive subtree, with no identity-file bundle
|
||||
in the inspected scope. No bootstrap API key, age key or object content was read
|
||||
by these inventories; other accounts/buckets/prefixes remain unassessed.
|
||||
|
||||
Thirteen fixture checks pass for contained caller scope, metadata/path bounds,
|
||||
pagination and malformed/foreign responses. Requested the human's hidden-terminal
|
||||
key check against the backup owner's declared public recipient. Matching that
|
||||
recipient remains distinct from actual decryption and full recovery; the
|
||||
bootstrap `keys/age.pub` is not assumed to be the same recipient. T02/T03 remain
|
||||
open. Evidence: `offsite-discovery-summary.json` and the two exact inventory
|
||||
receipts in the existing October15 evidence folder.
|
||||
|
||||
## Verify owner-approved recovery and current native callers
|
||||
|
||||
```task
|
||||
|
|
@ -445,7 +466,7 @@ id: NK-WP-0049-T03
|
|||
status: wait
|
||||
priority: high
|
||||
depends_on: [NK-WP-0049-T02]
|
||||
blocking_reason: "Exact off-host artifact/coverage and independently held matching key remain unconfirmed after Scaleway/Storage Share destination identification. Requires completed T02 owner/runtime and legacy-credential disposition, then the exact reviewed isolated recovery ceremony and native consumer acceptance. No live bootstrap/root retrieval or deferred exposure rotation is authorized by source tests."
|
||||
blocking_reason: "A recent off-host PI database ciphertext is identified, but matching full identity data/key/config coverage and actual independent-key decryption remain unverified after bounded Storage Share/Scaleway inventory. Requires completed T02 owner/runtime and legacy-credential disposition, then the exact reviewed isolated recovery ceremony and native consumer acceptance. No live bootstrap/root retrieval or deferred exposure rotation is authorized by source tests."
|
||||
state_hub_task_id: "d68cd4d3-1ba3-530e-88f9-26ebb8dd6eac"
|
||||
```
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue