Threat-model hardening: serve flock, APL1 cache envelope, doc refresh#33
Merged
jgowdy-godaddy merged 5 commits intomainfrom Apr 17, 2026
Merged
Threat-model hardening: serve flock, APL1 cache envelope, doc refresh#33jgowdy-godaddy merged 5 commits intomainfrom
jgowdy-godaddy merged 5 commits intomainfrom
Conversation
- `awsenc serve` acquires an exclusive fs4 advisory lock on `<profile>.enc.lock` before touching the cache (ServeLock in serve.rs). Two concurrent AWS CLI → credential_process → awsenc serve calls no longer each fire STS (and possibly the transparent-reauth chain); the second caller blocks on the lock and reads the now-fresh cache. - dispatch() in main.rs validates the profile argument before calling create_storage(). `awsenc serve` without --profile now fails at the arg layer with 'no profile specified' instead of initializing hardware storage first. Cleaner error, and avoids popping macOS Keychain prompts on malformed invocations. - Adds fs4 as a direct dep of awsenc-cli.
Depends on libenclaveapp's new enclaveapp-cache::envelope module. Changes: - CacheHeader gains a binding_bytes() method emitting the 22-byte authoritative header serialization (magic + version + flags + credential_expiration + okta_session_expiration) that the envelope hash covers. - cache.rs exposes wrap_for_encrypt / unwrap_after_decrypt helpers that wrap/unwrap plaintext with the envelope bound to the given header, plus read_counter / write_counter / next_counter for the sidecar. - auth.rs::encrypt_and_cache now builds the header first, then wraps each secret (aws creds JSON, optional okta session JSON) in the envelope with a fresh monotonic counter before passing to storage.encrypt. After a successful cache write it persists the counter to the <profile>.enc.counter sidecar. - serve.rs decrypt paths (Fresh and Refresh fallback) use decrypt_aws_credentials_with_envelope, which verifies the header hash and counter >= sidecar before yielding the AwsCredentials JSON. - serve.rs::try_transparent_reauth also re-wraps both the refreshed AWS creds and the Okta session under the new header, so the refreshed envelope's hash matches the header that lands on disk. - exec.rs::get_cached_credentials calls unwrap_after_decrypt before deserializing, matching the serve path. Pre-envelope caches (no APL1 magic) pass through as legacy with counter = 0 so existing user installs decrypt cleanly; the first write after upgrade lands in the new format.
No member crate referenced it — enclaveapp-software doesn't exist in the consolidated libenclaveapp/crates/ tree and was a stale reference from an earlier rename. Declaration-only at workspace level means cargo ignored it silently; removing the line cleans up the manifest.
README.md / DESIGN.md: - Sibling-crates path corrected to ../libenclaveapp/crates/ (workspace consolidation — top-level /enclaveapps/crates/ is gone). - 'Credential Cache Format' section documents the APL1 envelope (SHA-256(header) + monotonic counter + <profile>.enc.counter sidecar) that wraps each ciphertext payload before ECIES. THREAT_MODEL.md: - T8 'Encrypted cache tampering' rewritten around envelope header hash. - T15 'Concurrent awsenc serve race' rewritten around the new fs4 lock. - T16 'Cache rollback' rewritten around the monotonic counter. - T9 'WSL bridge compromise' extended with the Authenticode-presence check and the which-fallback removal note.
main's #31 landed a per-profile serve flock that overlaps my own flock commit. Their implementation is nearly identical; I take theirs (serve_lock_path_for_cache + matching ServeLock) and drop the duplicate serve_lock_path + ServeLock definitions from my branch. Call sites use theirs' slightly more tolerant cache::cache_path().map().unwrap_or_else() fallback. Our remaining unique changes on top of main: - main.rs dispatch: validate profile arg before create_storage (fail-fast on serve/exec without --profile, cleaner error message, avoids popping Keychain prompts on malformed invocations) - cache.rs: APL1 envelope wrap_for_encrypt / unwrap_after_decrypt + counter sidecar helpers - auth.rs: header-first then wrap-each-secret ordering, counter persistence after write - serve.rs: decrypt_aws_credentials_with_envelope used by the Fresh + Refresh-fallback paths; transparent-reauth re-wraps under the new header - exec.rs: unwrap_after_decrypt in get_cached_credentials
Contributor
Author
|
rekick ci |
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.
Summary
Ports the cache-hardening pass from libenclaveapp's PR into awsenc and adds two consumer-side fixes:
fs4serve lock — concurrentawsenc servecalls (two AWS CLI invocations landing at once) now serialize on<profile>.enc.lockinstead of each firing a redundant STS/SAML chain.awsenc serve/execwithout--profilenow error at the arg layer instead of initializing hardware storage first. Cleaner error, and avoids popping macOS Keychain prompts on malformed invocations.<profile>.enc.countersidecar under anfs4exclusive flock. Legacy caches without theAPL1magic decrypt transparently — the next write lands in the new format.enclaveapp-softwaredep removed, sibling-crates path documented as../libenclaveapp/crates/(workspace consolidation — the top-level/enclaveapps/crates/tree is gone).Depends on
cache: add APL1 authenticated envelope ...) — must land first since awsenc consumes the newenclaveapp-cache::envelopemodule via path dep.Test plan
cargo checkclean.cargo fmt --checkclean.cargo clippy --workspace --all-targets -- -D warningsclean.cargo test --workspace— 272 tests passing, 0 failing.awsenc-cli/tests/integration_tests.rs): all 21 pass, includingserve_without_profile_exits_nonzero/serve_with_nonexistent_profile_exits_nonzerowhich previously triggered Keychain dialogs on isolated-$HOMEtest runs — fixed by the fail-fast change and the libenclaveapp Keychain-discovery fix.Out of scope