Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ warp_isolation_platform = { path = "crates/isolation_platform" }
warp_js = { path = "crates/warp_js" }
warp_logging = { path = "crates/warp_logging" }
warp_managed_secrets = { path = "crates/managed_secrets" }
warp_vault = { path = "crates/vault" }
warp_ripgrep = { path = "crates/warp_ripgrep" }
warp_server_client = { path = "crates/warp_server_client" }
warp_terminal = { path = "crates/warp_terminal" }
Expand Down
1 change: 1 addition & 0 deletions app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ rmcp = { workspace = true, features = ["client"] }
warp_isolation_platform.workspace = true
warp_ripgrep.workspace = true
warp_managed_secrets.workspace = true
warp_vault.workspace = true

[target.'cfg(target_os = "macos")'.dependencies]
block.workspace = true
Expand Down
5 changes: 5 additions & 0 deletions app/src/ai/agent_sdk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use warp_cli::{
schedule::ScheduleSubcommand,
secret::SecretCommand,
share::ShareRequest,
vault::VaultCommand,
task::{MessageCommand, TaskCommand},
CliCommand, GlobalOptions,
};
Expand Down Expand Up @@ -101,6 +102,7 @@ pub(crate) mod retry;
mod schedule;
mod secret;
mod telemetry;
mod vault;
#[cfg(test)]
mod test_support;
mod text_layout;
Expand Down Expand Up @@ -180,6 +182,9 @@ fn dispatch_command(
}
secret::run(ctx, global_options, secret_cmd)
}
CliCommand::Vault(vault_cmd) => {
vault::run(ctx, global_options, vault_cmd)
}
CliCommand::Federate(federate_cmd) => {
if !FeatureFlag::OzIdentityFederation.is_enabled() {
return Err(anyhow::anyhow!("invalid value 'federate'"));
Expand Down
41 changes: 41 additions & 0 deletions app/src/ai/agent_sdk/vault.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use anyhow::Result;
use warp_cli::{vault::VaultCommand, GlobalOptions};
use warp_vault::{
config::{ProviderType, VaultConfig},
inject_secrets,
provider::aws::AwsProvider,
};
use warpui::AppContext;

pub fn run(_ctx: &mut AppContext, _global_options: GlobalOptions, command: VaultCommand) -> Result<()> {
match command {
VaultCommand::Inject(args) => {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let config = VaultConfig::load()?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] Loading the config before checking CLI args makes oz vault inject <path> --as <env> fail when ~/.warp/vault.toml is absent, despite the CLI advertising that path/--as can be used instead. Defer config loading until config-backed mappings are needed or allow AWS defaults for one-off injections.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] Loading the config before checking CLI args makes oz vault inject <path> --as <env> fail when ~/.warp/vault.toml is absent, despite the CLI advertising that path/--as can be used instead. Defer config loading until config-backed mappings are needed or allow AWS defaults for one-off injections.


let mappings = if let (Some(path), Some(env_var)) = (args.path, args.env_var) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] Passing only path or only --as silently falls back to config mappings, so a typo or missing argument can inject the wrong configured secrets. Reject partial overrides with clap requirements or an explicit error.

vec![warp_vault::config::SecretMapping { path, env_var }]
} else {
config.mappings()
};

if mappings.is_empty() {
anyhow::bail!("vault: no mappings found — add entries to ~/.warp/vault.toml or pass a path and --as flag");
}

let provider = match config.provider.provider_type {
ProviderType::Aws => AwsProvider::new(config.provider.region).await?,
};

let secrets = inject_secrets(&provider, &mappings).await?;

for secret in &secrets {
println!("✓ {} injected as ${}", secret.env_var, secret.env_var);
}

Ok(())
})
}
}
}
19 changes: 19 additions & 0 deletions crates/vault/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "warp_vault"
version = "0.1.0"
authors.workspace = true
publish.workspace = true
license.workspace = true
edition = "2024"

[dependencies]
anyhow.workspace = true
async-trait.workspace = true
log.workspace = true
serde.workspace = true
toml = "0.8"
zeroize = "1.8"
dirs = "5"
tokio = { version = "1", features = ["full"] }
aws-config = { version = "1", features = ["behavior-version-latest"] }
aws-sdk-secretsmanager = "1"
63 changes: 63 additions & 0 deletions crates/vault/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#[cfg(test)]
#[path = "config_tests.rs"]
mod tests;

use std::{collections::HashMap, fs, path::PathBuf};

use anyhow::{Context, Result};
use serde::Deserialize;

#[derive(Debug, Deserialize)]
pub struct VaultConfig {
pub provider: ProviderConfig,
#[serde(default)]
pub mappings: HashMap<String, String>,
}

#[derive(Debug, Deserialize)]
pub struct ProviderConfig {
#[serde(rename = "type")]
pub provider_type: ProviderType,
pub region: Option<String>,
}

#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ProviderType {
Aws,
}

pub struct SecretMapping {
pub path: String,
pub env_var: String,
}

impl VaultConfig {
pub fn load() -> Result<Self> {
let path = config_path();
let contents = fs::read_to_string(&path).with_context(|| {
format!(
"no config found at {} — run 'oz vault init' to get started",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] This error sends users to oz vault init, but this PR only registers the inject subcommand. Add the init command or replace the message with actionable manual config instructions.

path.display()
)
})?;
toml::from_str(&contents).context("failed to parse vault config")
}

pub fn mappings(&self) -> Vec<SecretMapping> {
self.mappings
.iter()
.map(|(path, env_var)| SecretMapping {
path: path.clone(),
env_var: env_var.clone(),
})
.collect()
}
}

fn config_path() -> PathBuf {
dirs::home_dir()
.unwrap_or_default()
.join(".warp")
.join("vault.toml")
}
Loading