Skip to content

feat: encrypted secrets passphrase unlock at gtc start (v0.6.0)#101

Draft
BimaPangestu28 wants to merge 1 commit into
mainfrom
feat/encrypted-secrets-passphrase
Draft

feat: encrypted secrets passphrase unlock at gtc start (v0.6.0)#101
BimaPangestu28 wants to merge 1 commit into
mainfrom
feat/encrypted-secrets-passphrase

Conversation

@BimaPangestu28
Copy link
Copy Markdown
Member

⚠️ DRAFT — depends on greenticai/greentic-secrets#51 + greenticai/greentic-setup#60. The `[patch.crates-io]` block in Cargo.toml MUST be removed before this PR is taken out of draft.

Summary

When a bundle's dev secret store has been encrypted by `gtc setup` (greentic-setup v0.6), `gtc start` now prompts the user for the passphrase, decrypts the store body in-memory, writes a 0600-mode plaintext copy to a tempfile, and points `GREENTIC_DEV_SECRETS_PATH` at it so the v0.5 runner stack can read secrets unchanged.

What changes

CLI

  • New `--passphrase-stdin` flag (also via `GREENTIC_PASSPHRASE_STDIN=1` env var) — read passphrase from stdin (CI/daemon mode)
  • New `--passphrase-file ` flag — read from 0600-mode file owned by current user

New module `src/passphrase_unlock.rs`

  • `unlock_dev_store(store_path, opts)` — detects v1 header via `greentic_secrets_passphrase::peek_header`. If absent (legacy plaintext or missing file), returns `Ok(None)` — bundle works unchanged.
  • If encrypted: prompts via `greentic_secrets_cli::passphrase::resolve` (priority file → stdin → TTY), derives master key with Argon2id, decrypts body with AES-256-GCM, writes plaintext copy with permissions `0600`, sets `GREENTIC_DEV_SECRETS_PATH`.
  • Returns `UnlockedDevStore` guard whose `Drop` removes the tempfile on shutdown (best effort).

Wiring

  • `run_start` in `src/lib.rs` calls `passphrase_unlock::unlock_dev_store` early (before `SecretsClient::open`) and binds the guard to `_unlocked_store` so the lifetime spans the runtime.

Why a tempfile shim instead of bumping greentic-secrets-lib?

The runtime stack (`greentic-runner-host`, `greentic-runner-desktop`, `greentic-distributor-client`) is currently pinned to `greentic-secrets-lib v0.5`. Bumping the entire chain to v0.6 in lockstep would block this PR on several upstream releases. The shim avoids that: greentic-start does the decrypt, the runner sees a normal plaintext store, the trait identity stays at v0.5 across the whole graph. When `greentic-runner-host` upgrades to v0.6 in a future release, this entire shim module can be deleted in favor of opening the encrypted store directly.

Version

`0.5.4` → `0.6.0`.

Security

  • Passphrase is `Zeroize`d after key derivation
  • Master key zeroized after decrypt (manual zeroize on error path; standard zeroize-on-drop on success)
  • Tempfile chmod'd to 0600, written under `$XDG_RUNTIME_DIR` when available
  • Tempfile removed on `UnlockedDevStore::Drop`
  • AES-GCM auth failure surfaces as "passphrase incorrect" (no oracle)

Test plan

  • `cargo fmt --all -- --check`
  • `cargo clippy --all-targets --all-features -- -D warnings`
  • `cargo test --lib` — 437 passing
  • Pre-merge cleanup: remove `[patch.crates-io]` block from `Cargo.toml`, change deps from path to crates.io "0.6"
  • Manual smoke: `gtc setup` (creates encrypted store), then `gtc start` prompts for passphrase, succeeds → flows execute and read secrets via `SecretsClient` without modification
  • Manual smoke: `gtc start --passphrase-stdin` (CI mode)
  • Manual smoke: bundle with legacy plaintext store still starts unchanged (no prompt)

Related

Adds a decrypt-to-tempfile shim that lets gtc start consume bundles
whose dev secret store has been encrypted by greentic-setup v0.6.

Architecture:
- New module passphrase_unlock parses the v1 header, prompts for
  passphrase via greentic-secrets-cli, derives the master key with
  Argon2id, decrypts the body in-memory with AES-256-GCM, then writes
  a 0600-mode plaintext copy to $XDG_RUNTIME_DIR (or /tmp) and points
  GREENTIC_DEV_SECRETS_PATH at it.
- The tempfile is held alive in run_start via UnlockedDevStore which
  removes the file on Drop (best effort).
- The runner stack stays on greentic-secrets-lib v0.5 — no version
  skew with greentic-runner-host etc. Only the new additive crates
  greentic-secrets-passphrase and greentic-secrets-cli are pulled in.

CLI flags:
- --passphrase-stdin: read passphrase from stdin (also via
  GREENTIC_PASSPHRASE_STDIN=1 env var)
- --passphrase-file <PATH>: read from 0600-mode file

When the bundle's dev store is in legacy plaintext format (or absent),
the unlock is a no-op: bundles that never went through encrypted setup
still work unchanged.

Version 0.5.4 -> 0.6.0.

[patch.crates-io] in Cargo.toml temporarily points greentic-secrets-
passphrase + greentic-secrets-cli at the local clone while
greenticai/greentic-secrets#51 awaits publish; remove the patch block
before merging this PR.
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