feat(recovery): prepare full identity backup and bootstrap handoff
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

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

View file

@ -239,10 +239,139 @@ 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.
The source implementation below supersedes the earlier adapter-preparation gap.
The Scaleway primary/Storage Share secondary proposal above is deferred: the
initial rollout implements Storage Share only. Neither destination has yet
received a full identity bundle. Owner admission and actual recovery remain open.
## Implemented identity backup packet — 2026-10-16
The new capture, sealing, upload/readback, independent retrieval, archive/native
PostgreSQL verification, separate retention and node installer are implemented
under `tools/bootstrap-recovery/identity-*.py`, `capture-identity.py`,
`retrieve-identity.py`, `verify-identity*.py` and `install-identity-backup.py`.
Source profile: `identity-backup-profile.json`. No production keys were collected,
no new credential grants were made and no timers were installed in this source
checkpoint. Decision `40bfae40-6108-4e51-8613-073339f5345f` remains pending.
The exact new owner requests are:
- [CCR-2026-0048 publisher](../../railiance-platform/credential-change-requests/CCR-2026-0048-net-kingdom-backup-publisher.yaml): a read/create share for only `Backup/railiance-backups/net-kingdom-identity/`, delivered through a dedicated 15-minute, audience/namespace/SA-bound ESO reader.
- [CCR-2026-0049 retention](../../railiance-platform/credential-change-requests/CCR-2026-0049-net-kingdom-backup-retention.yaml): a separate read/delete share for that same folder and separate process. Compromise of this credential can delete points in the folder; it must not be given to the publisher process.
Both require Platform, NetKingdom and Backup-share owner review. Only the scoped
share tokens enter exact new workload KV paths and ESO-managed Secrets. Neither
the operator account password nor the age private key is delivered to the job.
The setup helper refuses proposed/unapproved or hash-mismatched CCRs. Native
positive and wrong-SA/namespace/audience probes must prove the admitted reader
boundary, followed by equality to the exact ESO delivery; temporary readers are
revoked. These are new grants, so prior approval to prepare the missing coverage
does not count as completed credential-owner admission.
The initial provider is Storage Share only. Ciphertext and safe receipt are
created once under a unique generation name, read back using strong ETags and
checked by size/SHA256. A 512 MiB refusal ceiling and two GiB fresh provider
headroom apply; unsupported public-share quota fails closed. Public DAV behavior
and permission denials are checked with disposable public-recipient fixture
ciphertext before accepting the grants. No Scaleway credential or another
workload publisher is reused. Nextcloud documents the public-share DAV interface
in its [official WebDAV guide](https://docs.nextcloud.com/server/25/user_manual/en/files/access_webdav.html).
Two temporary helper Pods mount only reviewed PVCs read-only, have no service
account token, use pinned images, private tmpfs scratch and deny-all network
policies, then are deleted with exact UID preconditions. Kubernetes network
allows combine, so `identity-isolation.py` first prepares two guarded patches to
exclude the capture label from the existing namespace-wide DNS allows. Every
peer/port and ordinary service selector match is preserved. Plans bind the
current cluster/policy UIDs, resource versions and full original specs, and
refuse an existing pod using the capture label. Capture refuses any remaining
matching allow policy, source drift or unknown helper cleanup outcome.
Capture uses native SQLite online backups and a fresh native PostgreSQL dump;
all required current Secrets/configuration and original key pairs are sealed
with the public recipient. The manifest and plaintext hashes are inside the
ciphertext. privacyIDEA service logs, historical LLDAP/Authelia/PI `backups/`
subtrees and Authelia notification.txt are omitted; the audit database is in the
linked PostgreSQL dump, and the full KeyCape policy/receipt tree is preserved.
There is no claim of a globally atomic multi-service snapshot. Drift/partial
cleanup stops future runs and requires exact owner reconciliation.
### Ordered live rollout after exact packet approval
1. Record all required owner reviews in CCR-2026-0048/0049 and bind their exact file hashes. Run `identity-credential-setup.py publisher|retention --ccr-sha256 HASH --receipt PRIVATE_NEW_PATH` through the attended Platform-admin owner conduit. Never inspect private helper output or copy operator credentials into the runtime. Verify actual provider permissions, initial CAS-only KV publication, exact policy/role, agent high-risk denials and revoked fixture readers.
2. Apply only `identity-custody.json`; wait for its two SecretStores/ExternalSecrets to be Ready. Run `verify-identity-delivery.py publisher|retention --ccr-sha256 HASH --kubeconfig REVIEWED_PATH --receipt PRIVATE_NEW_PATH` through that same owner conduit. Resolve any native denial before accepting delivery; do not set confirmed-claim flags from fixtures.
3. Generate a fresh `identity-isolation.py plan --kubeconfig REVIEWED_PATH --receipt PRIVATE_PLAN`; review it, server-dry-run it, then `apply --plan PRIVATE_PLAN --plan-sha256 HASH` using the same explicit kubeconfig and a fresh receipt. Each patch tests UID/resourceVersion/spec. Regenerate stale plans rather than weakening the guards. Confirm the cluster's network policy enforcement through its owning layer.
4. Render `install-identity-backup.py render --decision-id 40bfae40-6108-4e51-8613-073339f5345f --receipt PRIVATE_PACKET`. Transfer only the reviewed runtime files in that packet plus the packet to the admitted node. As root on railiance01, run installer `install --packet PRIVATE_PACKET --packet-sha256 HASH --receipt PRIVATE_NEW_PATH`. Installation verifies exact cluster/source hashes and unit syntax; timers remain disabled. Execute the packet's exact `first_capture_command`. All output receipts/state and encrypted copies stay under root-private `/var/lib/net-kingdom-backup`.
5. Retrieve the successful generation's safe off-site receipt from that private state, then run attended `retrieve-identity.py --source-receipt PRIVATE_NODE_RECEIPT --receipt PRIVATE_INDEPENDENT_TRANSFER` with the independently authorized Backup account via Platform custody. It re-downloads and binds the ciphertext by ETag/size/hash. The receipt names the new private local ciphertext path. A published index alone is not independent retrieval proof.
6. The human independently verifies the downloaded bundle using the hidden-key command below. Review the actual receipt and exact source/artifact/hash. Copy only this safe receipt to private node state; activate with `install-identity-backup.py activate --accepted-receipt PRIVATE_ACTUAL_RECOVERY --receipt PRIVATE_NEW_PATH`. Activation binds the actual artifact to its local successful off-site receipt, pins that generation, checks health and enables the timers.
7. Confirm both active timers, last success, retained protection and safe Hub progress. Full NK49 native identity/caller and legacy-credential acceptance remains separately open.
All actions accept source/target guards; flags do not create authority. The
installer runs as the already admitted node owner. If installation or a grant
partly fails, stop and reconcile its safe receipt/exact resources before retry.
Do not rotate keys, change existing service credentials or remove older backups
as an implicit repair. For rollback, disable the two exact new timers first,
retain the verified ciphertext and restore receipt, then have the owners revoke
only these newly issued shares/roles and remove their delivery resources after
confirming no consumer remains. Never delete existing unrelated backup jobs.
### Independent full archive verification and bootstrap connection
The key is entered only in the operator's hidden terminal, using the independently
held backup key already checked against this public recipient:
```bash
sso-mfa/bootstrap/sops-custody-unlock.sh \
--expected-recipient age1zvryunvjhvpkmasskauga2heeg0ztnte9ymgppvjge36ekumk50syr3tsz \
--prompt -- python3 tools/bootstrap-recovery/verify-identity.py \
--transfer-receipt /PRIVATE/independent-transfer.json \
--receipt /PRIVATE/new-full-archive-recovery.json
```
With the reviewed sibling `railiance-bootstrap` checkout, the equivalent handoff
is `bash ../railiance-bootstrap/bin/railiance identity-recovery` with the same
transfer/receipt arguments, run inside that hidden-key wrapper. Its
[bootstrap sequence](../../railiance-bootstrap/docs/identity-recovery.md) links
independent custody and isolated verification to replacement-host/layer handoff.
The wrapper delegates to this verifier and never imports into production.
The complete manifest, SQLite integrity, original RSA pairs, wrong-key refusal,
native isolated PostgreSQL restore and verified cleanup are the archive gate for
recurring backups. A successful archive gate does not prove existing-token
plaintext/MFA, historical/new audit signatures, existing-user login or issuer
consumer flows. Those require separately reviewed isolated service acceptance
before T03 and NK49 can close. The verifier deletes its plaintext; it is not a
production importer.
Today's attended retrieval helper requires healthy OpenBao. For total custody
loss, independently held provider access and a separately reviewed offline
retrieval receipt procedure are still required; that offline adapter is not
implemented. No bare-VM unattended restore is claimed. The backup public key is
not interchangeable with the separate bootstrap encryption recipient.
### Recurrence, retention and health
After the first actual archive gate, the prepared root unit runs daily at
03:45 UTC with up to five minutes jitter. Retention runs in separate processes
before/after capture, protecting newest, seven distinct daily points, two
distinct weekly points and the independently recovered pinned generation.
Unknown/unindexed objects are never candidates. Both pinned and newest points
are re-downloaded and hashed before any expiry; each candidate is checked and
deleted conditionally by ETag. Partial/unknown expiry stops subsequent expiry
until reconciled. A matching guarded local-cache prune preserves protected
points and leaves unknown/failed runs for owner inspection. There is no claim
that 10 GiB is sufficient indefinitely: measured quota/headroom is checked each
run and an excess refuses new capture rather than deleting a protected point.
An hourly node health check reports missing/older-than-25-hour backups or partial
failure to State Hub, with durable local nonsecret outbox/replay. This host-local
check cannot detect or report a dead host; external outage monitoring remains a
separate gap. The daily backup RPO is explicit and distinct from the one-hour
ecosystem coordination frame. Timers are currently disabled/uninstalled.
Find this functionality through the owning registry entry
`capability.security.bootstrap-recovery` (keywords backup, restore,
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.

View file

@ -0,0 +1,393 @@
{
"schema": "netkingdom.identity-capture-plan.v1",
"status": "requires_owner_execution_admission",
"created_at": "2026-10-15T23:33:14.370426+00:00",
"run_id": "35ffd825063d",
"binding": {
"schema": "netkingdom.identity-backup-preparation.v1",
"status": "prepared_not_admitted",
"observed_at": "2026-10-15T23:33:13.858371+00:00",
"cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786",
"profile_sha256": "dff567f594826628544bb2252c77ccb8daf8a34fd2095026663cc948ffeff63a",
"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": [
"Approve exact CCR-2026-0048/0049 grants and node runtime packet; three required owner reviews, confirmed native SA/namespace/audience boundaries before acceptance",
"Exclude helper capture label from existing broad DNS allow policies; validate read-only helper manifests and cluster/PVC/source bindings before private capture",
"Preserve every referenced credential encrypted with original keys; fresh native SQLite/PG snapshots and exact manifest, with documented omissions and no global atomicity claim",
"Create fresh ciphertext and exact Storage Share object, read back ciphertext and receipt by strong ETag/hash, independently retrieve using operator custody",
"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"
]
},
"kubeconfig": "/home/worsch/.kube/config",
"sources": [
{
"path": "capture-identity.py",
"sha256": "47dca7c4dc96fa7eb9fb02c81fca9ca8dc5281aac918979eb7367148168ba705"
},
{
"path": "identity-files.py",
"sha256": "d0063a9eedd3610e3d45bd2d834aae83cff74e4b3504d138b3548f427e89331d"
},
{
"path": "identity-bundle.py",
"sha256": "491f6f9f074dd0f73a26b16f32d81dbf70e049569e65c9dcee1cbcd1305dc8dc"
},
{
"path": "prepare-identity-coverage.py",
"sha256": "cd5f3067d28b4c7cc93ed08b8cf3869218bdecf7c54b5dffed22d697de71dbb1"
},
{
"path": "identity-backup-profile.json",
"sha256": "dff567f594826628544bb2252c77ccb8daf8a34fd2095026663cc948ffeff63a"
}
],
"database": {
"namespace": "databases",
"cluster": "net-kingdom-pg",
"cluster_uid": "0cd04a7c-a183-48ac-975a-9f244ea82c73",
"pod": "net-kingdom-pg-1",
"pod_uid": "0404c66b-338b-4446-a767-622d248ab0d3",
"database": "privacyidea_db"
},
"create_resources": [
{
"apiVersion": "networking.k8s.io/v1",
"kind": "NetworkPolicy",
"metadata": {
"name": "nk49-capture-35ffd825063d",
"namespace": "sso"
},
"spec": {
"podSelector": {
"matchLabels": {
"net-kingdom/capture": "35ffd825063d"
}
},
"policyTypes": [
"Ingress",
"Egress"
],
"ingress": [],
"egress": []
}
},
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "nk49-capture-35ffd825063d",
"namespace": "sso",
"labels": {
"net-kingdom/capture": "35ffd825063d"
}
},
"spec": {
"restartPolicy": "Never",
"activeDeadlineSeconds": 900,
"automountServiceAccountToken": false,
"securityContext": {
"runAsUser": 0,
"runAsGroup": 0
},
"containers": [
{
"name": "capture",
"image": "ghcr.io/gpappsoft/privacyidea-docker@sha256:af7841adad262f129e0c1d4f553af13f21cb2f4dc713533f316cfe43ed0b4473",
"imagePullPolicy": "IfNotPresent",
"command": [
"python3",
"-c",
"import time; time.sleep(900)"
],
"securityContext": {
"allowPrivilegeEscalation": false,
"readOnlyRootFilesystem": true,
"capabilities": {
"drop": [
"ALL"
],
"add": [
"DAC_OVERRIDE"
]
}
},
"resources": {
"requests": {
"cpu": "50m",
"memory": "128Mi"
},
"limits": {
"cpu": "1",
"memory": "2Gi"
}
},
"volumeMounts": [
{
"name": "lldap",
"mountPath": "/source/lldap",
"readOnly": true
},
{
"name": "authelia",
"mountPath": "/source/authelia",
"readOnly": true
},
{
"name": "keycape",
"mountPath": "/source/keycape",
"readOnly": true
},
{
"name": "scratch",
"mountPath": "/scratch"
}
]
}
],
"volumes": [
{
"name": "lldap",
"persistentVolumeClaim": {
"claimName": "lldap-data",
"readOnly": true
}
},
{
"name": "authelia",
"persistentVolumeClaim": {
"claimName": "authelia-data",
"readOnly": true
}
},
{
"name": "keycape",
"persistentVolumeClaim": {
"claimName": "keycape-authentication-policy",
"readOnly": true
}
},
{
"name": "scratch",
"emptyDir": {
"medium": "Memory",
"sizeLimit": "512Mi"
}
}
]
}
},
{
"apiVersion": "networking.k8s.io/v1",
"kind": "NetworkPolicy",
"metadata": {
"name": "nk49-capture-35ffd825063d",
"namespace": "mfa"
},
"spec": {
"podSelector": {
"matchLabels": {
"net-kingdom/capture": "35ffd825063d"
}
},
"policyTypes": [
"Ingress",
"Egress"
],
"ingress": [],
"egress": []
}
},
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "nk49-capture-35ffd825063d",
"namespace": "mfa",
"labels": {
"net-kingdom/capture": "35ffd825063d"
}
},
"spec": {
"restartPolicy": "Never",
"activeDeadlineSeconds": 900,
"automountServiceAccountToken": false,
"securityContext": {
"runAsUser": 0,
"runAsGroup": 0
},
"containers": [
{
"name": "capture",
"image": "ghcr.io/gpappsoft/privacyidea-docker@sha256:af7841adad262f129e0c1d4f553af13f21cb2f4dc713533f316cfe43ed0b4473",
"imagePullPolicy": "IfNotPresent",
"command": [
"python3",
"-c",
"import time; time.sleep(900)"
],
"securityContext": {
"allowPrivilegeEscalation": false,
"readOnlyRootFilesystem": true,
"capabilities": {
"drop": [
"ALL"
],
"add": [
"DAC_OVERRIDE"
]
}
},
"resources": {
"requests": {
"cpu": "50m",
"memory": "128Mi"
},
"limits": {
"cpu": "1",
"memory": "2Gi"
}
},
"volumeMounts": [
{
"name": "privacyidea",
"mountPath": "/source/privacyidea",
"readOnly": true
},
{
"name": "scratch",
"mountPath": "/scratch"
}
]
}
],
"volumes": [
{
"name": "privacyidea",
"persistentVolumeClaim": {
"claimName": "privacyidea-data",
"readOnly": true
}
},
{
"name": "scratch",
"emptyDir": {
"medium": "Memory",
"sizeLimit": "512Mi"
}
}
]
}
}
],
"service_changes": false,
"credential_grants": false,
"matching_network_allow_policies": [
{
"namespace": "sso",
"name": "allow-egress-dns",
"uid": "d2a1bd49-7035-437b-9b8e-e9e66b203365",
"spec_sha256": "c22e3a30891f0fa3cdcde97d26c91a09d11a8e94f7dfc66641d8be1cbd976f15"
},
{
"namespace": "mfa",
"name": "allow-egress-dns",
"uid": "1d23ed08-bb6f-4959-b72e-3f03af40f136",
"spec_sha256": "c22e3a30891f0fa3cdcde97d26c91a09d11a8e94f7dfc66641d8be1cbd976f15"
}
],
"network_policy_enforcement_requires_owner_confirmation": true,
"offsite_delivery_included": false,
"global_atomic_snapshot_claimed": false,
"finished_at": "2026-10-15T23:33:14.927800+00:00"
}

View file

@ -0,0 +1,65 @@
{
"resources": [
{
"namespace": "sso",
"kind": "NetworkPolicy",
"name": "nk49-capture-preflight001",
"server_dry_run_passed": true
},
{
"namespace": "sso",
"kind": "Pod",
"name": "nk49-capture-preflight001",
"server_dry_run_passed": true
},
{
"namespace": "mfa",
"kind": "NetworkPolicy",
"name": "nk49-capture-preflight001",
"server_dry_run_passed": true
},
{
"namespace": "mfa",
"kind": "Pod",
"name": "nk49-capture-preflight001",
"server_dry_run_passed": true
},
{
"namespace": "sso",
"kind": "ServiceAccount",
"name": "net-kingdom-backup-publisher-eso",
"server_dry_run_passed": true
},
{
"namespace": "sso",
"kind": "SecretStore",
"name": "net-kingdom-backup-publisher",
"server_dry_run_passed": true
},
{
"namespace": "sso",
"kind": "ExternalSecret",
"name": "net-kingdom-backup-publisher",
"server_dry_run_passed": true
},
{
"namespace": "sso",
"kind": "ServiceAccount",
"name": "net-kingdom-backup-retention-eso",
"server_dry_run_passed": true
},
{
"namespace": "sso",
"kind": "SecretStore",
"name": "net-kingdom-backup-retention",
"server_dry_run_passed": true
},
{
"namespace": "sso",
"kind": "ExternalSecret",
"name": "net-kingdom-backup-retention",
"server_dry_run_passed": true
}
],
"live_resources_created": false
}

