Production Preview¶
The first release target is a production preview for Kubernetes operators who store backups on S3-compatible infrastructure they do not fully trust. The main client is Velero with Kopia uploader; direct Kopia use remains in scope because it is the compatibility substrate.
The preview defines the behavior to evaluate. It is not a stable repository-format promise.
Repository release is currently blocked
commits/v01 has been removed and is unsupported; it had no production
repositories. The runtime now reads and writes the preview-scoped
commits/v03 envelope with framed index runs, signed catalogs,
payload-skipping replay, guarded metadata-only packed-run compaction, and
automatic active-run watermarks. Existing compatibility and provider
results are useful gateway regression evidence. New-write protection
cohorts, exact guarded full GC, the maintenance supervisor, and its fenced
operator surfaces are implemented. Pinned-runner qualification, current
retained-provider restart/fault and production-cardinality maintenance
evidence, owner release infrastructure, and external review still block a
production repository release. There will be no v01 migration or
dual-reader requirement.
Preview Scope¶
rs3 is a path-private, tamper-evident S3 gateway for backup repositories.
It should let an operator put Velero/Kopia backup data on an S3-compatible store while reducing what that provider can learn or change:
- backend object keys do not reveal backup paths, namespaces, Kubernetes object names, snapshot names, or Kopia blob names
- payloads and privacy-sensitive metadata are encrypted and authenticated before they reach the backend
- backend-injected objects are ignored unless they are referenced by signed repository state
- backend rollback is rejected against the configured external anchor
- retained exact-version objects and trusted recovery bundles help investigate whether the backend or anchor service served stale or inconsistent state
rs3 does not replace Velero, Kopia, database-native backup consistency, or
application-aware restore validation. It protects the object-store boundary and
the repository-state transition model.
Claim Boundaries¶
| Evaluated preview behavior | Not a preview claim |
|---|---|
| Backend object keys and path-sensitive metadata are opaque to the storage backend. | The backend learns nothing; object counts, sizes, timing, and broad object classes remain visible. |
| Anchored signed commits detect backend rollback against the trusted anchor. | S3 Object Lock or retention alone establishes the latest repository state. |
restore-readonly serves restore reads and rejects supported repository mutations. |
Every backup client will report a successful status when its own restore bookkeeping writes are denied. |
| Velero/Kopia and Kopia compatibility are covered by local integration evidence. | Broad generic S3 application compatibility is promised. |
| The preview repository can be evaluated with documented recovery material. | The durable repository format is stable across future releases. |
Supported Shape¶
| Area | Preview scope |
|---|---|
| Primary client | Velero with Kopia uploader |
| Compatibility client | Kopia |
| Deployment | Kubernetes |
| External anchor | Kubernetes Lease |
| Storage witness | Retained exact-version commit objects; the Kubernetes Lease remains the latest-state authority |
| Backend | S3-compatible retained-version backend; RustFS/local checks for disposable evidence, live checks opt-in |
| Keys | Encrypted keyring envelope |
| Gateway modes | read-write for backups and routine restores, restore-readonly for incident restore |
| Retention | Provider retention/Object Lock capability checked where configured |
Trust Model¶
The preview assumes the S3-compatible backend can read, list, delay, delete, overwrite, replay, or inject objects unless provider retention prevents it. It also assumes the Kubernetes API used for the Lease anchor is a separate trust domain from the storage backend.
That separation is operational, not automatic. If the same compromised control domain can rewrite the Lease, read the wrapping-key source, alter backend credentials, and replace preserved restore bundles, the online protection model is exhausted. Preview deployments should keep those authorities separated enough for incident recovery and audit.
The Kubernetes Lease is the preview authority for latest accepted repository state. Retained commit objects are useful history, not the authority. On a healthy open, the accepted anchor position, signed commit chain, format root, and keyring envelope reference must agree. If they do not agree, the gateway must fail closed or enter an explicit recovery workflow.
This means a malicious backend should not be able to make the gateway silently restore an older valid commit while the in-cluster anchor still records a newer one. The backend can still deny service by hiding required objects.
Failure Rules¶
| Situation | Preview behavior |
|---|---|
| Empty backend prefix and no anchor | Startup may initialize one generated keyring envelope only when repository initialization is explicitly enabled using the supplied repository ID and wrapping-key source, with a generated or pinned salt. An envelope object ID is optional override state, not normal Helm state. |
| Existing backend prefix and matching anchor | Open after signed commit-chain, format-root, and envelope validation. |
Fresh initialization finds unsupported commits/v01, commits/v02 or objects/v02 objects or ambiguous existing state |
Fail closed. v03 initialization does not import, migrate, overwrite, or adopt the old generation. |
| Backend serves an older commit than the Lease anchor | Fail closed as rollback. |
| Backend hides the commit named by the Lease anchor | Fail closed as unavailable or tampered. |
| Backend adds unrelated objects | Ignore them unless signed and reachable from anchored state. |
| Backend overwrites format, keyring, commit, metadata, or payload bytes | Reject through native create-only write checks, signed/digested state, AEAD authentication, or retained-version exact reads. S3 providers qualify through either atomic-create or retained-version; HEAD before PUT is not a production fallback. |
| Backend contains commits newer than the Lease anchor | Do not silently advance. Treat as ambiguous until explicit recovery validates a trusted bundle or a separately approved anchor decision. |
| Lease missing but backend objects exist | Do not silently trust storage. Require a trusted generation-matched recovery bundle and verify the exact catalog, run, and payload graph before recreating the anchor. |
Multiple gateways serve the same repository as read-write |
Supported only as failover within one Kubernetes apiserver and anchor-Lease coordination domain. A gateway acquires a unique fenced writer epoch on the anchor Lease itself; every anchor advance verifies that epoch atomically, and the gateway shuts down if renewal is no longer trustworthy. Disconnected writers that only share S3 are unsupported. |
Gateway started as restore-readonly without an accepted anchor |
Fail closed. Run explicit anchor recovery first, then serve restore traffic. |
| Healthy Velero restore through the primary path | Run through the single read-write gateway so Velero restore-result artifacts are committed and the restore can report Completed. |
Restore client attempts PUT, DELETE, or legal-hold mutation through restore-readonly |
Reject the request instead of advancing repository state. |
Velero restore reports PartiallyFailed only because restore-result artifact uploads were denied by restore-readonly |
Accept only after verifying restored data, completed pod-volume restore, and zero backend writes during restore. Treat any other restore error as failure. |
| Lease and backend are both compromised | Online protection is exhausted; recovery needs offline or externally protected authority. |
| Wrapping key and old envelope are both exposed | Rewrap protects only future envelope handling; historical data under that keyring is treated as exposed. |
Kubernetes Lease fencing is not a cross-cluster consensus protocol. S3 object
synchronization, conditional writes, Object Lock, or a newest-looking commit do
not provide a safe writer lock under the malicious and eventually inconsistent
backend threat model. Run disconnected gateways as restore-readonly readers;
multiple such readers are supported when each can verify an accepted anchored
state. A future disconnected multi-writer mode would need authenticated
branches, deterministic conflict handling, and a separate repository protocol,
not an S3-sync switch.
Within the active fenced gateway, declared-length large bodies upload in parallel and serialize only their short signed-reference publications. This is data-plane concurrency, not disconnected multi-writer support. A stalled upload does not own the repository publication lock, and a caller cancellation after the backend may have completed does not make unanchored bytes visible.
Incomplete Multipart Cleanup¶
Production-preview retained S3-compatible buckets MUST configure a lifecycle rule that aborts incomplete multipart uploads. Client disconnects, pod crashes, or provider-side abort failures can leave temporary multipart parts that are not committed repository objects, so rs3 repository GC cannot discover or clean them. Use at least two days after initiation: the gateway permits a 24-hour client session and needs additional margin for admitted parts and completion. Read-write S3 startup rejects a shorter overlapping rule, and it also rejects ordinary age-based expiration that overlaps repository storage.
Example lifecycle shape, adapting the prefix syntax to the selected provider:
{
"Rules": [
{
"ID": "abort-incomplete-rs3-multipart-uploads",
"Status": "Enabled",
"Filter": {
"Prefix": "<backend-prefix>/"
},
"AbortIncompleteMultipartUpload": {
"DaysAfterInitiation": 2
}
}
]
}
Bootstrap UX¶
The preview bootstrap path is declarative:
- Choose a stable
repository.id. - Store the wrapping-key source outside the object store.
- Deploy the gateway with Kubernetes Lease anchoring.
The public salt is generated by initialization, journaled before the first
artifact is written, and recovered from the verified format root or keyring
envelope on every later start, tool run, and historical read. It needs no
independent custody. repositoryKeys.saltHex remains available to pin a known
value; a pinned value that disagrees with the envelope fails startup.
Bootstrap assumes a fresh, randomized backend prefix. The gateway probes the
root and known repository sub-prefixes, including refused legacy generations,
before initializing, but provider LIST
semantics are still part of the storage trust boundary. Do not bootstrap over an
old or unknown prefix; recover or import the existing repository instead.
For production-like Helm deployments, values remain declarative. The chart should consume an existing Secret or explicit values. It should not mutate Helm values after first run.
Startup bootstrap behavior is:
- if the configured backend prefix is empty, initialize exactly one repository using the supplied repository ID, wrapping-key source, and a generated or pinned salt
- if the prefix already contains repository state, verify that the configured repository ID, any pinned salt, format-bound envelope, wrapping-key source, anchor, and commit chain match
- if the prefix is non-empty but cannot be verified, stop with a precise error instead of creating new state
Disaster Recovery¶
Initial onboarding uses Kubernetes Secret custody and the live Lease anchor. It verifies a fresh payload restore before admitting clients, without requiring an offline signer. This proves the configured local recovery path; independent custody and recovery after total cluster loss are not established by onboarding.
A new cluster needs more than backend credentials:
- repository ID
- wrapping-key source for the envelope
- trusted v03 anchor position and repository format generation: sequence, commit key, commit object version ID when available, commit body digest, signing key ID, and format-root reference
The trusted anchor position can come from a recovery bundle or audited export. Retained backend versions can help establish what existed in storage, but by themselves they are not a latest-state authority because a malicious backend can hide newer valid commits or replay older valid commits.
Recovery bundles are weak-subjectivity inputs. Import requires an
operator-supplied --min-sequence floor external to the bundle and refuses
older valid bundles below that floor. The portable production bundle-import path also requires an
offline Ed25519 bundle signature verified by RS3_RECOVERY_PUBLIC_KEY.
Import scans stored v03 commits and refuses to strand higher commit sequences
unless the operator passes the explicit --force-rollback override after
rollback review.
Preserve fresh signed bundles outside the backend and compare the sequence and
commit digest with an external record before anchor import.
Any external anchor should store or sign the accepted commit position, not the whole repository index. That position already commits to the signed commit chain and therefore to the repository state reachable from it.
Release requirements¶
The implementation is a controlled preview, not a production repository release. Select one exact clean candidate and retain revision-bound evidence for all of the following before accepting production data:
- local and scheduled checks, source-bound Kubernetes/Velero recovery, and disaster-recovery bundle import;
- retained-provider behavior at the required maintenance cardinality, including restart, checkpoint interruption, stale fencing, delayed visibility, renewal, guarded exact-version GC and writer handoff;
- a provider profile that explicitly supports either
atomic-createorretained-version. The latter requires Object Lock, version IDs and exact-version reads after a newer object exists; - operator-owned governance IAM or bucket-policy review proving ordinary gateway credentials cannot bypass governance retention; and
- approved capability, key-provider and compatibility policy, release identity, artifact provenance and external cryptographic/security review.
Local RustFS and Kubernetes results are regression evidence. They do not certify a selected external provider. The historical qualification report preserves prior evidence and its limits; Performance records current cost results and remaining measured performance failures.
Release commands¶
The commands below exercise the preview implementation. They qualify only the candidate and environment that produce retained, revision-bound evidence.
Run the cheap local regression gate first:
just preview-gate-local
Release candidates should also run the release integration gate:
just preview-gate-release
Scheduled hardening may run the local prototype nightly gate:
just preview-gate-v3-nightly
For gateway regression evidence, run restore traffic through the anchored gateway and verify the restored bytes against the application workload.
For disaster-recovery evidence, export a trusted restore bundle with
rs3-server export-restore-bundle and verify anchor import with
rs3-server import-anchor in a new cluster. For retained-version
repositories, first verify that the same bundle is rejected when the matching
retention context is omitted, then import with the configured retention mode
and verify the recovered bundle with rs3 verify-bundle.
Operator configuration¶
For a controlled prototype deployment only:
- use
RS3_ANCHOR_MODE=kubernetes-lease - leave
RS3_WRITER_GUARD=requiredenabled for read-write gateways - place the S3 and admin listeners behind TLS termination, or keep them on cluster-local networks protected by NetworkPolicy or equivalent controls
- if you run the separate experimental console, protect its listener and its console-to-gateway admin hop with the same controls
- use
repositoryKeys.create=trueorrepositoryKeys.existingSecretin Helm - set
repository.allowInit=trueonly for deliberate first initialization on a fresh backend prefix; turn it off for normal existing-repository serving - keep Helm
updateStrategy.type=Recreateforread-writegateways so rollouts do not overlap two writers - set a stable
repository.id - configure gateway access credentials explicitly
- configure repository retention when retention evidence is part of the trial
- leave
maintenance.modeunset for the default automatic posture on a read-write gateway, or document whymanualoroffis operationally safe - configure a distinct
admin.mutationBearerTokenfrom an external Secret when operators need maintenance mutations; omit it to keep the admin listener read-only - preserve
rs3 check-provider --format jsonoutput and configureRS3_PROVIDER_CONFORMANCE_REPORT_FILEwhen exposing admin posture - set gateway hardening limits for maximum
PutObjectsize, buffered upload threshold, backend multipart part size, in-flight upload body bytes, open connections, concurrent requests, and request rate; align ingress limits with them - use
gateway.mode=restore-readonlyfor incident restore deployments - run
rs3-server doctor --profile production --probebefore exposing the gateway - keep restore verification inputs outside the object-store trust boundary
- collect metrics and logs with path-safe labels only
- treat wrapping-key rewrap as hygiene, not as recovery from a leaked wrapping key plus copied envelope
Do not rely on backend state alone. Use the configured anchor position and restore-verification inputs from a trusted bundle or separate authority. See Retention and Object Lock and Restore Under Attack for the operating procedures behind these constraints.
Non-goals¶
The preview does not promise:
- stable repository-format compatibility
- broad generic S3 application compatibility
- external anchor implementation beyond Kubernetes Lease
- replacement of Kopia, Velero, or database-native backup systems
- that Velero application backups are confidential or complete by themselves