Record off-host database recovery and prepare identity backup coverage
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
This commit is contained in:
tegwick 2026-10-16 00:32:50 +02:00
parent a1939a7d06
commit dca999222f
13 changed files with 596 additions and 29 deletions

View file

@ -1,7 +1,8 @@
# NK-WP-0049 recovery ceremony and closure packet
Status: preparation; no live restore, credential read/grant, revocation, rotation,
root retrieval or production bootstrap is authorized by this document.
Status: actual off-host privacyIDEA database drill passed; full identity backup
coverage remains in preparation. This document grants no credential export,
provider access, revocation, rotation, root retrieval or production bootstrap.
## What has been established
@ -41,12 +42,12 @@ 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 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.
The operator supplied the independently held `railiance-backup` age key only to
the attended local custody helper. The actual off-host PI ciphertext decrypted
and restored successfully; the agent received no private key material. The full
identity asset manifest remains unfulfilled. 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 full coverage is unknown.
Once fixed, bind the source revisions, target identities, allowed operations,
recovery/cleanup sequence and maintenance window to the reviewed live packet.
Existing eligibility or another client's approval is not this ceremony's grant.
@ -137,8 +138,8 @@ self-revoked. Existing scoped backup credentials were reused without changes;
the Scaleway bootstrap API key was not used.
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.
temporary key file was removed. The later actual database drill also passed;
the private key was not provided to the agent.
## Prepared bounded privacyIDEA database drill — 2026-10-16
@ -164,7 +165,9 @@ 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
Executed successfully on 2026-10-16 (local time). The command is retained for
provenance; its r1 receipt now exists and must not be overwritten or rerun merely
to repeat evidence. Run future reviewed drills with a new receipt. Paste the 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.
@ -183,3 +186,63 @@ 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.
## Actual database recovery result — 2026-10-16
The operator reported success and the actual receipt was reviewed against the
exact checker source, pinned image and fetched ciphertext SHA256. The retained
ciphertext digest was independently rechecked. Decryption, unrelated wrong-key
refusal, native PostgreSQL restore, fixed aggregate readback and container/plaintext
cleanup all passed. The restored database has five tokens, two resolvers and
two realms. The local Docker inventory contains no remaining NK49 containers.
Evidence: `privacyidea-database-drill-r1.json` and `database-drill-review.json`
in the October15 evidence directory. No production connection occurred.
This proves recovery of the selected database using the operator's independently
held key. It does not prove the recoverability of token plaintext, old audit
signatures, directory accounts, issuer keys or a full identity environment.
## Missing coverage prepared at the operator's request
The operator confirmed there is no known full identity bundle and requested
preparation of the missing coverage. The machine-readable proposal is
[`identity-backup-profile.json`](../tools/bootstrap-recovery/identity-backup-profile.json).
The read-only preparer checked the primary cluster UID, four current Deployment
UIDs/readiness/observed generations, five Bound PVC UIDs and the complete current
Secret/ConfigMap reference sets. It captured names and identities only; it did
not read Secret values or execute in pods. Its exact result is
`identity-backup-preparation-r1.json`. Fourteen tests cover foreign/stale targets,
new unreviewed dependencies, projected Secrets and private-value suppression.
| Component | Required new coverage | Required recovery proof |
| --- | --- | --- |
| LLDAP | Consistent `users.db` snapshot, remaining durable data and current credentials/configuration | User, group and membership preservation; existing-user authentication |
| Authelia | Consistent storage DB, original encryption/OIDC/session keys and current configuration | Storage decryption, signing identity and isolated authentication |
| privacyIDEA | Original enckey plus matching audit pair, configuration/scripts and a linked native database dump | Existing token decryption/MFA, old audit signature verification and new isolated audit entry |
| KeyCape | Current registry/config and signing key, policy/receipts, every referenced credential's recovery or independent reissue disposition | Issuer/JWKS identity, registry/policy continuity and positive/negative consumer acceptance |
The proposed primary prefix is
`platform-pg/application-archives/net-kingdom/`; the proposed secondary folder is
`Backup/railiance-backups/net-kingdom-identity/`. These are **not admitted**.
Platform's current generic application archive adapter only admits
`coulomb-social` and `email-connect`, and their publisher custody profiles are
application-bound. A new NetKingdom profile and scoped publisher require owner
admission; existing credentials must not be borrowed. The operator's Scaleway
bootstrap API key is unnecessary for preparation.
The initial goal is one verified generation, with a measured encrypted size and
fresh provider quota/upload-overlap checks. A proposed 512 MiB ceiling is a
refusal bound, not evidence that the data fits the secondary budget. Seven daily
and two weekly points remain a later measured proposal. No retention deletion
or recurring schedule was installed. Capture must preserve original keys and
use SQLite online-backup/PostgreSQL logical snapshots, record each capture
interval and refuse changed source identities/configuration. It must not claim
global atomicity or substitute a fresh export for the already recovered old dump.
Implementation remains: owner-admitted capture/sealing and new-profile transfer
adapters, exact credential disposition, immutable create plus retrieved readback,
then the isolated full-service recovery checks above. The current bootstrap
secrets-only tar checker cannot verify this new data-bearing profile. Approval
of preparation does not make these adapters implemented or the proposed
destinations active. The profile lists these gates explicitly; no live grant or
deployment approval is requested from this incomplete execution packet.

View file

@ -1,8 +1,8 @@
{
"schema": "netkingdom.bootstrap-caller-inventory.v1",
"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,
"observed_at": "2026-10-15T22:29:09.131605+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 plus proposed full identity backup profile and metadata-only preparer",
"entry_count": 93,
"entries": [
{
"path": "Makefile",
@ -458,6 +458,16 @@
"path": "tools/bootstrap-recovery/check-privacyidea-database.py",
"sha256": "90f454c87998d7f5d625e3c6656c3418f8f56275dc1879a9d19ed605431b94a4",
"classification": "bounded off-host transfer / isolated database drill; actual recovery acceptance requires operator execution"
},
{
"path": "tools/bootstrap-recovery/prepare-identity-coverage.py",
"sha256": "cd5f3067d28b4c7cc93ed08b8cf3869218bdecf7c54b5dffed22d697de71dbb1",
"classification": "missing identity backup coverage proposal and metadata-only preparation; no live capture/grant/delivery capability"
},
{
"path": "tools/bootstrap-recovery/identity-backup-profile.json",
"sha256": "176c1f2f9111c2c51503212deb162274609d5e53bfe03d7319e19e44c5e09500",
"classification": "missing identity backup coverage proposal and metadata-only preparation; no live capture/grant/delivery capability"
}
],
"source_inventory_complete_for_declared_scope": true,

View file

@ -2,7 +2,7 @@
"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,
"actual_ciphertext_decryption_verified": true,
"transfer_receipt": "privacyidea-transfer-r1.json",
"transfer_envelope_exit_code": 0,
"transfer_session_revoked_and_cleaned": true,
@ -13,7 +13,7 @@
"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",
"pending_human_command": null,
"native_identity_recovery_proven": false,
"sources": [
{
@ -28,5 +28,6 @@
"path": "tests/test_privacyidea_offhost_recovery.py",
"sha256": "ea977b0d3368fb14f6a7a252c24cc5cb9915022afa3dca61c80952e7489bd0e5"
}
]
],
"execution_receipt": "privacyidea-database-drill-r1.json"
}

View file

@ -0,0 +1,15 @@
{
"observed_at": "2026-10-15T22:24:49.717396+00:00",
"receipt": "privacyidea-database-drill-r1.json",
"receipt_sha256": "c72dc3ababcc3ef3d7485a434128daf71e0ee58a8118e633fb09fed516f4b980",
"source_and_transfer_binding_verified": true,
"retained_ciphertext_digest_rechecked": true,
"operator_reported_success": true,
"independent_custody_basis": "Operator reports independently held railiance-backup key; attended temporary-key drill decrypted the fetched ciphertext. Agent never received private key material.",
"docker_nk49_container_inventory_empty": true,
"full_identity_recovery_proven": false,
"operator_reports_no_known_full_identity_bundle": true,
"next_work_authorized": "Prepare missing backup coverage; no blanket production restore or credential rotation.",
"coverage_preparation": "identity-backup-preparation-r1.json",
"operator_coverage_decision_id": "ed20023f-0168-421d-be86-460391339876"
}

