Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02994-7685-7940-bf34-3555b8256018
178 lines
6.9 KiB
Markdown
178 lines
6.9 KiB
Markdown
---
|
|
id: RAIL-HO-WP-0012
|
|
type: workplan
|
|
title: "Close the encrypted S1 backup and recovery loop"
|
|
domain: financials
|
|
repo: railiance-infra
|
|
status: proposed
|
|
owner: codex
|
|
topic_slug: railiance
|
|
created: "2026-08-23"
|
|
updated: "2026-08-23"
|
|
related:
|
|
- RAIL-HO-WP-0011
|
|
---
|
|
|
|
# RAIL-HO-WP-0012 — encrypted S1 backup and recovery loop
|
|
|
|
## Goal
|
|
|
|
Turn the existing local `railiance-backup-s1` helper into a fail-closed,
|
|
scheduled, off-host, and restore-tested S1 recovery capability. Preserve the
|
|
repository boundary: this covers host operating-system configuration and
|
|
package-selection evidence only, not Kubernetes, database, Forgejo, or
|
|
application data.
|
|
|
|
This workplan does **not** authorize deployment of a systemd timer, access to an
|
|
age private key, an off-host upload, a live `/etc` restore, or deletion of a
|
|
retained artifact. Each live action retains its normal owner review and exact
|
|
operator approval.
|
|
|
|
## Current State and Risks
|
|
|
|
`tools/cmd/railiance-backup-s1` currently creates age-encrypted OS-configuration
|
|
and package-selection files under `/opt/backup/railiance/infra` and retains the
|
|
latest seven of each. It is not scheduled, has no off-host transfer, produces
|
|
no manifest or receipt, has no restoration tool or drill, duplicates an age
|
|
recipient in executable source, suppresses individual copy failures, and can
|
|
leave ambiguous partial output. The output is therefore a useful local helper,
|
|
not current proof of recoverability.
|
|
|
|
## Delivery Order
|
|
|
|
T01 → T02 → T03 establish a locally testable contract. T04 and T05 may proceed
|
|
after T02 but need separate deployment/custody review. T06 depends on all prior
|
|
tasks and an attended isolated drill.
|
|
|
|
## T01 — Define the S1 backup declaration and safety contract
|
|
|
|
```task
|
|
id: RAIL-HO-WP-0012-T01
|
|
status: todo
|
|
priority: high
|
|
```
|
|
|
|
Create a machine-readable declaration for included paths, optional paths,
|
|
artifact classes, public encryption recipients, retention, and excluded
|
|
higher-layer data. Validate that every selected path is absolute and bounded,
|
|
that no private-key or credential path can be selected, and that the recipient
|
|
contract cannot drift silently from the repository's approved public metadata.
|
|
|
|
**Done when:** the current intended S1 files resolve deterministically, unsafe
|
|
or relative paths fail closed, optional absence is explicit, and validation
|
|
requires neither root nor a decryption key.
|
|
|
|
## T02 — Produce atomic encrypted artifacts, manifests, and receipts
|
|
|
|
```task
|
|
id: RAIL-HO-WP-0012-T02
|
|
status: todo
|
|
priority: high
|
|
```
|
|
|
|
Refactor the backup helper around the T01 declaration. Check privileges before
|
|
creating the destination, stage output on the same filesystem, fail on an
|
|
unreadable required input, encrypt before publication, and atomically publish
|
|
only complete artifacts. Emit a metadata-only manifest and receipt containing
|
|
host identity, source revision, declaration digest, artifact class, encrypted
|
|
size/hash, completion time, and retention decision.
|
|
|
|
Exercise it against disposable fixture roots and output directories. Tests
|
|
must prove a failed collection or encryption cannot produce a passing receipt
|
|
or a final-named partial artifact.
|
|
|
|
**Done when:** a fixture backup is deterministic in membership, encrypted
|
|
before publication, fully described by redacted metadata, and failure-injection
|
|
tests leave no ambiguous success state.
|
|
|
|
## T03 — Add non-destructive inspection and isolated restore validation
|
|
|
|
```task
|
|
id: RAIL-HO-WP-0012-T03
|
|
status: todo
|
|
priority: high
|
|
```
|
|
|
|
Provide a restore command that defaults to inspection or extraction beneath an
|
|
explicit empty staging directory. It must verify receipt/manifest hashes before
|
|
decryption, suppress decrypted content from logs, reject path traversal and
|
|
links escaping the staging root, and compare restored membership and critical
|
|
file modes with the declaration.
|
|
|
|
Restoring over live `/etc` is outside this task and must remain impossible
|
|
without a separately designed exact-approval interface.
|
|
|
|
**Done when:** a fixture artifact restores into disposable storage, tampering
|
|
and unsafe members fail closed, no plaintext enters repository paths or logs,
|
|
and no default invocation can overwrite a host file.
|
|
|
|
## T04 — Define scheduling, freshness, and local retention
|
|
|
|
```task
|
|
id: RAIL-HO-WP-0012-T04
|
|
status: todo
|
|
priority: medium
|
|
```
|
|
|
|
Add source-controlled systemd service/timer units and Ansible deployment for a
|
|
bounded backup cadence. Define maximum acceptable age, failure state, disk
|
|
budget, atomic pruning, and an operator status interface. Keep timer deployment
|
|
separate from source validation and require explicit host-change approval.
|
|
|
|
**Done when:** unit rendering and calendar behavior test locally, stale/missing
|
|
backup status fails, pruning never crosses the declared backup directory, and
|
|
no timer is installed merely by running a verification command.
|
|
|
|
## T05 — Integrate the governed write-only off-host lane
|
|
|
|
```task
|
|
id: RAIL-HO-WP-0012-T05
|
|
status: wait
|
|
priority: high
|
|
```
|
|
|
|
Coordinate with `railiance-platform` through the existing
|
|
`railiance-backup-offsite-lane` route. Upload only completed encrypted
|
|
artifacts plus metadata manifests, use a host-local or workload-owned
|
|
credential projection, and record a non-secret remote object identity and
|
|
encrypted hash. Do not place WebDAV credentials or an age private key in this
|
|
repository, State Hub, command output, or agent prompts.
|
|
|
|
**Done when:** the owner accepts the exact projection and write contract, a
|
|
dry-run proves object naming and collision behavior, a controlled upload is
|
|
visible through owner-provided metadata, and local/off-host retention cannot
|
|
delete the only recoverable copy.
|
|
|
|
## T06 — Perform an attended isolated restore drill
|
|
|
|
```task
|
|
id: RAIL-HO-WP-0012-T06
|
|
status: wait
|
|
priority: high
|
|
```
|
|
|
|
Select a fresh off-host artifact by metadata, retrieve it through the governed
|
|
lane, and restore it into an isolated disposable filesystem. Prove manifest
|
|
integrity, expected membership and modes, usable SSH/fail2ban/UFW configuration
|
|
syntax, package-selection readability, and cleanup of all decrypted temporary
|
|
material. Do not restore over a live host in this drill.
|
|
|
|
**Done when:** a metadata-only drill receipt records artifact and source
|
|
identity, checks, duration, cleanup, and result; an independent observer can
|
|
distinguish a successful recovery from a backup-only claim without seeing
|
|
decrypted configuration.
|
|
|
|
## Acceptance
|
|
|
|
- [ ] S1 backup membership, exclusions, recipients, and retention are declared
|
|
and validated from source.
|
|
- [ ] Backup publication is atomic, encrypted, and accompanied by a safe
|
|
manifest and receipt.
|
|
- [ ] Restore defaults to isolated inspection and rejects tampering, traversal,
|
|
and accidental live overwrite.
|
|
- [ ] Scheduling and freshness checks are source-controlled but deployed only
|
|
through an explicit host-change approval.
|
|
- [ ] A governed off-host copy and an attended isolated restore drill prove the
|
|
selected S1 state is recoverable.
|
|
- [ ] No cluster, platform, tenant, secret-issuance, or private-key custody
|
|
responsibility is absorbed into this repository.
|