Make S1 handoff read-only by default
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02994-7685-7940-bf34-3555b8256018
This commit is contained in:
parent
24b799ec59
commit
40e295e3bd
16 changed files with 637 additions and 46 deletions
|
|
@ -179,9 +179,12 @@ interpreting an expected-red exception.
|
|||
`make s1-handoff` validate source contracts, require a clean revision, run each
|
||||
host separately, fail the aggregate on any non-zero result, require TAP hashes
|
||||
for a pass, and record a freshness boundary. Dry-run output is forcibly
|
||||
`not-run`. The attended all-host run waits for an environment with Ansible and
|
||||
reviewed permission to refresh the on-host Goss surface; this workstation has
|
||||
no `ansible-playbook`. No host was contacted.
|
||||
`not-run`. The live playbook is now remotely read-only: a static contract
|
||||
rejects mutating modules and arbitrary commands, it requires the installed
|
||||
Goss surface to match the locally rendered profile digest, and it writes only
|
||||
controller-side evidence. Refresh is a separate exact-approval interface. The
|
||||
attended all-host run now waits only for Ansible plus short-lived SSH access;
|
||||
no host was contacted.
|
||||
|
||||
## T06 — Repair and enforce the secret-source contract
|
||||
|
||||
|
|
@ -286,10 +289,14 @@ decryption receipt or recipient change was attempted.
|
|||
|
||||
Current evidence (2026-08-23):
|
||||
|
||||
- Python unit suite: 25 tests pass.
|
||||
- Python unit suite: 30 tests pass, including rejection of a remote template,
|
||||
arbitrary remote command, non-delegated controller write, and unreviewed
|
||||
Goss command surface in the handoff path.
|
||||
- Terraform 1.9.8 mock-provider tests: 2 pass.
|
||||
- Ansible 2.17.13 syntax checks: bootstrap, verify, and firewall pass in a
|
||||
disposable environment.
|
||||
- Ansible 2.17.13 syntax checks: bootstrap, read-only verify, explicit
|
||||
verify-refresh, and firewall pass in a disposable environment. A disposable
|
||||
render comparison also proves the template lookup digest matches the bytes
|
||||
produced by Ansible's deployment template action.
|
||||
- Both profile-specific Goss templates render and parse as YAML.
|
||||
- Inventory, baseline, secret metadata, receipt, shell syntax, Python compile,
|
||||
and whitespace checks pass.
|
||||
|
|
|
|||
178
workplans/RAIL-HO-WP-0012-s1-backup-recovery-loop.md
Normal file
178
workplans/RAIL-HO-WP-0012-s1-backup-recovery-loop.md
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
---
|
||||
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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue