Skip to content

feat(TC-887): sealed KV block helpers and plaintext sizes by space tag - #329

Draft
samgbafa wants to merge 1 commit into
skgbafa/tc-887-s1-node-wiringfrom
skgbafa/tc-887-s3-kv-sealed
Draft

samgbafa wants to merge 1 commit into
skgbafa/tc-887-s1-node-wiringfrom
skgbafa/tc-887-s3-kv-sealed

Conversation

@samgbafa

@samgbafa samgbafa commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Part of TC-887 (encryption at rest), slice S3a: shared pieces for sealing KV blocks. The block stores don't seal yet (that's S3b for FileSystem and S3c for S3). With at-rest mode off, every code path behaves as it does today.

Stack: base skgbafa/tc-887-s1-node-wiring = #327 @ c1a040e (S1 final, on #326 9131d24 → #325). The diff below is S3a only. I'll retarget it as the stack merges.

What changed

  • tinycloud-node-server/src/storage/sealed.rs (new):
    • AtRestView: a per-operation snapshot of AtRestRuntime (mode, encrypting, provider).
    • write_target: off → legacy name. Otherwise the sealed name and the space's current data key, but only while encrypting(); anything else returns Unavailable, never plaintext.
    • read_plan: tries the sealed name and then the legacy name. With locked keys it reads legacy only. strict reads sealed only.
    • SealedName (v1/{space_tag}/{object_tag}), space_tag_of.
    • open_sealed: header check → key lookup → stored size must equal the header's ciphertext length → Content with the plaintext length over DecryptReader. plan_range + SealedRange::reader serve ranges; an unsatisfiable range carries the plaintext total.
    • seal_vec (runs in spawn_blocking) and seal_file (streams the staged file into a second temp file in the same directory).
    • AtRestStoreError, with status(): 503 for unavailable keys or key-store failures; 500 for integrity, unsupported format, or legacy refused in strict. Conversions from DataKeyError and EnvelopeError.
    • scanned_object: classifies bucket keys as legacy, sealed or other (_tc/ is S1's key mirror).
    • A rate-limited warning for invalid sealed lengths (logs the length only).
  • storage/size.rs: SpaceSizes keeps legacy bytes per space plus sealed bytes per space tag. get_size(space, tag) joins the two when the caller can name the tag, i.e. keys are unlocked. Sizes are plaintext bytes.
  • storage/s3.rs, storage/file_system.rs: boot scans file v1/… objects under their tag, using plaintext_len(object size). The S3 scan skips _tc/. The FS scan no longer fails on the v1 directory. Both store errors gain an AtRest variant. The stores pass tag = None for now.
  • routes/mod.rs, routes/public.rs: at-rest store errors answer 503/500 on /invoke (checked before the existing catch-all) and on the signed and public KV GET/HEAD routes. Other errors are unchanged.
  • tinycloud-node-server/Cargo.toml: dev-dependency tinycloud-core features add test-util, for FixedKeyProvider in tests.

How it's tested

cargo test -p tinycloud-node --lib storage::: 17 pass (12 new):

  • write_target matrix: off; encrypt with the gate open (sealed name and DEK id checked); gate closed / no keys / locked → 503.
  • read_plan matrix (off, encrypt, locked, strict).
  • Sealed round trip at 0, 1, 64Ki−1, 64Ki, 64Ki+1 and 2·64Ki+7, with Content::len equal to the plaintext length.
  • Fail-closed cases: size mismatch; a TCE2 header → Unsupported (500); unknown DEK id; locked → 503; the wrong content hash yields zero bytes and then InvalidData; legacy bytes under a sealed name are refused.
  • A forged header length (smaller, larger, 1, with the object padded or cut to match) reports that total but aborts with zero bytes released.
  • Ranges between every pair of segment edges equal the plaintext slice; an unsatisfiable range reports the plaintext total.
  • seal_file round trip, and refusal on a length mismatch.
  • Boot-scan name classification. Space tags only exist while keys are unlocked.
  • Sizes: sealed bytes join only when the tag is known; a sealed-only space grows under its tag; removal drains the tag entry first.
  • FS boot scan with a v1/ directory: opens, keeps legacy bytes per space, counts sealed plaintext per tag, counts an invalid length as 0.
  • Error statuses for both stores: 503 / 500 / 500.

Also: cargo clippy -p tinycloud-node --all-targets -- -D warnings, cargo fmt --all -- --check, did_resolution_single_entry, and the at_rest_harness self-test pass. GitHub CI doesn't run here because the base isn't main.

@samgbafa

Copy link
Copy Markdown
Contributor Author

Rebased onto S1 round 3 (#327 daef3cf). Exact-head CI on 21aa0e9 (dispatched with workflow_dispatch on the PR branch): run https://github.com/TinyCloudLabs/tinycloud-node/actions/runs/39032815863, all jobs green.

@samgbafa

Copy link
Copy Markdown
Contributor Author

APPROVE: see TC-887 (head 21aa0e9cca14eb032764cdfc09a0cbee64540329)

@samgbafa

Copy link
Copy Markdown
Contributor Author

APPROVE: see TC-887 (head 21aa0e9)

@samgbafa

Copy link
Copy Markdown
Contributor Author

APPROVE: see TC-887 (head 21aa0e9, unchanged)

@samgbafa
samgbafa force-pushed the skgbafa/tc-887-s3-kv-sealed branch 2 times, most recently from 843cea8 to d05c261 Compare October 17, 2026 17:33
@samgbafa

Copy link
Copy Markdown
Contributor Author

APPROVE: see TC-887 (head d05c261)

@samgbafa

Copy link
Copy Markdown
Contributor Author

Rebased onto S1 #327 029c703. Exact-head CI on d05c261: run https://github.com/TinyCloudLabs/tinycloud-node/actions/runs/39054262669, all jobs green.

…at-rest error statuses

Adds storage::sealed: the write/read decisions from the at-rest mode and
keys, sealed names, header-checked open and range planning, and
in-memory/streaming file sealing. SpaceSizes counts sealed objects
under their space tag and joins them once the object-name key is
unlocked; the S3 and FS boot scans file v1/ objects by tag (plaintext
length) and skip the _tc/ key mirror. At-rest store errors answer 503
(keys unavailable) or 500 (integrity, unsupported) on /invoke and the
KV GET routes. The stores do not seal yet.
@samgbafa
samgbafa force-pushed the skgbafa/tc-887-s3-kv-sealed branch from d05c261 to 8acfd5d Compare October 17, 2026 18:48

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant