Restore Under Attack¶
Do not repair repository state automatically during an incident. First preserve evidence, then choose the trusted v2 anchor, then restore with the narrowest credentials practical.
Assume¶
- object storage can delay, delete, replay, or hide objects
- the Kubernetes anchor can be stale or maliciously advanced
- a backup pod may have written bad new state
- old retained versions may exist even when latest listings mislead
- in-cluster logs may be incomplete
Required Inputs¶
Before serving restore traffic, identify or recover:
- repository ID
- public repository salt
- wrapping-key source for the keyring envelope
- accepted v2 anchor sequence, commit key, commit object version ID when available, commit body digest, signing key ID, and format-root reference
- format-bound keyring-envelope reference
- backend endpoint, bucket, and prefix
- object-store audit events where available
Backend credentials alone are not sufficient recovery material.
1. Stop New Writes¶
Stop or isolate the read-write gateway before investigating.
kubectl -n <gateway-namespace> scale deployment/<gateway-deployment> --replicas=0
If the deployment must remain online for restore traffic, switch it to
restore-readonly before exposing it to restore clients.
helm upgrade <release> charts/rs3-gateway \
--namespace <gateway-namespace> \
--reuse-values \
--set-string gateway.mode=restore-readonly
Do not switch back to read-write until the trusted commit chain and anchor state
are understood.
2. Preserve Evidence¶
Capture the anchor, gateway logs, Velero resources, and storage evidence before cleanup.
kubectl -n <anchor-namespace> get lease <anchor-name> -o yaml > anchor.yaml
kubectl -n <gateway-namespace> logs deployment/<gateway-deployment> --since=24h > gateway.log
kubectl -n velero get backups,restores,podvolumebackups,podvolumerestores -o yaml > velero.yaml
Preserve object-store audit logs outside the affected account when available. Do not include plaintext paths or Kubernetes secrets in shared artifacts.
3. Prefer a Trusted Restore Bundle¶
From a healthy cluster or regular operations job, export a trusted bundle and store it outside the object-store account.
cargo run -p rs3-server -- export-restore-bundle --format json > rs3-restore-bundle.json
Machine-readable commands reserve stdout for the report or bundle payload and write logs to stderr. Do not redirect stderr into preserved JSON artifacts.
Export a new bundle after each successful backup window or at least before
declaring a repository trial ready for incident restore. The bundle contains
public but integrity-sensitive restore metadata, not wrapping-key material.
Store it outside the object-store account and outside the cluster whose Lease it
may need to recreate. Export prints offline_signature_payload_hex; sign those
canonical bytes with an offline Ed25519 recovery key and store the resulting hex
signature in offline_signature before production import. Verify the preserved
bundle before anchor import:
RS3_BACKEND_ENDPOINT=s3 \
RS3_BACKEND_BUCKET=<bucket> \
RS3_BACKEND_PREFIX=<repository-prefix> \
RS3_REPOSITORY_ID=<repository-id> \
RS3_REPOSITORY_SALT_HEX=<repository-salt-hex> \
RS3_RECOVERY_PUBLIC_KEY=ed25519:<recovery-public-key-hex> \
cargo run -p rs3-server -- verify-bundle \
--bundle-file rs3-restore-bundle.json \
--min-sequence <external-floor-sequence> \
--wrapping-key-hex-file <wrapping-key-hex-file>
If a fresh cluster is missing the Kubernetes Lease, import the trusted v2 anchor after configuring the same repository ID, salt, wrapping-key source, backend, and retention settings.
cargo run -p rs3-server -- import-v2-anchor \
--bundle-file rs3-restore-bundle.json \
--min-sequence <external-floor-sequence>
verify-bundle does not write storage or anchors. import-v2-anchor repeats
the named signed commit-chain, format-root, and keyring-envelope checks before
writing the missing anchor. Anchor import reads repository, anchor, format-root,
and recovery-signature fields only from --bundle-file. Production import also
requires RS3_RECOVERY_PUBLIC_KEY and refuses a bundle whose anchor sequence is
below the operator-supplied --min-sequence.
It also lists stored v2 commits and refuses to import when it sees a higher
commit sequence than the bundle names. Use --force-rollback only after an
explicit rollback review accepts stranding those newer commits.
4. If No Bundle Exists, Stop¶
When the old Lease is gone and no trusted v2 bundle exists, do not promote backend state by listing storage. A malicious backend can hide newer valid commits. Escalate to an operator-held authority, offline audit trail, or external anchor record before recreating the Lease.
5. Verify Before Restore¶
Verify the trusted v2 anchor before using it for restore. The
rs3 verify-bundle command and the import path check the signed commit
chain, format root, and keyring envelope. After the gateway starts from the
recovered anchor, run the restore client and verify restored application bytes
before declaring the incident restore successful.
6. Restore Read-Only¶
Patch Velero's backup storage location to read-only and serve restore traffic
through restore-readonly.
kubectl -n velero patch backupstoragelocations.velero.io/default \
--type=merge \
-p '{"spec":{"accessMode":"ReadOnly"}}'
helm upgrade <release> charts/rs3-gateway \
--namespace <gateway-namespace> \
--reuse-values \
--set-string gateway.mode=restore-readonly
Run the restore into an isolated target where practical.
velero --namespace velero restore create <restore-name> \
--from-backup <backup-name> \
--wait
Velero may report PartiallyFailed if the only failure is denied upload of
restore result artifacts after data restore. Accept that status only when all of
the following are true:
- Velero logs show restore-result artifact upload attempts denied by
restore-readonly - pod-volume restore completed
- restored bytes or application checks verify
- gateway/backend counters show no writes during restore
Any other restore error is a failed restore.
Decision Table¶
| Observation | Action |
|---|---|
| Signature fails | Reject commit chain. |
| Parent chain broken | Reject unless it is a trusted snapshot root. |
| Sequence lower than trusted anchor | Treat as rollback. |
| Digest differs from anchor | Fail closed and investigate. |
| Anchor unavailable | Do not accept newer-looking storage state silently. |
| Backend contains commits newer than anchor | Investigate anchor rollback or missed anchor advance. |
| Backend lacks the anchored commit | Treat as unavailable or tampered. |
| Anchor missing but backend objects exist | Require a trusted recovery bundle; do not trust storage alone. |
Break Glass¶
Break-glass restore, if implemented, must require:
- explicit operator command
- selected commit key and sequence
- audit reason or ticket
- read-only backend credentials where possible
- no automatic anchor repair
Its job is data recovery, not making ambiguous state look healthy.