feat(recovery): prepare full identity backup and bootstrap handoff
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
This commit is contained in:
parent
dca999222f
commit
994d11c869
29 changed files with 3277 additions and 29 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
{
|
||||
"schema": "netkingdom.identity-isolation.v1",
|
||||
"status": "prepared_not_applied",
|
||||
"kubeconfig": "/home/worsch/.kube/config",
|
||||
"source_sha256": "88d1e13112a44c79571825af89d3fe5b01fa490cff2653badef4c62a45c844f1",
|
||||
"policies": [
|
||||
{
|
||||
"namespace": "sso",
|
||||
"name": "allow-egress-dns",
|
||||
"uid": "d2a1bd49-7035-437b-9b8e-e9e66b203365",
|
||||
"patch": [
|
||||
{
|
||||
"op": "test",
|
||||
"path": "/metadata/uid",
|
||||
"value": "d2a1bd49-7035-437b-9b8e-e9e66b203365"
|
||||
},
|
||||
{
|
||||
"op": "test",
|
||||
"path": "/metadata/resourceVersion",
|
||||
"value": "3623833"
|
||||
},
|
||||
{
|
||||
"op": "test",
|
||||
"path": "/spec",
|
||||
"value": {
|
||||
"egress": [
|
||||
{
|
||||
"ports": [
|
||||
{
|
||||
"port": 53,
|
||||
"protocol": "UDP"
|
||||
},
|
||||
{
|
||||
"port": 53,
|
||||
"protocol": "TCP"
|
||||
}
|
||||
],
|
||||
"to": [
|
||||
{
|
||||
"namespaceSelector": {
|
||||
"matchLabels": {
|
||||
"kubernetes.io/metadata.name": "kube-system"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"podSelector": {},
|
||||
"policyTypes": [
|
||||
"Egress"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/spec/podSelector",
|
||||
"value": {
|
||||
"matchExpressions": [
|
||||
{
|
||||
"key": "net-kingdom/capture",
|
||||
"operator": "DoesNotExist"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"namespace": "mfa",
|
||||
"name": "allow-egress-dns",
|
||||
"uid": "1d23ed08-bb6f-4959-b72e-3f03af40f136",
|
||||
"patch": [
|
||||
{
|
||||
"op": "test",
|
||||
"path": "/metadata/uid",
|
||||
"value": "1d23ed08-bb6f-4959-b72e-3f03af40f136"
|
||||
},
|
||||
{
|
||||
"op": "test",
|
||||
"path": "/metadata/resourceVersion",
|
||||
"value": "3623822"
|
||||
},
|
||||
{
|
||||
"op": "test",
|
||||
"path": "/spec",
|
||||
"value": {
|
||||
"egress": [
|
||||
{
|
||||
"ports": [
|
||||
{
|
||||
"port": 53,
|
||||
"protocol": "UDP"
|
||||
},
|
||||
{
|
||||
"port": 53,
|
||||
"protocol": "TCP"
|
||||
}
|
||||
],
|
||||
"to": [
|
||||
{
|
||||
"namespaceSelector": {
|
||||
"matchLabels": {
|
||||
"kubernetes.io/metadata.name": "kube-system"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"podSelector": {},
|
||||
"policyTypes": [
|
||||
"Egress"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/spec/podSelector",
|
||||
"value": {
|
||||
"matchExpressions": [
|
||||
{
|
||||
"key": "net-kingdom/capture",
|
||||
"operator": "DoesNotExist"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue