Skip to content

encryption: owner-signed key and prefix delete with a deletion ledger - #438

Draft
samgbafa wants to merge 35 commits into
mainfrom
tc1777/n3b-key-delete
Draft

samgbafa wants to merge 35 commits into
mainfrom
tc1777/n3b-key-delete

Conversation

@samgbafa

@samgbafa samgbafa commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Adds the owner-signed authorization for tinycloud.encryption/key.delete and an append-only deletion ledger.

What this change does so far:

  • Verifies an owner delete credential in the delete path itself: a UCAN invocation for did:key owners, a SIWE CACAO for did:pkh owners. The credential names exactly the requested key or apps/<app id>/ prefix, the node, and the network; it is valid for at most 300 seconds and its (owner, nonce) pair is consumed once, before any state change.
  • Adds the deletion ledger (one JSON line per deleted key, outside the database), its configuration key [global.encryption_network] deletion_ledger_path, and tinycloud node deletion-ledger replay.
  • Adds tests that /delegate and /revoke refuse key.delete credentials.

The delete route and its effect on key rows follow in this PR. named_keys_v1 stays off by default.

Details: TC-1777

test-ruling: TC-1777

dryrun added 30 commits October 23, 2026 23:20
…-only deletion ledger

Owner delete credentials (UCAN for did:key owners, SIWE CACAO for did:pkh
owners) are verified by the delete path itself, never stored as delegations,
with a one-time (owner, nonce) pair and one lifetime rule. The ledger records
each deleted key outside the database and can be replayed onto a database.

Details: TC-1777
A key path whose first segment is exactly apps needs three segments for a key and two for a prefix. The kind of a key follows from its path. App ids are validated and can be checked against a host name.

Details: TC-1777
…m public GET under apps/

Deletable keys carry a sealed per-key seed opened only with the strict unseal. Create, block and unblock run under the network row lock and refuse deleted rows. Create reuses a deleted deletable row with a new seed and the next key version. The public GET answers one 404 for every path under apps/. A startup check refuses key rows under apps/ that are not deletable.

Details: TC-1777
Adds the key delete route and its effect on key rows on top of the seeded
deletable keys: one transaction under the network row lock, byte-exact prefix
selection, one audit row per deleted key, and the deletion ledger append before
the commit.

Details: TC-1777
…p ids to the signing domain

Details: TC-1777
…invocations, and fail closed on a moved ledger

- A delete invocation (UCAN) carries nbf and lives at most 300 seconds from it.
- The ledger holds one line per delete operation, settled by a committed or aborted line; open operations are settled against the database at every start; replay applies committed and open operations and skips aborted ones, keys that are not deletable, and paths the key grammar refuses.
- Appends fail when the ledger file was renamed or removed; the file mode is 0600.
- Expired owner-delete nonces are pruned; the request network must be the target's network.
- The shared vector file carries the delete prefix and exact statement vectors.

Details: TC-1777
… lock waits and startup refusal

Details: TC-1777
…tc1777/n3b-key-delete

# Conflicts:
#	CHANGELOG.md
… a failed commit from the rows, and replay the ledger at start

- The prefix select builds substr(key_path, 1, n) = prefix with the query builder, so it runs on SQLite and PostgreSQL.
- After a commit error the node reads the rows again and writes committed or aborted from them; if the rows cannot be read the operation stays open.
- Ledger replay at start applies committed and open operations and closes the open ones; a ledger with entries that are not valid keys fails it.
- Owner-delete nonces are kept for 30 seconds past their expiry before they are pruned.
- Tests for each of these, for a deleted row that still holds a seed, and for a row of an unknown kind.

Details: TC-1777
… and keep them off when it cannot be used

- The node replays the ledger onto the database after the migrations and before the routes are chosen. If the ledger cannot be opened or replayed, the named-key routes stay off, an error event with a fixed name and error kind is logged, and the rest of the node serves.
- The delete route takes a body with the key path only.
- Tests for a restored database, an empty ledger, a malformed ledger, an unopenable ledger, a non-canonical network id and a body with another field.
- Docs: commit settling, start-time replay and the restore runbook.

Details: TC-1777
…tc1777/n3b-key-delete

# Conflicts:
#	tinycloud-node-server/src/policy_v3/named_keys_gate_tests.rs
dryrun added 2 commits October 24, 2026 02:03
- siwe_app_grants_need_an_https_or_absent_scheme: the config closure sets deletion_ledger_path in a temp dir.
- startup_refuses_a_non_deletable_key_row_under_apps: the named-keys boot sets deletion_ledger_path in a temp dir, keeps its is_err check, and adds a check that the start error names the rows under apps/ that are not deletable keys.

Details: TC-1777
- The read that settles an unknown commit outcome runs in a new transaction that first takes the network row lock (30 s lock timeout on PostgreSQL), so it waits for a commit still running on the server. Any error leaves the operation open.
- A PostgreSQL test stretches the commit with a deferred trigger and checks that the ledger ends committed and a restore plus replay deletes the key.
- The failing-ledger start test also checks the managed config, /info, the key delegation refusal and every key route.
- The refusal detail is asserted for a ReCap path without the trailing slash.

Details: TC-1777
@samgbafa

Copy link
Copy Markdown
Contributor Author

APPROVE: see TC-1777 (reviewed exact head e863ee0)

…create the ledger directory in the image

- An append whose write or fsync fails truncates the file to its length before the call and fsyncs, so a partial line is never followed by another. If the truncation fails, later appends fail.
- The module comment states what the code does: start-time replay applies committed and open operations and closes the open ones.
- The image contains /var/lib/tinycloud/ledger owned by uid 1000 with mode 0700.
- Test: a write that fails after 0, 10 and all-but-one bytes leaves the file unchanged, and a later append reads and replays.

Details: TC-1777
@samgbafa

Copy link
Copy Markdown
Contributor Author

APPROVE: see TC-1777 (reviewed exact head 3d3838e)

dryrun added 2 commits October 24, 2026 15:39
…ode runs

- open takes an exclusive flock on <ledger path>.lock (created 0600, never removed) before it repairs or reads the ledger file, and holds it for the life of the value. If another process holds it, open tries every 500 ms for up to 60 s, logs deletion_ledger_lock_wait once, and then fails with error kind lock_held.
- A failed append that cannot be cut back logs deletion_ledger_unrepaired.
- Tests: a second open waits for the first to close; it fails as lock_held past the limit and leaves a partial last line in place; the lock file is forced to 0600; a whole line followed by an error is cut back.
- Three start tests that kept one ledger file open in two apps at once use a second file for the first app (setup only): siwe_app_grants_need_an_app_id_bound_to_the_domain (second harness), a_restored_database_has_the_ledger_applied_before_the_key_routes_serve, a_start_with_an_empty_ledger_changes_nothing; a_crash_between_the_intent_and_the_commit_is_applied_at_the_next_start uses the fixture's open ledger.

Details: TC-1777
@samgbafa

Copy link
Copy Markdown
Contributor Author

APPROVE: see TC-1777 (reviewed exact head 4644aaf)

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