Skip to content

Threat-model hardening: serve flock, APL1 cache envelope, doc refresh#33

Merged
jgowdy-godaddy merged 5 commits intomainfrom
docs/design-cleanup
Apr 17, 2026
Merged

Threat-model hardening: serve flock, APL1 cache envelope, doc refresh#33
jgowdy-godaddy merged 5 commits intomainfrom
docs/design-cleanup

Conversation

@jgowdy-godaddy
Copy link
Copy Markdown
Contributor

Summary

Ports the cache-hardening pass from libenclaveapp's PR into awsenc and adds two consumer-side fixes:

  • Per-profile fs4 serve lock — concurrent awsenc serve calls (two AWS CLI invocations landing at once) now serialize on <profile>.enc.lock instead of each firing a redundant STS/SAML chain.
  • Fail-fast profile validation in the dispatch layer — awsenc serve/exec without --profile now error at the arg layer instead of initializing hardware storage first. Cleaner error, and avoids popping macOS Keychain prompts on malformed invocations.
  • APL1 envelope wraps the AWS credentials JSON and the optional Okta session JSON before ECIES. The envelope binds the cache header (magic, version, flags, expirations) via SHA-256 and embeds a monotonic rollback counter held in a <profile>.enc.counter sidecar under an fs4 exclusive flock. Legacy caches without the APL1 magic decrypt transparently — the next write lands in the new format.
  • Workspace hygiene — dead enclaveapp-software dep removed, sibling-crates path documented as ../libenclaveapp/crates/ (workspace consolidation — the top-level /enclaveapps/crates/ tree is gone).

Depends on

Test plan

  • cargo check clean.
  • cargo fmt --check clean.
  • cargo clippy --workspace --all-targets -- -D warnings clean.
  • cargo test --workspace — 272 tests passing, 0 failing.
  • Integration tests (awsenc-cli/tests/integration_tests.rs): all 21 pass, including serve_without_profile_exits_nonzero / serve_with_nonexistent_profile_exits_nonzero which previously triggered Keychain dialogs on isolated-$HOME test runs — fixed by the fail-fast change and the libenclaveapp Keychain-discovery fix.

Out of scope

  • User-facing config-file tamper (T14) is still game-over for same-UID write access. Documented as a trust boundary, not an active gap.

jgowdy added 5 commits April 17, 2026 09:52
- `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
@jgowdy-godaddy
Copy link
Copy Markdown
Contributor Author

rekick ci

@jgowdy-godaddy jgowdy-godaddy merged commit e82abcc into main Apr 17, 2026
3 of 6 checks passed
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.

2 participants