api/client: preserve device keys on read errors - #577
Open
basnijholt wants to merge 1 commit into
Open
Conversation
Distinguish missing stored device keys from read or decoding failures. Return internal errors before identity writes while retaining first-upload, replacement, and signature-preserving retry behavior. Exercise real HTTP uploads against corrupt stored rows and verify byte preservation.
There was a problem hiding this comment.
🟢 Approval recommended
The fix is well-scoped, tested, and has no unresolved issues.
Pull request overview
Preserves stored device keys when existing-key reads or decoding fail.
Changes:
- Distinguishes missing keys from read failures.
- Returns HTTP 500 without overwriting unreadable rows.
- Adds end-to-end regression coverage.
File summaries
| File | Description |
|---|---|
src/main/tests/device_key_read_errors.rs |
Tests uploads, retries, corrupt data, and byte preservation. |
src/api/client/keys/upload_keys.rs |
Propagates stored-key read errors safely. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 0
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
While reviewing the remaining device-key changes in the fork, I noticed that the exact-copy check in
/keys/uploadalso hides errors reading the existing keys. Theis_ok_andchain turns a database or decoding error intofalse, then the handler writes the uploaded keys over the stored row. A failed read is not evidence that no keys exist or that they differ.This distinguishes a missing row from a failed read. A genuinely missing row still permits the first upload; other read or decoding errors return a server error and leave the stored device-key bytes untouched. Malformed keys in the request are still rejected as a client error before this check.
The successful cases stay the same: uploading different valid device keys still replaces the existing keys, and an exact-copy retry is still ignored so it cannot strip the existing cross-signing signatures. The fork has a stricter identity-replacement policy, but that policy is not part of this PR.
The regression uses the existing server-booting HTTP harness with a real database. It checks first upload, valid replacement, and signature-preserving retries, then inserts malformed stored rows: missing required fields, invalid JSON syntax, truncated JSON, and invalid UTF-8. Each corrupt-row upload must return HTTP 500 and preserve the original bytes. These fixtures exercise decoding failures; they do not simulate physical storage I/O failures.
Verified in the pinned Nix dynamic shell (Rust 1.95.0 and nightly rustfmt):
cargo fmt --all -- --checkcargo clippy --offline --locked --workspace --all-targets --all-features -- -D warningscargo test --offline --locked -p tuwunel --all-features --test device_key_read_errors— passed.cargo test --offline --locked --workspace --all-targets --all-features— 1,079 passed, 0 failed, 4 ignored, matching upstream's ignored count.I also ran the regression against unchanged upstream: all four corrupt stored rows returned HTTP 200 and were overwritten. Test runs used isolated networking and storage, with debug symbols disabled.
Checklist