@wlfi-agent/cli 1.4.16 → 1.4.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +26 -20
- package/Cargo.toml +1 -1
- package/README.md +61 -28
- package/crates/vault-cli-admin/src/io_utils.rs +149 -1
- package/crates/vault-cli-admin/src/main.rs +639 -16
- package/crates/vault-cli-admin/src/shared_config.rs +18 -18
- package/crates/vault-cli-admin/src/tui/token_rpc.rs +190 -3
- package/crates/vault-cli-admin/src/tui/utils.rs +59 -0
- package/crates/vault-cli-admin/src/tui.rs +1205 -120
- package/crates/vault-cli-agent/Cargo.toml +1 -0
- package/crates/vault-cli-agent/src/io_utils.rs +163 -2
- package/crates/vault-cli-agent/src/main.rs +648 -32
- package/crates/vault-cli-daemon/Cargo.toml +4 -0
- package/crates/vault-cli-daemon/src/main.rs +617 -67
- package/crates/vault-cli-daemon/src/relay_sync.rs +776 -4
- package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +5 -0
- package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +32 -1
- package/crates/vault-daemon/src/persistence.rs +637 -100
- package/crates/vault-daemon/src/tests.rs +1013 -3
- package/crates/vault-daemon/src/tests_parts/part2.rs +99 -0
- package/crates/vault-daemon/src/tests_parts/part4.rs +11 -7
- package/crates/vault-domain/src/nonce.rs +4 -0
- package/crates/vault-domain/src/tests.rs +616 -0
- package/crates/vault-policy/src/engine.rs +55 -32
- package/crates/vault-policy/src/tests.rs +195 -0
- package/crates/vault-sdk-agent/src/lib.rs +415 -22
- package/crates/vault-signer/Cargo.toml +3 -0
- package/crates/vault-signer/src/lib.rs +266 -40
- package/crates/vault-transport-unix/src/lib.rs +653 -5
- package/crates/vault-transport-xpc/src/tests.rs +531 -3
- package/crates/vault-transport-xpc/tests/e2e_flow.rs +3 -0
- package/dist/cli.cjs +663 -190
- package/dist/cli.cjs.map +1 -1
- package/package.json +5 -2
- package/packages/cache/.turbo/turbo-build.log +53 -52
- package/packages/cache/coverage/clover.xml +529 -394
- package/packages/cache/coverage/coverage-final.json +2 -2
- package/packages/cache/coverage/index.html +21 -21
- package/packages/cache/coverage/src/client/index.html +1 -1
- package/packages/cache/coverage/src/client/index.ts.html +1 -1
- package/packages/cache/coverage/src/errors/index.html +1 -1
- package/packages/cache/coverage/src/errors/index.ts.html +12 -12
- package/packages/cache/coverage/src/index.html +1 -1
- package/packages/cache/coverage/src/index.ts.html +1 -1
- package/packages/cache/coverage/src/service/index.html +21 -21
- package/packages/cache/coverage/src/service/index.ts.html +769 -313
- package/packages/cache/dist/{chunk-QNK6GOTI.js → chunk-KC53LH5Z.js} +35 -2
- package/packages/cache/dist/chunk-KC53LH5Z.js.map +1 -0
- package/packages/cache/dist/{chunk-QF4XKEIA.cjs → chunk-UVU7VFE3.cjs} +35 -2
- package/packages/cache/dist/chunk-UVU7VFE3.cjs.map +1 -0
- package/packages/cache/dist/index.cjs +2 -2
- package/packages/cache/dist/index.js +1 -1
- package/packages/cache/dist/service/index.cjs +2 -2
- package/packages/cache/dist/service/index.js +1 -1
- package/packages/cache/node_modules/.bin/tsc +2 -2
- package/packages/cache/node_modules/.bin/tsserver +2 -2
- package/packages/cache/node_modules/.bin/tsup +2 -2
- package/packages/cache/node_modules/.bin/tsup-node +2 -2
- package/packages/cache/node_modules/.bin/vitest +4 -4
- package/packages/cache/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/cache/src/service/index.test.ts +165 -19
- package/packages/cache/src/service/index.ts +38 -1
- package/packages/config/.turbo/turbo-build.log +18 -17
- package/packages/config/dist/index.cjs +0 -17
- package/packages/config/dist/index.cjs.map +1 -1
- package/packages/config/src/index.ts +0 -17
- package/packages/rpc/.turbo/turbo-build.log +32 -31
- package/packages/rpc/dist/index.cjs +0 -17
- package/packages/rpc/dist/index.cjs.map +1 -1
- package/packages/rpc/src/index.js +1 -0
- package/packages/ui/.turbo/turbo-build.log +44 -43
- package/packages/ui/dist/components/badge.d.ts +1 -1
- package/packages/ui/dist/components/button.d.ts +1 -1
- package/packages/ui/node_modules/.bin/tsc +2 -2
- package/packages/ui/node_modules/.bin/tsserver +2 -2
- package/packages/ui/node_modules/.bin/tsup +2 -2
- package/packages/ui/node_modules/.bin/tsup-node +2 -2
- package/scripts/install-cli-launcher.mjs +37 -0
- package/scripts/install-rust-binaries.mjs +112 -0
- package/scripts/run-tests-isolated.mjs +210 -0
- package/src/cli.ts +310 -50
- package/src/lib/admin-reset.ts +15 -30
- package/src/lib/admin-setup.ts +246 -55
- package/src/lib/agent-auth-migrate.ts +5 -1
- package/src/lib/asset-broadcast.ts +15 -4
- package/src/lib/config-amounts.ts +6 -4
- package/src/lib/hidden-tty-prompt.js +1 -0
- package/src/lib/hidden-tty-prompt.ts +105 -0
- package/src/lib/keychain.ts +1 -0
- package/src/lib/local-admin-access.ts +4 -29
- package/src/lib/rust.ts +129 -33
- package/src/lib/signed-tx.ts +1 -0
- package/src/lib/sudo.ts +15 -5
- package/src/lib/wallet-profile.ts +3 -0
- package/src/lib/wallet-setup.ts +52 -0
- package/packages/cache/dist/chunk-QF4XKEIA.cjs.map +0 -1
- package/packages/cache/dist/chunk-QNK6GOTI.js.map +0 -1
package/Cargo.lock
CHANGED
|
@@ -823,9 +823,9 @@ dependencies = [
|
|
|
823
823
|
|
|
824
824
|
[[package]]
|
|
825
825
|
name = "darling"
|
|
826
|
-
version = "0.
|
|
826
|
+
version = "0.20.11"
|
|
827
827
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
828
|
-
checksum = "
|
|
828
|
+
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
|
829
829
|
dependencies = [
|
|
830
830
|
"darling_core",
|
|
831
831
|
"darling_macro",
|
|
@@ -833,10 +833,11 @@ dependencies = [
|
|
|
833
833
|
|
|
834
834
|
[[package]]
|
|
835
835
|
name = "darling_core"
|
|
836
|
-
version = "0.
|
|
836
|
+
version = "0.20.11"
|
|
837
837
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
838
|
-
checksum = "
|
|
838
|
+
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
|
|
839
839
|
dependencies = [
|
|
840
|
+
"fnv",
|
|
840
841
|
"ident_case",
|
|
841
842
|
"proc-macro2",
|
|
842
843
|
"quote",
|
|
@@ -846,9 +847,9 @@ dependencies = [
|
|
|
846
847
|
|
|
847
848
|
[[package]]
|
|
848
849
|
name = "darling_macro"
|
|
849
|
-
version = "0.
|
|
850
|
+
version = "0.20.11"
|
|
850
851
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
851
|
-
checksum = "
|
|
852
|
+
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
|
852
853
|
dependencies = [
|
|
853
854
|
"darling_core",
|
|
854
855
|
"quote",
|
|
@@ -867,12 +868,12 @@ dependencies = [
|
|
|
867
868
|
|
|
868
869
|
[[package]]
|
|
869
870
|
name = "deranged"
|
|
870
|
-
version = "0.
|
|
871
|
+
version = "0.3.11"
|
|
871
872
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
872
|
-
checksum = "
|
|
873
|
+
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
|
873
874
|
dependencies = [
|
|
874
875
|
"powerfmt",
|
|
875
|
-
"
|
|
876
|
+
"serde",
|
|
876
877
|
]
|
|
877
878
|
|
|
878
879
|
[[package]]
|
|
@@ -1523,9 +1524,9 @@ dependencies = [
|
|
|
1523
1524
|
|
|
1524
1525
|
[[package]]
|
|
1525
1526
|
name = "instability"
|
|
1526
|
-
version = "0.3.
|
|
1527
|
+
version = "0.3.10"
|
|
1527
1528
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1528
|
-
checksum = "
|
|
1529
|
+
checksum = "6778b0196eefee7df739db78758e5cf9b37412268bfa5650bfeed028aed20d9c"
|
|
1529
1530
|
dependencies = [
|
|
1530
1531
|
"darling",
|
|
1531
1532
|
"indoc",
|
|
@@ -1752,9 +1753,9 @@ dependencies = [
|
|
|
1752
1753
|
|
|
1753
1754
|
[[package]]
|
|
1754
1755
|
name = "num-conv"
|
|
1755
|
-
version = "0.
|
|
1756
|
+
version = "0.1.0"
|
|
1756
1757
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1757
|
-
checksum = "
|
|
1758
|
+
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
|
1758
1759
|
|
|
1759
1760
|
[[package]]
|
|
1760
1761
|
name = "num-integer"
|
|
@@ -2822,30 +2823,30 @@ dependencies = [
|
|
|
2822
2823
|
|
|
2823
2824
|
[[package]]
|
|
2824
2825
|
name = "time"
|
|
2825
|
-
version = "0.3.
|
|
2826
|
+
version = "0.3.36"
|
|
2826
2827
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2827
|
-
checksum = "
|
|
2828
|
+
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
|
2828
2829
|
dependencies = [
|
|
2829
2830
|
"deranged",
|
|
2830
2831
|
"itoa",
|
|
2831
2832
|
"num-conv",
|
|
2832
2833
|
"powerfmt",
|
|
2833
|
-
"
|
|
2834
|
+
"serde",
|
|
2834
2835
|
"time-core",
|
|
2835
2836
|
"time-macros",
|
|
2836
2837
|
]
|
|
2837
2838
|
|
|
2838
2839
|
[[package]]
|
|
2839
2840
|
name = "time-core"
|
|
2840
|
-
version = "0.1.
|
|
2841
|
+
version = "0.1.2"
|
|
2841
2842
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2842
|
-
checksum = "
|
|
2843
|
+
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
|
2843
2844
|
|
|
2844
2845
|
[[package]]
|
|
2845
2846
|
name = "time-macros"
|
|
2846
|
-
version = "0.2.
|
|
2847
|
+
version = "0.2.18"
|
|
2847
2848
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2848
|
-
checksum = "
|
|
2849
|
+
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
|
|
2849
2850
|
dependencies = [
|
|
2850
2851
|
"num-conv",
|
|
2851
2852
|
"time-core",
|
|
@@ -3776,6 +3777,7 @@ name = "wlfi-agent-agent"
|
|
|
3776
3777
|
version = "0.1.0"
|
|
3777
3778
|
dependencies = [
|
|
3778
3779
|
"anyhow",
|
|
3780
|
+
"async-trait",
|
|
3779
3781
|
"clap",
|
|
3780
3782
|
"hex",
|
|
3781
3783
|
"libc",
|
|
@@ -3798,8 +3800,11 @@ name = "wlfi-agent-daemon"
|
|
|
3798
3800
|
version = "0.1.0"
|
|
3799
3801
|
dependencies = [
|
|
3800
3802
|
"anyhow",
|
|
3803
|
+
"async-trait",
|
|
3804
|
+
"chacha20poly1305",
|
|
3801
3805
|
"clap",
|
|
3802
3806
|
"core-foundation",
|
|
3807
|
+
"hex",
|
|
3803
3808
|
"libc",
|
|
3804
3809
|
"nix",
|
|
3805
3810
|
"reqwest",
|
|
@@ -3815,6 +3820,7 @@ dependencies = [
|
|
|
3815
3820
|
"vault-domain",
|
|
3816
3821
|
"vault-signer",
|
|
3817
3822
|
"vault-transport-unix",
|
|
3823
|
+
"x25519-dalek",
|
|
3818
3824
|
"zeroize",
|
|
3819
3825
|
]
|
|
3820
3826
|
|
package/Cargo.toml
CHANGED
|
@@ -43,7 +43,7 @@ serde = { version = "1", features = ["derive"] }
|
|
|
43
43
|
serde_json = "1"
|
|
44
44
|
sha2 = "0.10"
|
|
45
45
|
thiserror = "2"
|
|
46
|
-
time = { version = "0.3", features = ["macros", "serde", "formatting", "parsing"] }
|
|
46
|
+
time = { version = "=0.3.36", features = ["macros", "serde", "formatting", "parsing"] }
|
|
47
47
|
tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync", "net", "io-util", "time", "signal"] }
|
|
48
48
|
uuid = { version = "1", features = ["serde", "v4"] }
|
|
49
49
|
zeroize = "1"
|
package/README.md
CHANGED
|
@@ -2,6 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
WLFI Agentic SDK is a root-managed local signing daemon with policy enforcement, a single `wlfi-agent` CLI, and an optional relay + web approval flow.
|
|
4
4
|
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
### Prerequisites
|
|
8
|
+
|
|
9
|
+
- macOS
|
|
10
|
+
- Rust toolchain on `PATH` (`cargo`, `rustc`) with Rust `1.87.0` or newer
|
|
11
|
+
- Xcode Command Line Tools (`xcode-select --install`)
|
|
12
|
+
|
|
13
|
+
### Install from npm
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm i -g @wlfi-agent/cli
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`npm i -g @wlfi-agent/cli` builds the local Rust runtime during `postinstall`. If the prerequisites above are already installed, this is the normal one-step install path. If `cargo` or the macOS Command Line Tools are missing, installation fails immediately and tells you how to install the missing prerequisite before retrying.
|
|
20
|
+
|
|
21
|
+
### Work from this repo
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm install
|
|
25
|
+
npm run build
|
|
26
|
+
npm run install:cli-launcher
|
|
27
|
+
npm run install:rust-binaries
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
On macOS, add `export PATH="$HOME/.wlfi_agent/bin:$PATH"` to `~/.zshrc`, then reload your shell with `source ~/.zshrc`.
|
|
31
|
+
|
|
32
|
+
On Linux, add `export PATH="$HOME/.wlfi_agent/bin:$PATH"` to your shell startup file such as `~/.bashrc`, `~/.zshrc`, or `~/.profile`, then reload that file or open a new shell.
|
|
33
|
+
|
|
34
|
+
`npm run install:cli-launcher` installs the `wlfi-agent` launcher into `~/.wlfi_agent/bin`, and `npm run install:rust-binaries` installs the Rust runtime into the same directory.
|
|
35
|
+
|
|
36
|
+
### Reinstall Rust daemon
|
|
37
|
+
|
|
38
|
+
If you update Rust daemon code, rerun `npm run install:rust-binaries` so the root-managed daemon uses the new installed binaries under `~/.wlfi_agent/bin`.
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
5
42
|
The main user path is:
|
|
6
43
|
|
|
7
44
|
1. run `wlfi-agent admin setup`
|
|
@@ -15,6 +52,7 @@ User-facing examples below avoid shell env vars on purpose. Prefer prompts, conf
|
|
|
15
52
|
|
|
16
53
|
- `wlfi-agent admin setup`
|
|
17
54
|
- first-run setup
|
|
55
|
+
- `--reuse-existing-wallet` reattaches the current local vault when you need to recover the daemon or refresh local credentials without creating a fresh wallet
|
|
18
56
|
- stores the vault password in macOS System Keychain
|
|
19
57
|
- installs the root LaunchDaemon
|
|
20
58
|
- creates a vault key + agent key
|
|
@@ -41,49 +79,36 @@ User-facing examples below avoid shell env vars on purpose. Prefer prompts, conf
|
|
|
41
79
|
- `wlfi-agent daemon`
|
|
42
80
|
- not a user entrypoint; daemon lifecycle is managed by `wlfi-agent admin setup`
|
|
43
81
|
|
|
44
|
-
##
|
|
45
|
-
|
|
46
|
-
### Prerequisites
|
|
47
|
-
|
|
48
|
-
- macOS
|
|
49
|
-
- Rust toolchain on `PATH` (`cargo`, `rustc`)
|
|
50
|
-
- Xcode Command Line Tools (`xcode-select --install`)
|
|
82
|
+
## Easiest wallet setup
|
|
51
83
|
|
|
52
|
-
|
|
84
|
+
Run this once:
|
|
53
85
|
|
|
54
86
|
```bash
|
|
55
|
-
|
|
87
|
+
wlfi-agent admin setup
|
|
56
88
|
```
|
|
57
89
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
### Work from this repo
|
|
90
|
+
Preview the exact sanitized setup plan first:
|
|
61
91
|
|
|
62
92
|
```bash
|
|
63
|
-
|
|
64
|
-
npm run build
|
|
65
|
-
npm run install:rust-binaries
|
|
93
|
+
wlfi-agent admin setup --plan
|
|
66
94
|
```
|
|
67
95
|
|
|
68
|
-
|
|
96
|
+
The preview is read-only. It does not prompt for the vault password, does not touch sudo, and does not mutate wallet or policy state. It prints the planned Rust command, trust preflight results, overwrite risk, and the password transport mode that would be used for the real setup.
|
|
69
97
|
|
|
70
|
-
|
|
98
|
+
During a real `wlfi-agent admin setup`, you may be prompted for two different secrets:
|
|
71
99
|
|
|
72
|
-
|
|
100
|
+
- `Vault password`: the wallet password you choose for encrypted local state
|
|
101
|
+
- `macOS admin password for sudo`: your macOS login/admin password, used only when setup needs elevated privileges to install or recover the root LaunchDaemon
|
|
73
102
|
|
|
74
|
-
|
|
75
|
-
wlfi-agent admin setup
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Preview the exact sanitized setup plan first:
|
|
103
|
+
If the local vault already exists and you only need to recover the managed daemon or refresh local setup state, reuse the current wallet instead of creating a fresh one:
|
|
79
104
|
|
|
80
105
|
```bash
|
|
81
|
-
wlfi-agent admin setup --
|
|
106
|
+
wlfi-agent admin setup --reuse-existing-wallet
|
|
82
107
|
```
|
|
83
108
|
|
|
84
|
-
|
|
109
|
+
This reuse path keeps the current vault address, prompts for `REUSE` in interactive mode, and still requires `--yes` in non-interactive mode.
|
|
85
110
|
|
|
86
|
-
|
|
111
|
+
After that, the command:
|
|
87
112
|
|
|
88
113
|
- installs or refreshes the root daemon
|
|
89
114
|
- waits for the daemon to come up
|
|
@@ -372,10 +397,18 @@ When relay is configured and a request requires manual approval:
|
|
|
372
397
|
3. the frontend encrypts the vault password + decision to the daemon’s advertised X25519 public key
|
|
373
398
|
4. the relay queues the encrypted update
|
|
374
399
|
5. the daemon polls, decrypts, applies the decision, and reports status back
|
|
375
|
-
6. the original
|
|
400
|
+
6. for `wlfi-agent transfer --broadcast`, `wlfi-agent transfer-native --broadcast`, and `wlfi-agent approve --broadcast`, the original CLI command keeps waiting on that same approval request and continues automatically after approval
|
|
401
|
+
7. commands outside those broadcast flows still print the approval details and exit, so operators can approve or reject the request separately
|
|
376
402
|
|
|
377
403
|
If the frontend link is unavailable, operators can always fall back to the local admin CLI approval command printed by the agent CLI.
|
|
378
404
|
|
|
405
|
+
For the auto-waiting broadcast flows above:
|
|
406
|
+
|
|
407
|
+
- do not rerun the original command after approving in the browser
|
|
408
|
+
- the CLI polls every 2 seconds for up to 5 minutes
|
|
409
|
+
- if the daemon returns a different approval request id while waiting, the CLI stops and tells you to inspect the approval status before rerunning
|
|
410
|
+
- approval details and waiting events go to `stderr`; the final successful `--json` result still goes to `stdout`
|
|
411
|
+
|
|
379
412
|
## Operational notes
|
|
380
413
|
|
|
381
414
|
- The daemon state file lives at `/var/db/wlfi-agent/daemon-state.enc` and is intended to be root-only.
|
|
@@ -438,7 +471,7 @@ wlfi-agent admin tui
|
|
|
438
471
|
wlfi-agent admin uninstall
|
|
439
472
|
wlfi-agent admin get-relay-config
|
|
440
473
|
wlfi-agent admin list-manual-approval-requests
|
|
441
|
-
wlfi-agent wallet
|
|
474
|
+
wlfi-agent wallet
|
|
442
475
|
npm run install:rust-binaries
|
|
443
476
|
pnpm build
|
|
444
477
|
pnpm typecheck
|
|
@@ -62,9 +62,11 @@ fn read_secret_from_reader(mut reader: impl Read, label: &str) -> Result<String>
|
|
|
62
62
|
#[cfg(test)]
|
|
63
63
|
mod tests {
|
|
64
64
|
use super::{
|
|
65
|
-
|
|
65
|
+
emit_output, ensure_output_parent, print_status, read_secret_from_reader,
|
|
66
|
+
resolve_output_target, resolve_vault_password, should_print_status, validate_password,
|
|
66
67
|
write_output_file,
|
|
67
68
|
};
|
|
69
|
+
use crate::{OutputFormat, OutputTarget};
|
|
68
70
|
use std::fs;
|
|
69
71
|
use std::io::Cursor;
|
|
70
72
|
use std::path::PathBuf;
|
|
@@ -95,12 +97,57 @@ mod tests {
|
|
|
95
97
|
assert!(err.to_string().contains("must not exceed"));
|
|
96
98
|
}
|
|
97
99
|
|
|
100
|
+
#[test]
|
|
101
|
+
fn read_secret_from_reader_trims_newlines_and_accepts_valid_secret() {
|
|
102
|
+
let secret =
|
|
103
|
+
read_secret_from_reader(Cursor::new(b"vault-secret\r\n".to_vec()), "vault password")
|
|
104
|
+
.expect("valid stdin secret");
|
|
105
|
+
assert_eq!(secret, "vault-secret");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#[test]
|
|
109
|
+
fn validate_password_accepts_non_empty_secret() {
|
|
110
|
+
let password = validate_password("vault-secret".to_string(), "prompt")
|
|
111
|
+
.expect("valid password");
|
|
112
|
+
assert_eq!(password, "vault-secret");
|
|
113
|
+
}
|
|
114
|
+
|
|
98
115
|
#[test]
|
|
99
116
|
fn resolve_vault_password_requires_stdin_in_non_interactive_mode() {
|
|
100
117
|
let err = resolve_vault_password(false, true).expect_err("must fail");
|
|
101
118
|
assert!(err.to_string().contains("use --vault-password-stdin"));
|
|
102
119
|
}
|
|
103
120
|
|
|
121
|
+
#[test]
|
|
122
|
+
fn resolve_output_target_covers_stdout_and_file_paths() {
|
|
123
|
+
let stdout_target = resolve_output_target(None, false).expect("default stdout");
|
|
124
|
+
assert!(matches!(stdout_target, OutputTarget::Stdout));
|
|
125
|
+
|
|
126
|
+
let file_target = resolve_output_target(Some(PathBuf::from("out.json")), false)
|
|
127
|
+
.expect("file output target");
|
|
128
|
+
assert!(matches!(
|
|
129
|
+
file_target,
|
|
130
|
+
OutputTarget::File {
|
|
131
|
+
path,
|
|
132
|
+
overwrite: false
|
|
133
|
+
} if path == PathBuf::from("out.json")
|
|
134
|
+
));
|
|
135
|
+
|
|
136
|
+
let err = resolve_output_target(Some(PathBuf::from("-")), true).expect_err("must fail");
|
|
137
|
+
assert!(err.to_string().contains("--overwrite cannot be used with --output -"));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#[test]
|
|
141
|
+
fn emit_output_to_stdout_and_print_status_cover_text_paths() {
|
|
142
|
+
emit_output("stdout output", &OutputTarget::Stdout).expect("stdout output");
|
|
143
|
+
assert!(should_print_status(OutputFormat::Text, false));
|
|
144
|
+
assert!(!should_print_status(OutputFormat::Json, false));
|
|
145
|
+
assert!(!should_print_status(OutputFormat::Text, true));
|
|
146
|
+
print_status("status output", OutputFormat::Text, false);
|
|
147
|
+
print_status("quiet output", OutputFormat::Text, true);
|
|
148
|
+
print_status("json output", OutputFormat::Json, false);
|
|
149
|
+
}
|
|
150
|
+
|
|
104
151
|
#[test]
|
|
105
152
|
#[cfg(unix)]
|
|
106
153
|
fn ensure_output_parent_rejects_symlinked_parent_directory() {
|
|
@@ -188,6 +235,46 @@ mod tests {
|
|
|
188
235
|
fs::remove_dir_all(&root).expect("cleanup temp tree");
|
|
189
236
|
}
|
|
190
237
|
|
|
238
|
+
#[test]
|
|
239
|
+
#[cfg(unix)]
|
|
240
|
+
fn ensure_output_parent_rejects_directory_path() {
|
|
241
|
+
use std::os::unix::fs::PermissionsExt;
|
|
242
|
+
|
|
243
|
+
let root = temp_path("vault-cli-admin-output-dir-path");
|
|
244
|
+
fs::create_dir_all(&root).expect("create root");
|
|
245
|
+
fs::set_permissions(&root, fs::Permissions::from_mode(0o700))
|
|
246
|
+
.expect("secure root permissions");
|
|
247
|
+
|
|
248
|
+
let err = ensure_output_parent(&root).expect_err("directory path must fail");
|
|
249
|
+
assert!(err
|
|
250
|
+
.to_string()
|
|
251
|
+
.contains("is a directory; provide a file path"));
|
|
252
|
+
|
|
253
|
+
fs::remove_dir_all(&root).expect("cleanup temp tree");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#[test]
|
|
257
|
+
#[cfg(unix)]
|
|
258
|
+
fn ensure_output_parent_allows_sticky_world_writable_ancestor() {
|
|
259
|
+
use std::os::unix::fs::PermissionsExt;
|
|
260
|
+
|
|
261
|
+
let root = temp_path("vault-cli-admin-output-sticky-ancestor");
|
|
262
|
+
let shared = root.join("shared");
|
|
263
|
+
let nested = shared.join("nested");
|
|
264
|
+
fs::create_dir_all(&nested).expect("create nested directory");
|
|
265
|
+
fs::set_permissions(&shared, fs::Permissions::from_mode(0o1777))
|
|
266
|
+
.expect("set sticky shared permissions");
|
|
267
|
+
fs::set_permissions(&nested, fs::Permissions::from_mode(0o700))
|
|
268
|
+
.expect("set secure nested permissions");
|
|
269
|
+
|
|
270
|
+
ensure_output_parent(&nested.join("output.json"))
|
|
271
|
+
.expect("sticky world-writable ancestor should be allowed");
|
|
272
|
+
|
|
273
|
+
fs::set_permissions(&shared, fs::Permissions::from_mode(0o700))
|
|
274
|
+
.expect("restore cleanup permissions");
|
|
275
|
+
fs::remove_dir_all(&root).expect("cleanup temp tree");
|
|
276
|
+
}
|
|
277
|
+
|
|
191
278
|
#[test]
|
|
192
279
|
#[cfg(unix)]
|
|
193
280
|
fn write_output_file_overwrite_replaces_existing_hard_link_instead_of_mutating_shared_inode() {
|
|
@@ -218,6 +305,67 @@ mod tests {
|
|
|
218
305
|
|
|
219
306
|
fs::remove_dir_all(&root).expect("cleanup temp tree");
|
|
220
307
|
}
|
|
308
|
+
|
|
309
|
+
#[test]
|
|
310
|
+
#[cfg(unix)]
|
|
311
|
+
fn write_output_file_creates_new_file_and_rejects_existing_without_overwrite() {
|
|
312
|
+
use std::os::unix::fs::PermissionsExt;
|
|
313
|
+
|
|
314
|
+
let root = temp_path("vault-cli-admin-output-create-file");
|
|
315
|
+
fs::create_dir_all(&root).expect("create root directory");
|
|
316
|
+
fs::set_permissions(&root, fs::Permissions::from_mode(0o700))
|
|
317
|
+
.expect("secure root directory permissions");
|
|
318
|
+
|
|
319
|
+
let output_path = root.join("output.json");
|
|
320
|
+
write_output_file(&output_path, "created", false).expect("write new output file");
|
|
321
|
+
assert_eq!(
|
|
322
|
+
fs::read_to_string(&output_path).expect("read created output"),
|
|
323
|
+
"created\n"
|
|
324
|
+
);
|
|
325
|
+
assert_eq!(
|
|
326
|
+
fs::metadata(&output_path)
|
|
327
|
+
.expect("metadata")
|
|
328
|
+
.permissions()
|
|
329
|
+
.mode()
|
|
330
|
+
& 0o777,
|
|
331
|
+
0o600
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
let err = write_output_file(&output_path, "second", false)
|
|
335
|
+
.expect_err("existing file without overwrite must fail");
|
|
336
|
+
assert!(err
|
|
337
|
+
.to_string()
|
|
338
|
+
.contains("already exists; pass --overwrite to replace it"));
|
|
339
|
+
|
|
340
|
+
fs::remove_dir_all(&root).expect("cleanup temp tree");
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
#[test]
|
|
344
|
+
#[cfg(unix)]
|
|
345
|
+
fn emit_output_to_file_target_writes_expected_content() {
|
|
346
|
+
use std::os::unix::fs::PermissionsExt;
|
|
347
|
+
|
|
348
|
+
let root = temp_path("vault-cli-admin-emit-output-file");
|
|
349
|
+
fs::create_dir_all(&root).expect("create root directory");
|
|
350
|
+
fs::set_permissions(&root, fs::Permissions::from_mode(0o700))
|
|
351
|
+
.expect("secure root directory permissions");
|
|
352
|
+
|
|
353
|
+
let output_path = root.join("output.json");
|
|
354
|
+
emit_output(
|
|
355
|
+
"file output",
|
|
356
|
+
&OutputTarget::File {
|
|
357
|
+
path: output_path.clone(),
|
|
358
|
+
overwrite: false,
|
|
359
|
+
},
|
|
360
|
+
)
|
|
361
|
+
.expect("emit output to file");
|
|
362
|
+
assert_eq!(
|
|
363
|
+
fs::read_to_string(&output_path).expect("read emitted file"),
|
|
364
|
+
"file output\n"
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
fs::remove_dir_all(&root).expect("cleanup temp tree");
|
|
368
|
+
}
|
|
221
369
|
}
|
|
222
370
|
|
|
223
371
|
pub(crate) fn resolve_output_format(
|