View file

@ -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"
}
]
}
}
]
}
]
}

View file

@ -0,0 +1,27 @@
{
"status": "server_dry_run_passed",
"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"
}
]
}

View file

@ -0,0 +1,37 @@
{
"schema": "netkingdom.identity-implementation-review.v1",
"reviewed_at": "2026-10-15T23:36:41.132816+00:00",
"focused_tests": {
"command": "env NK49_RUN_DISPOSABLE_POSTGRES_TEST=1 python3 -m pytest -q tests/test_identity_bundle.py tests/test_identity_offsite.py tests/test_identity_backup_preparation.py",
"passed": 51,
"duration_seconds": 14.78,
"synthetic_data_only": true,
"real_age_and_native_postgresql_fixture": true
},
"systemd_analyze_verify_exit": 0,
"helper_and_delivery_server_dry_run_count": 10,
"dns_exclusion_server_dry_run_passed": true,
"owner_ccr_validation_errors": 0,
"owner_ccr_bound_claim_confirmation_pending": true,
"live_grants_made": false,
"actual_identity_capture_performed": false,
"identity_timers_installed": false,
"first_actual_full_archive_recovery_pending": true,
"full_native_identity_recovery_proven": false,
"pending_decision": "40bfae40-6108-4e51-8613-073339f5345f",
"knowledge": {
"version": "0.2.6",
"digest": "8d1d6a9d4d07fec7b619618de7dfe2be2254c1790f85f4198c7fe19db9751c02",
"source_status": "withheld_orientation_only",
"source_reviewed": "SECURITY-COMPANION.md"
},
"cross_repo_handoff": "railiance-bootstrap/bin/railiance identity-recovery; isolated verifier delegation only",
"capability": "capability.security.bootstrap-recovery",
"capability_vector": "D3 / A2 / C1 / R0",
"limitations": [
"No full MFA/token/audit/directory/issuer authentication proven",
"Total-custody-loss offline retrieval adapter remains open",
"No Scaleway second full identity copy",
"Host-local freshness reporting cannot detect host loss"
]
}

