Skip to content

Commit 4017860

Browse files
UnbreakableMJclaude
andcommitted
feat(vault-agent, vault-ipc, vault-cli): M3 — agent state machine, UDS IPC, CLI subcommands
vault-ipc: CBOR-framed request/response protocol over a Unix domain socket with proto, socket, and transport modules plus a length-prefix transport round-trip test suite. vault-agent: long-lived daemon that holds the unwrapped user key. New state, server, and unlock modules implement lock/unlock state, an idle-lock policy, and a one-task-per-connection accept loop bound at $XDG_RUNTIME_DIR/ vault/agent.sock with mode 0700 on the parent and 0600 on the socket. Inline tests cover state transitions and an end-to-end Status → locked-Get → Quit drive against a tempfile socket. vault-cli: vault binary gains status / unlock / lock / sync / list / get / stop-agent subcommands that talk to the agent over the UDS protocol, with --json output on the read paths and dedicated exit codes per IpcError variant. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 9572930 commit 4017860

17 files changed

Lines changed: 1965 additions & 20 deletions

File tree

Cargo.lock

Lines changed: 62 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ time = { version = "0.3", features = ["serde", "serde-well-known"] }
7171
fs2 = "0.4"
7272

7373
tempfile = "3"
74+
ciborium = "0.2"
75+
ciborium-io = "0.2"
7476

7577
# Dev-only.
7678
wiremock = "0.6"

crates/vault-agent/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ workspace = true
2222
[dependencies]
2323
anyhow = { workspace = true }
2424
tokio = { workspace = true }
25+
clap = { workspace = true }
26+
serde_json = { workspace = true }
27+
zeroize = { workspace = true }
28+
uuid = { workspace = true }
29+
url = { workspace = true }
30+
thiserror = { workspace = true }
2531
vault-core = { path = "../vault-core" }
2632
vault-ipc = { path = "../vault-ipc" }
2733
vault-store = { path = "../vault-store" }
34+
vault-api = { path = "../vault-api" }
35+
36+
[dev-dependencies]
37+
tempfile = { workspace = true }

crates/vault-agent/src/main.rs

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,76 @@
11
// SPDX-License-Identifier: GPL-3.0-or-later
22

3-
//! Vault agent — long-lived daemon holding the decrypted master key.
3+
//! Vault agent — long-lived daemon that holds the user symmetric key.
44
//!
5-
//! Stub binary at M0. See PRD §7.3.
5+
//! Run with no arguments to bind the default socket
6+
//! (`$XDG_RUNTIME_DIR/vault/agent.sock`). Override with `--socket PATH` or
7+
//! `VAULT_AGENT_SOCK`. The agent does NOT yet daemonise itself — start it
8+
//! with `nohup vault-agent &` or run it under a systemd user unit. M5 adds
9+
//! auto-spawn from the CLI.
610
7-
fn main() {
8-
eprintln!("vault-agent: stub binary (M0) — see PRD §7.3");
11+
#![forbid(unsafe_code)]
12+
13+
use std::path::PathBuf;
14+
use std::sync::Arc;
15+
16+
use clap::Parser;
17+
use tokio::sync::Mutex;
18+
19+
use vault_ipc::default_socket_path;
20+
21+
mod server;
22+
mod state;
23+
mod unlock;
24+
25+
use state::AgentState;
26+
27+
const ATTRIBUTION: &str = "\
28+
Maintained by Mohamed Hammad <Mohamed.Hammad@SpacecraftSoftware.org>
29+
Copyright (C) 2026 Mohamed Hammad & Spacecraft Software | License: GPL-3.0-or-later
30+
https://Vault.SpacecraftSoftware.org/";
31+
32+
#[derive(Parser, Debug)]
33+
#[command(
34+
name = "vault-agent",
35+
version = env!("CARGO_PKG_VERSION"),
36+
about = "Vault agent — holds the unwrapped user key behind a Unix socket",
37+
after_help = ATTRIBUTION,
38+
after_long_help = ATTRIBUTION,
39+
)]
40+
struct Args {
41+
/// Bind path. Defaults to `$VAULT_AGENT_SOCK` or `$XDG_RUNTIME_DIR/vault/agent.sock`.
42+
#[arg(long, short = 's')]
43+
socket: Option<PathBuf>,
44+
/// Idle-lock timeout in seconds. The agent zeroises its keys after this
45+
/// many seconds with no client activity. `0` disables auto-lock.
46+
#[arg(long, default_value_t = 900)]
47+
idle_lock_secs: u64,
48+
}
49+
50+
#[tokio::main(flavor = "current_thread")]
51+
async fn main() -> anyhow::Result<()> {
52+
let args = Args::parse();
53+
let path = pick_socket(args.socket)?;
54+
let state = Arc::new(Mutex::new(AgentState::new(args.idle_lock_secs)));
55+
56+
if args.idle_lock_secs > 0 {
57+
let st = state.clone();
58+
tokio::spawn(server::idle_lock_loop(st));
59+
}
60+
61+
server::run(path, state).await?;
62+
Ok(())
63+
}
64+
65+
fn pick_socket(cli: Option<PathBuf>) -> anyhow::Result<PathBuf> {
66+
if let Some(p) = cli {
67+
return Ok(p);
68+
}
69+
if let Ok(env_path) = std::env::var("VAULT_AGENT_SOCK") {
70+
if let Some(p) = vault_ipc::sanitize_socket_path(&env_path) {
71+
return Ok(p);
72+
}
73+
anyhow::bail!("VAULT_AGENT_SOCK is not an absolute path: {env_path}");
74+
}
75+
default_socket_path().ok_or_else(|| anyhow::anyhow!("no XDG_RUNTIME_DIR / TMPDIR available"))
976
}

0 commit comments

Comments
 (0)