View file

@ -0,0 +1,106 @@
{
"schema": "netkingdom.identity-backup-preparation.v1",
"status": "prepared_not_admitted",
"observed_at": "2026-10-15T22:32:41.149409+00:00",
"cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786",
"profile_sha256": "176c1f2f9111c2c51503212deb162274609d5e53bfe03d7319e19e44c5e09500",
"source_sha256": "cd5f3067d28b4c7cc93ed08b8cf3869218bdecf7c54b5dffed22d697de71dbb1",
"bindings": [
{
"namespace": "sso",
"deployment": "lldap",
"uid": "743a86bd-5cb0-4ca0-9c56-a3438f4746a8",
"generation": 7,
"pvc_bindings": [
{
"name": "lldap-data",
"uid": "0a06b65a-e7b3-42d3-916e-e74dbe5fc7da"
}
],
"secret_names_only": [
"lldap-secrets"
],
"configmap_names_only": []
},
{
"namespace": "sso",
"deployment": "authelia",
"uid": "dc40a925-2306-4a3b-89c0-e714187503c3",
"generation": 28,
"pvc_bindings": [
{
"name": "authelia-data",
"uid": "86826554-b014-4df4-8b28-866aa64f6413"
}
],
"secret_names_only": [
"authelia-secrets",
"authelia-smtp"
],
"configmap_names_only": [
"authelia-config"
]
},
{
"namespace": "sso",
"deployment": "keycape",
"uid": "99ddd83c-cb3f-4847-bcf8-35f1aa87627f",
"generation": 65,
"pvc_bindings": [
{
"name": "keycape-authentication-policy",
"uid": "423f558f-8c11-4edf-956b-243559df4a8e"
}
],
"secret_names_only": [
"keycape-approval-engine-operator-client",
"keycape-config",
"keycape-factor-read",
"keycape-informed-decision-sitting-requester-client",
"keycape-rapp-qonto-client",
"keycape-secrets-engine-approval-client",
"keycape-secrets-engine-openbao-client",
"keycape-secrets-engine-requester-client"
],
"configmap_names_only": []
},
{
"namespace": "mfa",
"deployment": "privacyidea",
"uid": "58c7f96d-61cb-4dd4-bca2-54661c0ac375",
"generation": 11,
"pvc_bindings": [
{
"name": "privacyidea-data",
"uid": "65650fc1-02d8-4c0f-84fc-e0015c8f3ed9"
},
{
"name": "privacyidea-logs",
"uid": "39fa90d9-bc54-4a03-a0d6-ae85165e0cb9"
}
],
"secret_names_only": [
"privacyidea-config"
],
"configmap_names_only": [
"factor-recovery-01119187d90d7b76",
"keycape-onboarding-9465e5e0b98f7986",
"privacyidea-cfg"
]
}
],
"secret_values_read": false,
"backup_captured": false,
"uploaded": false,
"runtime_changed": false,
"credential_grant_created": false,
"owner_admission_required": [
"Railiance owner accepts exact read/capture executor, immutable helper images, PVC access and task-scoped caller attribution; no production stop/restart inferred",
"NetKingdom/service owners accept full asset manifest, original key preservation, consistency interval, bounded recovery environment and native acceptance",
"Platform/reef-storage admits both new archive prefixes, immutable create/readback, scoped publisher custody and current quota/headroom; current application archive profiles do not include net-kingdom",
"Resolve every referenced credential through encrypted recovery or an independent owner reissue path; no undocumented grant propagation",
"Implement and test capture/sealing/transfer adapters against this new profile before live execution; existing bootstrap tar checker is insufficient",
"Retrieve by immutable object/version from each admitted provider; digest check, attended independent-key decryption, native service recovery, failed-key case and verified cleanup",
"Only then enable recurring capture/alerts and measured retention; preserve T02 legacy provider-credential and deferred-rotation boundaries"
]
}

View file

@ -2,7 +2,7 @@
"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,
"candidate_key_match_verified": true,
"storage_share": {
"inventory": "storage-share-inventory-r1.json",
"status": "metadata_inventory_complete",
@ -14,7 +14,9 @@
"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
"transfer_session_self_revocation_and_cleanup_confirmed": true,
"candidate_decryption_and_database_restore_verified": true,
"database_drill_receipt": "privacyidea-database-drill-r1.json"
},
"scaleway": {
"inventory": "scaleway-inventory-r1.json",
@ -38,5 +40,6 @@
"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
"production_mutations_performed": false,
"private_key_received_or_read_scope": "Agent custody: false; attended human recovery child used the temporary explicit key."
}

View file

@ -0,0 +1,28 @@
{
"schema": "netkingdom.privacyidea-database-drill.v1",
"status": "isolated_database_drill_passed",
"started_at": "2026-10-15T22:18:55.858354+00:00",
"source_sha256": "90f454c87998d7f5d625e3c6656c3418f8f56275dc1879a9d19ed605431b94a4",
"production_contacted": false,
"secret_values_exported": false,
"native_identity_recovery_proven": false,
"token_decryption_proven": false,
"audit_signature_continuity_proven": false,
"full_identity_file_coverage_proven": false,
"container_cleanup_verified": true,
"temporary_plaintext_cleanup_verified": true,
"ciphertext_sha256": "cc81da442755928171d2c51bce93eda7de4392a0a1aa9d0ffb83a25ba34e5ead",
"registered_recipient_match": true,
"wrong_key_refused": true,
"offhost_ciphertext_decryption_verified": true,
"image": "postgres@sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70",
"image_id": "sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70",
"pg_restore_version": "pg_restore (PostgreSQL) 16.14 (Debian 16.14-1.pgdg13+1)",
"aggregate_row_counts": {
"token": 5,
"resolver": 2,
"realm": 2
},
"database_restore_and_readback_passed": true,
"finished_at": "2026-10-15T22:19:05.279736+00:00"
}

View file

@ -1,13 +1,13 @@
{
"workplan": "NK-WP-0049",
"observed_at": "2026-10-15T22:08:27.470032+00:00",
"status": "source_prepared_pending_owner_inputs_and_native_acceptance",
"observed_at": "2026-10-15T22:24:49.717396+00:00",
"status": "offhost_privacyidea_database_recovered_full_identity_coverage_open",
"backup_destinations_operator_reported": [
"Scaleway",
"Storage Share"
],
"exact_offhost_artifact_confirmed": true,
"independently_held_matching_key_confirmed": false,
"independently_held_matching_key_confirmed": true,
"recovery_key_or_backup_contents_retrieved": true,
"legacy_token_secret_present": true,
"legacy_trigger_secret_present": true,
@ -18,15 +18,14 @@
"verification": "source-verification.json",
"ceremony": "../../bootstrap-recovery-ceremony.md",
"outstanding": [
"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",
"Prepare and admit missing encrypted identity data/key/config backup coverage; operator confirms no known full identity bundle",
"Retrieve new off-host bundle and prove original token decryption, audit continuity, directory and issuer recovery with safe negative cases and cleanup",
"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,
"key_matches_selected_ciphertext_verified": true,
"offsite_discovery": "offsite-discovery-summary.json",
"privacyidea_database_ciphertext_identified": true,
"required_full_asset_coverage_confirmed": false,
@ -36,5 +35,14 @@
"ciphertext_fetched": true,
"transfer_receipt": "privacyidea-transfer-r1.json",
"prepared_database_drill": "../../bootstrap-recovery-ceremony.md",
"age_private_key_retrieved_by_agent": false
"age_private_key_retrieved_by_agent": false,
"offhost_database_restore_proven": true,
"database_drill_receipt": "privacyidea-database-drill-r1.json",
"database_drill_review": "database-drill-review.json",
"operator_reports_no_known_full_identity_bundle": true,
"identity_backup_profile": "../../../tools/bootstrap-recovery/identity-backup-profile.json",
"identity_coverage_preparation": "identity-backup-preparation-r1.json",
"coverage_preparation_status": "prepared_not_admitted",
"coverage_preparation_tests_passed": 14,
"coverage_decision_id": "ed20023f-0168-421d-be86-460391339876"
}