View file

@ -0,0 +1,59 @@
{
"schema": "netkingdom.identity-runtime-install.v1",
"status": "prepared_not_installed",
"decision_ref": "40bfae40-6108-4e51-8613-073339f5345f",
"release_id": "17d1ae2d447a7882a0e581a4c61fe4f69f3fd499e39d563c984c3a65276abd08",
"release_directory": "/opt/net-kingdom-backup/releases/17d1ae2d447a7882a0e581a4c61fe4f69f3fd499e39d563c984c3a65276abd08",
"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": "491f6f9f074dd0f73a26b16f32d81dbf70e049569e65c9dcee1cbcd1305dc8dc"
},
{
"path": "tools/bootstrap-recovery/identity-runtime.py",
"sha256": "8b1fe1138e28ec5df7287ed6e207a026c0e73cf8a55a25773ae9a89e04c97cbf"
},
{
"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": "dff567f594826628544bb2252c77ccb8daf8a34fd2095026663cc948ffeff63a"
},
{
"path": "tools/bootstrap-recovery/prepare-identity-coverage.py",
"sha256": "cd5f3067d28b4c7cc93ed08b8cf3869218bdecf7c54b5dffed22d697de71dbb1"
},
{
"path": "tools/bootstrap-recovery/install-identity-backup.py",
"sha256": "94ae7a22fda1cce43b52a1553a3e9293a4ab0ca4bbec062dab7ed62839593bad"
},
{
"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/17d1ae2d447a7882a0e581a4c61fe4f69f3fd499e39d563c984c3a65276abd08/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/17d1ae2d447a7882a0e581a4c61fe4f69f3fd499e39d563c984c3a65276abd08/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/17d1ae2d447a7882a0e581a4c61fe4f69f3fd499e39d563c984c3a65276abd08/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/17d1ae2d447a7882a0e581a4c61fe4f69f3fd499e39d563c984c3a65276abd08/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/17d1ae2d447a7882a0e581a4c61fe4f69f3fd499e39d563c984c3a65276abd08/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
}

View file

@ -0,0 +1,82 @@
[
{
"path": "tools/bootstrap-recovery/capture-identity.py",
"sha256": "47dca7c4dc96fa7eb9fb02c81fca9ca8dc5281aac918979eb7367148168ba705"
},
{
"path": "tools/bootstrap-recovery/check-privacyidea-database.py",
"sha256": "90f454c87998d7f5d625e3c6656c3418f8f56275dc1879a9d19ed605431b94a4"
},
{
"path": "tools/bootstrap-recovery/fetch-privacyidea-backup.py",
"sha256": "233160e1206643a5cf7ad05141d733f0a75aff651190e27f77515a124ff0a59f"
},
{
"path": "tools/bootstrap-recovery/identity-bundle.py",
"sha256": "491f6f9f074dd0f73a26b16f32d81dbf70e049569e65c9dcee1cbcd1305dc8dc"
},
{
"path": "tools/bootstrap-recovery/identity-credential-setup.py",
"sha256": "9a754b72640cb3dac95fa3bbc74448be81eb96a38c9319228db1c7f967dbba14"
},
{
"path": "tools/bootstrap-recovery/identity-files.py",
"sha256": "d0063a9eedd3610e3d45bd2d834aae83cff74e4b3504d138b3548f427e89331d"
},
{
"path": "tools/bootstrap-recovery/identity-isolation.py",
"sha256": "88d1e13112a44c79571825af89d3fe5b01fa490cff2653badef4c62a45c844f1"
},
{
"path": "tools/bootstrap-recovery/identity-offsite.py",
"sha256": "e7c31fa9ee081961af873348ee3b4829a645d40c7dc7f1e22c564aa9ed01bc49"
},
{
"path": "tools/bootstrap-recovery/identity-retention.py",
"sha256": "a41ea4759f69c35548149fc8d0d5ace547f16cf9fe4c9dac10b119a4d0becdec"
},
{
"path": "tools/bootstrap-recovery/identity-runtime.py",
"sha256": "8b1fe1138e28ec5df7287ed6e207a026c0e73cf8a55a25773ae9a89e04c97cbf"
},
{
"path": "tools/bootstrap-recovery/install-identity-backup.py",
"sha256": "94ae7a22fda1cce43b52a1553a3e9293a4ab0ca4bbec062dab7ed62839593bad"
},
{
"path": "tools/bootstrap-recovery/inventory-scaleway.py",
"sha256": "a219df205708fa4ed44b763a418585d63c820c25434bc0fd14e805eb5c2f79d3"
},
{
"path": "tools/bootstrap-recovery/inventory-storage-share.py",
"sha256": "673f9b17fb2f3fef6fcdb5c50a2bcca69e3df3157a9e5582a2d995c6b87640b5"
},
{
"path": "tools/bootstrap-recovery/prepare-identity-coverage.py",
"sha256": "cd5f3067d28b4c7cc93ed08b8cf3869218bdecf7c54b5dffed22d697de71dbb1"
},
{
"path": "tools/bootstrap-recovery/retrieve-identity.py",
"sha256": "ec123d47a07305253570dacab7e103f8c193a23f20c9fc87466ea4a95ae0ec13"
},
{
"path": "tools/bootstrap-recovery/verify-bundle.py",
"sha256": "885b05a2011e711228e42e316bda70bbe0d97d481383d61aa8469d0f10452370"
},
{
"path": "tools/bootstrap-recovery/verify-identity-delivery.py",
"sha256": "9e2afdc94b5cf0a81b66a1fe8658e7853c0972e4e3593bd198dd2af5817b254f"
},
{
"path": "tools/bootstrap-recovery/verify-identity.py",
"sha256": "039864d0141b552887752bf17cfdcf1bef4bdc9126f946a9c0533e4fd54ebf4f"
},
{
"path": "tools/bootstrap-recovery/identity-backup-profile.json",
"sha256": "dff567f594826628544bb2252c77ccb8daf8a34fd2095026663cc948ffeff63a"
},
{
"path": "tools/bootstrap-recovery/identity-custody.json",
"sha256": "f8aee8487ed01d7940b82676bb4a5c8d61f51b6c598521b8b8a73588aaea4d4f"
}
]