Prepare bounded off-host privacyIDEA recovery drill
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
This commit is contained in:
parent
c262470089
commit
a1939a7d06
12 changed files with 549 additions and 23 deletions
|
|
@ -136,9 +136,50 @@ 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.
|
||||
The operator subsequently reported the hidden-terminal check passed and the
|
||||
temporary key file was removed. This is operator-reported recipient matching;
|
||||
the private key was not provided to the agent. Actual decryption remains pending.
|
||||
|
||||
## Prepared bounded privacyIDEA database drill — 2026-10-16
|
||||
|
||||
The identified existing off-host ciphertext was downloaded through the same
|
||||
attended Platform Backup-account procedure. The before/GET/after strong-ETag
|
||||
binding and exact byte count passed. Ciphertext SHA256:
|
||||
`cc81da442755928171d2c51bce93eda7de4392a0a1aa9d0ffb83a25ba34e5ead`.
|
||||
See `privacyidea-transfer-r1.json` in the October15 evidence folder. The owner
|
||||
envelope exited0 and self-revoked; no age key or plaintext was read by the fetch.
|
||||
Ciphertext is retained in the fresh private `/tmp/nk49-offhost-j9dzl0ge/` directory.
|
||||
|
||||
The following exact human command performs only an isolated local database
|
||||
drill. It checks the transfer receipt/digest before opening the temporary key,
|
||||
checks the backup recipient, refuses an unrelated wrong key, decrypts to private
|
||||
temporary storage and restores into disposable PostgreSQL. Image
|
||||
`postgres@sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70`
|
||||
is already cached locally (pg_restore16.14). There is no image pull, network,
|
||||
published port, host data mount, production connection or IAM/client execution.
|
||||
Database storage is a bounded temporary memory filesystem. Only fixed aggregate
|
||||
counts for `token`, `resolver`, and `realm` leave the database. The container,
|
||||
volumes and temporary plaintext are removed and cleanup is checked. The original
|
||||
ciphertext remains for reproducibility. This proves database recovery only;
|
||||
token decryption, audit signature continuity and full identity service recovery
|
||||
require matching key/config files and further native owner checks.
|
||||
|
||||
Run from the repository in the operator's own terminal. Paste the private key
|
||||
only into the hidden prompt, then share the success/error line or safe JSON
|
||||
receipt. The receipt destination must be new; do not overwrite a failed receipt.
|
||||
|
||||
```bash
|
||||
cd /home/worsch/net-kingdom
|
||||
bash sso-mfa/bootstrap/sops-custody-unlock.sh \
|
||||
--expected-recipient age1zvryunvjhvpkmasskauga2heeg0ztnte9ymgppvjge36ekumk50syr3tsz \
|
||||
--prompt -- python3 tools/bootstrap-recovery/check-privacyidea-database.py \
|
||||
--transfer-receipt docs/evidence/2026-10-15-bootstrap-recovery/privacyidea-transfer-r1.json \
|
||||
--receipt docs/evidence/2026-10-15-bootstrap-recovery/privacyidea-database-drill-r1.json
|
||||
```
|
||||
|
||||
Source verification:23 focused checks passed, including a real local Docker
|
||||
custom-format dump, age encryption, temporary fixture-key unlock, wrong-key
|
||||
refusal, native restore/readback and cleanup. Those tests use synthetic data and
|
||||
fresh fixture keys. They are not evidence of actual backup decryption. If the
|
||||
real drill fails, review its closed receipt; do not expose PostgreSQL/age stderr
|
||||
or dump contents in chat. T02/T03 remain open.
|
||||
|
|
|
|||
|
|
@ -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 plus two bounded provider metadata inventory helpers",
|
||||
"entry_count": 89,
|
||||
"observed_at": "2026-10-15T22:08:27.470032+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 plus two bounded provider metadata inventory helpers plus bounded fixed-object ciphertext fetch and isolated PostgreSQL recovery check",
|
||||
"entry_count": 91,
|
||||
"entries": [
|
||||
{
|
||||
"path": "Makefile",
|
||||
|
|
@ -431,7 +431,7 @@
|
|||
},
|
||||
{
|
||||
"path": "tools/bootstrap-recovery/inventory-storage-share.py",
|
||||
"sha256": "fe56454e5ca740395e6d6a2a0f8185abdac1d8ca6d0eedb31273db409dee4954",
|
||||
"sha256": "673f9b17fb2f3fef6fcdb5c50a2bcca69e3df3157a9e5582a2d995c6b87640b5",
|
||||
"classification": "read-only bounded owner backup metadata; existing caller/custody only; no age key or backup content read"
|
||||
},
|
||||
{
|
||||
|
|
@ -448,6 +448,16 @@
|
|||
"path": "tools/sync-user-engine-delivery-secret.sh",
|
||||
"sha256": "7c0fb6b066e4ad8b103e75551beecd5de65a4bbb22570d95b42e5da6395e2dfc",
|
||||
"classification": "retired entry point; refuses before credential/cluster access"
|
||||
},
|
||||
{
|
||||
"path": "tools/bootstrap-recovery/fetch-privacyidea-backup.py",
|
||||
"sha256": "233160e1206643a5cf7ad05141d733f0a75aff651190e27f77515a124ff0a59f",
|
||||
"classification": "bounded off-host transfer / isolated database drill; actual recovery acceptance requires operator execution"
|
||||
},
|
||||
{
|
||||
"path": "tools/bootstrap-recovery/check-privacyidea-database.py",
|
||||
"sha256": "90f454c87998d7f5d625e3c6656c3418f8f56275dc1879a9d19ed605431b94a4",
|
||||
"classification": "bounded off-host transfer / isolated database drill; actual recovery acceptance requires operator execution"
|
||||
}
|
||||
],
|
||||
"source_inventory_complete_for_declared_scope": true,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"observed_at": "2026-10-15T22:08:27.470032+00:00",
|
||||
"operator_reported_key_check_passed": true,
|
||||
"private_key_received_by_agent": false,
|
||||
"actual_ciphertext_decryption_verified": false,
|
||||
"transfer_receipt": "privacyidea-transfer-r1.json",
|
||||
"transfer_envelope_exit_code": 0,
|
||||
"transfer_session_revoked_and_cleaned": true,
|
||||
"source_tests": {
|
||||
"passed": 23,
|
||||
"command": "NK49_RUN_DISPOSABLE_POSTGRES_TEST=1 python3 -m pytest -q tests/test_privacyidea_offhost_recovery.py tests/test_storage_share_inventory.py tests/test_scaleway_backup_inventory.py",
|
||||
"duration_seconds": 8.54,
|
||||
"data": "synthetic database and fresh disposable age keys only",
|
||||
"includes_native_postgres_restore_and_cleanup": true
|
||||
},
|
||||
"pending_human_command": "docs/bootstrap-recovery-ceremony.md: Prepared bounded privacyIDEA database drill",
|
||||
"native_identity_recovery_proven": false,
|
||||
"sources": [
|
||||
{
|
||||
"path": "tools/bootstrap-recovery/check-privacyidea-database.py",
|
||||
"sha256": "90f454c87998d7f5d625e3c6656c3418f8f56275dc1879a9d19ed605431b94a4"
|
||||
},
|
||||
{
|
||||
"path": "tools/bootstrap-recovery/fetch-privacyidea-backup.py",
|
||||
"sha256": "233160e1206643a5cf7ad05141d733f0a75aff651190e27f77515a124ff0a59f"
|
||||
},
|
||||
{
|
||||
"path": "tests/test_privacyidea_offhost_recovery.py",
|
||||
"sha256": "ea977b0d3368fb14f6a7a252c24cc5cb9915022afa3dca61c80952e7489bd0e5"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"observed_at": "2026-10-15T21:58:20.278564+00:00",
|
||||
"observed_at": "2026-10-15T22:08:27.470032+00:00",
|
||||
"operator_reports_holding_railiance_backup_age_key": true,
|
||||
"private_key_received_or_read": false,
|
||||
"candidate_key_match_verified": false,
|
||||
|
|
@ -10,8 +10,11 @@
|
|||
"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
|
||||
"candidate_contents_downloaded": true,
|
||||
"bootstrap_identity_file_bundle_found_in_inspected_scope": false,
|
||||
"transfer_receipt": "privacyidea-transfer-r1.json",
|
||||
"attended_transfer_envelope_exit_code": 0,
|
||||
"transfer_session_self_revocation_and_cleanup_confirmed": true
|
||||
},
|
||||
"scaleway": {
|
||||
"inventory": "scaleway-inventory-r1.json",
|
||||
|
|
@ -26,7 +29,9 @@
|
|||
"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."
|
||||
"scope": "A successful key match checks the declared public recipient, not actual ciphertext decryption or whole-stack recovery.",
|
||||
"operator_reports_check_passed": true,
|
||||
"operator_reports_temporary_key_removed": true
|
||||
},
|
||||
"source_tests": {
|
||||
"passed": 13,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"schema": "netkingdom.privacyidea-offhost-transfer.v1",
|
||||
"status": "ciphertext_fetched",
|
||||
"started_at": "2026-10-15T22:07:08.615448+00:00",
|
||||
"provider": "Storage Share",
|
||||
"object": "r01-net-kingdom-pg/net-kingdom-pg-privacyidea_db-20261015T023000Z.dump.age",
|
||||
"age_keys_read": false,
|
||||
"plaintext_created": false,
|
||||
"production_writes": false,
|
||||
"source_sha256": "233160e1206643a5cf7ad05141d733f0a75aff651190e27f77515a124ff0a59f",
|
||||
"owner_accessor_sha256": "673f9b17fb2f3fef6fcdb5c50a2bcca69e3df3157a9e5582a2d995c6b87640b5",
|
||||
"caller_auth_path": "auth/netkingdom/oidc/callback",
|
||||
"caller_entity_id": "4a1f663c-0621-0483-edd0-f852274f4b6b",
|
||||
"ciphertext_bytes": 89129309,
|
||||
"ciphertext_sha256": "cc81da442755928171d2c51bce93eda7de4392a0a1aa9d0ffb83a25ba34e5ead",
|
||||
"object_unchanged_during_transfer": true,
|
||||
"ciphertext_path": "/tmp/nk49-offhost-j9dzl0ge/privacyidea.dump.age",
|
||||
"finished_at": "2026-10-15T22:07:16.363645+00:00"
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
{
|
||||
"workplan": "NK-WP-0049",
|
||||
"observed_at": "2026-10-15T20:56:58.471680+00:00",
|
||||
"observed_at": "2026-10-15T22:08:27.470032+00:00",
|
||||
"status": "source_prepared_pending_owner_inputs_and_native_acceptance",
|
||||
"backup_destinations_operator_reported": [
|
||||
"Scaleway",
|
||||
"Storage Share"
|
||||
],
|
||||
"exact_offhost_artifact_confirmed": false,
|
||||
"exact_offhost_artifact_confirmed": true,
|
||||
"independently_held_matching_key_confirmed": false,
|
||||
"recovery_key_or_backup_contents_retrieved": false,
|
||||
"recovery_key_or_backup_contents_retrieved": true,
|
||||
"legacy_token_secret_present": true,
|
||||
"legacy_trigger_secret_present": true,
|
||||
"legacy_provider_revocation_proven": false,
|
||||
|
|
@ -18,9 +18,9 @@
|
|||
"verification": "source-verification.json",
|
||||
"ceremony": "../../bootstrap-recovery-ceremony.md",
|
||||
"outstanding": [
|
||||
"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",
|
||||
"Matching off-host identity data/key/config set remains unlocated in inspected scopes; selected existing PI database ciphertext is now fetched and digest-bound",
|
||||
"Operator reports matching-recipient key check passed; actual ciphertext decryption/native database drill is prepared and pending operator hidden-key invocation",
|
||||
"Full identity service recovery, token decryption, audit signature continuity and 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,
|
||||
|
|
@ -29,5 +29,12 @@
|
|||
"key_matches_selected_ciphertext_verified": false,
|
||||
"offsite_discovery": "offsite-discovery-summary.json",
|
||||
"privacyidea_database_ciphertext_identified": true,
|
||||
"required_full_asset_coverage_confirmed": false
|
||||
"required_full_asset_coverage_confirmed": false,
|
||||
"operator_reports_recipient_check_passed": true,
|
||||
"operator_reports_temporary_key_cleanup": true,
|
||||
"private_key_received_by_agent": false,
|
||||
"ciphertext_fetched": true,
|
||||
"transfer_receipt": "privacyidea-transfer-r1.json",
|
||||
"prepared_database_drill": "../../bootstrap-recovery-ceremony.md",
|
||||
"age_private_key_retrieved_by_agent": false
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue