diff --git a/Cargo.lock b/Cargo.lock index 25e888968..c779b34bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1662,7 +1662,6 @@ dependencies = [ "reqwest 0.13.2", "ring", "rustls", - "rustls-pemfile", "scopeguard", "semver", "serde", @@ -5525,15 +5524,6 @@ dependencies = [ "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" version = "1.14.0" diff --git a/docs/reference/user-configuration.mdx b/docs/reference/user-configuration.mdx index fd5538946..4f4f18a4c 100644 --- a/docs/reference/user-configuration.mdx +++ b/docs/reference/user-configuration.mdx @@ -63,11 +63,6 @@ _version = 1 type = "http" url = "https://fabro.example.com/api/v1" -[cli.target.tls] -cert = "~/.fabro/tls/client.crt" -key = "~/.fabro/tls/client.key" -ca = "~/.fabro/tls/ca.crt" - [cli.exec] prevent_idle_sleep = true @@ -264,25 +259,6 @@ An explicit `http(s)://...` target is always remote-by-contract. Fabro does not `fabro exec` does not automatically use `[cli.target]`. It only routes model traffic through a Fabro server when you pass `--server` for that invocation. -### `[cli.target.tls]` section - -Optional client-certificate configuration for authenticating with an HTTP target. When present, the CLI presents a client certificate during the TLS handshake with your external HTTPS endpoint or reverse proxy. - -```toml title="settings.toml" -[cli.target.tls] -cert = "~/.fabro/tls/client.crt" -key = "~/.fabro/tls/client.key" -ca = "~/.fabro/tls/ca.crt" -``` - -| Key | Description | -|---|---| -| `cert` | Path to client certificate PEM file | -| `key` | Path to client private key PEM file | -| `ca` | Path to CA certificate PEM file (to verify the server) | - -Paths support `~/` expansion. - ## `[run.pull_request]` Enable auto-PR globally so workflows open a GitHub pull request on successful completion. diff --git a/lib/crates/fabro-cli/Cargo.toml b/lib/crates/fabro-cli/Cargo.toml index df4325601..6aadb6032 100644 --- a/lib/crates/fabro-cli/Cargo.toml +++ b/lib/crates/fabro-cli/Cargo.toml @@ -75,7 +75,6 @@ ulid.workspace = true scopeguard = "1" rustls = { version = "0.23", default-features = false, features = ["std", "ring"] } ring = "0.17" -rustls-pemfile = "2" x509-parser = "0.16" rand.workspace = true dialoguer.workspace = true diff --git a/lib/crates/fabro-cli/src/auth_store.rs b/lib/crates/fabro-cli/src/auth_store.rs index 179082723..f88291b1c 100644 --- a/lib/crates/fabro-cli/src/auth_store.rs +++ b/lib/crates/fabro-cli/src/auth_store.rs @@ -47,7 +47,7 @@ pub(crate) struct ServerTargetKey(String); impl ServerTargetKey { pub(crate) fn new(target: &ServerTarget) -> Result { match target { - ServerTarget::HttpUrl { api_url, .. } => canonical_http_target(api_url).map(Self), + ServerTarget::HttpUrl(api_url) => canonical_http_target(api_url).map(Self), ServerTarget::UnixSocket(path) => Ok(Self(format!( "unix://{}", canonical_socket_path(path)?.display() @@ -460,10 +460,7 @@ mod tests { } fn https_target(value: &str) -> ServerTarget { - ServerTarget::HttpUrl { - api_url: value.to_string(), - tls: None, - } + ServerTarget::HttpUrl(value.to_string()) } #[cfg(unix)] diff --git a/lib/crates/fabro-cli/src/commands/auth/login.rs b/lib/crates/fabro-cli/src/commands/auth/login.rs index a3a96f85b..b71437d1f 100644 --- a/lib/crates/fabro-cli/src/commands/auth/login.rs +++ b/lib/crates/fabro-cli/src/commands/auth/login.rs @@ -343,10 +343,7 @@ mod tests { #[test] fn token_transport_accepts_only_https_loopback_or_unix() { - let target = ServerTarget::HttpUrl { - api_url: "https://fabro.example.com".to_string(), - tls: None, - }; + let target = ServerTarget::HttpUrl("https://fabro.example.com".to_string()); assert_eq!( is_loopback_or_unix_socket(&target).unwrap(), LoopbackClassification::Https diff --git a/lib/crates/fabro-cli/src/commands/auth/logout.rs b/lib/crates/fabro-cli/src/commands/auth/logout.rs index 046390102..db18379fe 100644 --- a/lib/crates/fabro-cli/src/commands/auth/logout.rs +++ b/lib/crates/fabro-cli/src/commands/auth/logout.rs @@ -85,10 +85,7 @@ fn server_target_from_key(key: &ServerTargetKey) -> Result { return Ok(ServerTarget::UnixSocket(path.into())); } if value.starts_with("http://") || value.starts_with("https://") { - return Ok(ServerTarget::HttpUrl { - api_url: value, - tls: None, - }); + return Ok(ServerTarget::HttpUrl(value)); } bail!("invalid auth store server key `{value}`") } @@ -109,18 +106,14 @@ mod tests { #[test] fn rebuilds_server_target_from_http_key() { - let key = ServerTargetKey::new(&ServerTarget::HttpUrl { - api_url: "https://fabro.example.com/api/v1".to_string(), - tls: None, - }) + let key = ServerTargetKey::new(&ServerTarget::HttpUrl( + "https://fabro.example.com/api/v1".to_string(), + )) .unwrap(); assert_eq!( server_target_from_key(&key).unwrap(), - ServerTarget::HttpUrl { - api_url: "https://fabro.example.com".to_string(), - tls: None, - } + ServerTarget::HttpUrl("https://fabro.example.com".to_string()) ); } @@ -137,10 +130,9 @@ mod tests { #[test] fn warning_mentions_local_removal_and_remote_failure() { - let key = ServerTargetKey::new(&ServerTarget::HttpUrl { - api_url: "https://fabro.example.com".to_string(), - tls: None, - }) + let key = ServerTargetKey::new(&ServerTarget::HttpUrl( + "https://fabro.example.com".to_string(), + )) .unwrap(); let warning = format_warning(&key, "request failed with status 500"); assert!(warning.contains("removed local session")); diff --git a/lib/crates/fabro-cli/src/commands/exec.rs b/lib/crates/fabro-cli/src/commands/exec.rs index 758ba1440..7d3026557 100644 --- a/lib/crates/fabro-cli/src/commands/exec.rs +++ b/lib/crates/fabro-cli/src/commands/exec.rs @@ -369,7 +369,7 @@ pub(crate) async fn execute( permissions, output_format, ); - let server_target = user_config::exec_server_target(&args.server, &raw_settings)?; + let server_target = user_config::exec_server_target(&args.server)?; // v2 MCPs live under `cli.exec.agent.mcps` (owner-specific) or // `run.agent.mcps`. For `fabro exec` we use the cli.exec path, falling // back to run.agent.mcps if unset. diff --git a/lib/crates/fabro-cli/src/commands/install.rs b/lib/crates/fabro-cli/src/commands/install.rs index 5f916a91a..808018f4e 100644 --- a/lib/crates/fabro-cli/src/commands/install.rs +++ b/lib/crates/fabro-cli/src/commands/install.rs @@ -2113,13 +2113,12 @@ mod tests { .and_then(|c| c.target.as_ref()) .expect("cli.target should be set"); match target { - CliTargetLayer::Http { url, tls } => { + CliTargetLayer::Http { url } => { assert_eq!( url.as_ref() .map(fabro_types::settings::InterpString::as_source), Some("http://127.0.0.1:32276".to_string()) ); - assert!(tls.is_none()); } CliTargetLayer::Unix { .. } => panic!("expected http target"), } diff --git a/lib/crates/fabro-cli/src/commands/run/create.rs b/lib/crates/fabro-cli/src/commands/run/create.rs index 8019aa27c..48685ee1d 100644 --- a/lib/crates/fabro-cli/src/commands/run/create.rs +++ b/lib/crates/fabro-cli/src/commands/run/create.rs @@ -80,7 +80,7 @@ pub(crate) async fn create_run( .root() .to_path_buf(), ), - ServerTarget::HttpUrl { .. } => None, + ServerTarget::HttpUrl(_) => None, }; Ok(CreatedRun { diff --git a/lib/crates/fabro-cli/src/commands/version.rs b/lib/crates/fabro-cli/src/commands/version.rs index 4a45c2adf..a4eeb3dcd 100644 --- a/lib/crates/fabro-cli/src/commands/version.rs +++ b/lib/crates/fabro-cli/src/commands/version.rs @@ -133,7 +133,7 @@ fn is_non_release_profile(profile: &str) -> bool { fn format_server_target(target: &ServerTarget) -> String { match target { - ServerTarget::HttpUrl { api_url, .. } => api_url.clone(), + ServerTarget::HttpUrl(api_url) => api_url.clone(), ServerTarget::UnixSocket(path) => path.display().to_string(), } } diff --git a/lib/crates/fabro-cli/src/loopback_target.rs b/lib/crates/fabro-cli/src/loopback_target.rs index c41086322..96903111b 100644 --- a/lib/crates/fabro-cli/src/loopback_target.rs +++ b/lib/crates/fabro-cli/src/loopback_target.rs @@ -27,7 +27,7 @@ pub(crate) fn is_loopback_or_unix_socket( ) -> Result { match target { ServerTarget::UnixSocket(_) => Ok(LoopbackClassification::UnixSocket), - ServerTarget::HttpUrl { api_url, .. } => classify_http_target(api_url), + ServerTarget::HttpUrl(api_url) => classify_http_target(api_url), } } @@ -128,31 +128,19 @@ mod tests { fn classifies_https_loopback_and_unix_targets() { let cases = [ ( - ServerTarget::HttpUrl { - api_url: "https://fabro.example.com".to_string(), - tls: None, - }, + ServerTarget::HttpUrl("https://fabro.example.com".to_string()), LoopbackClassification::Https, ), ( - ServerTarget::HttpUrl { - api_url: "http://127.0.0.1:3000".to_string(), - tls: None, - }, + ServerTarget::HttpUrl("http://127.0.0.1:3000".to_string()), LoopbackClassification::LoopbackHttp, ), ( - ServerTarget::HttpUrl { - api_url: "http://[::1]:3000".to_string(), - tls: None, - }, + ServerTarget::HttpUrl("http://[::1]:3000".to_string()), LoopbackClassification::LoopbackHttp, ), ( - ServerTarget::HttpUrl { - api_url: "http://[::ffff:127.0.0.1]:3000".to_string(), - tls: None, - }, + ServerTarget::HttpUrl("http://[::ffff:127.0.0.1]:3000".to_string()), LoopbackClassification::LoopbackHttp, ), ( @@ -179,10 +167,7 @@ mod tests { ]; for api_url in cases { - let target = ServerTarget::HttpUrl { - api_url: api_url.to_string(), - tls: None, - }; + let target = ServerTarget::HttpUrl(api_url.to_string()); assert_eq!( is_loopback_or_unix_socket(&target).unwrap(), LoopbackClassification::Rejected @@ -192,10 +177,7 @@ mod tests { #[test] fn rejects_unsupported_schemes() { - let target = ServerTarget::HttpUrl { - api_url: "ftp://fabro.example.com".to_string(), - tls: None, - }; + let target = ServerTarget::HttpUrl("ftp://fabro.example.com".to_string()); let error = is_loopback_or_unix_socket(&target).unwrap_err(); assert!(error.to_string().contains("unsupported server URL scheme")); } diff --git a/lib/crates/fabro-cli/src/server_client.rs b/lib/crates/fabro-cli/src/server_client.rs index b59b0a3ed..730dc1db8 100644 --- a/lib/crates/fabro-cli/src/server_client.rs +++ b/lib/crates/fabro-cli/src/server_client.rs @@ -186,11 +186,7 @@ pub(crate) async fn connect_server_target(target: &user_config::ServerTarget) -> pub(crate) async fn connect_server_target_direct(target: &str) -> Result { if target.starts_with("http://") || target.starts_with("https://") { - connect_server_target(&user_config::ServerTarget::HttpUrl { - api_url: target.to_string(), - tls: None, - }) - .await + connect_server_target(&user_config::ServerTarget::HttpUrl(target.to_string())).await } else { let path = Path::new(target); if !path.is_absolute() { @@ -301,12 +297,11 @@ pub(crate) async fn connect_api_client(storage_dir: &Path) -> Result Result { match target { - user_config::ServerTarget::HttpUrl { api_url, tls } => { + user_config::ServerTarget::HttpUrl(api_url) => { let bearer = resolve_target_bearer(target, None, local_dev_token_fallback(target))?; let refreshable_oauth = refreshable_oauth(target, bearer.as_ref())?; let bundle = connect_remote_api_client_bundle( api_url, - tls.as_ref(), bearer.as_ref().map(ResolvedBearer::bearer_token), )?; Ok(Client::from_bundle( @@ -336,11 +331,10 @@ async fn connect_target_api_client_bundle(target: &user_config::ServerTarget) -> fn connect_remote_api_client_bundle( api_url: &str, - tls: Option<&user_config::ClientTlsSettings>, bearer_token: Option<&str>, ) -> Result { let normalized = user_config::normalized_http_base_url(api_url); - let mut builder = user_config::build_server_client_builder(tls)?; + let mut builder = user_config::cli_http_client_builder(); builder = match bearer_token { Some(token) => apply_bearer_token_auth(builder, token)?, None => builder, @@ -760,8 +754,8 @@ impl Client { bearer_token: Option<&str>, ) -> Result<()> { let bundle = match target { - user_config::ServerTarget::HttpUrl { api_url, tls } => { - connect_remote_api_client_bundle(api_url, tls.as_ref(), bearer_token)? + user_config::ServerTarget::HttpUrl(api_url) => { + connect_remote_api_client_bundle(api_url, bearer_token)? } user_config::ServerTarget::UnixSocket(path) => { connect_unix_socket_api_client_bundle(path, None, bearer_token).await? @@ -1880,10 +1874,8 @@ mod tests { #[test] fn explicit_http_targets_do_not_allow_local_dev_token_fallback() { - let target = user_config::ServerTarget::HttpUrl { - api_url: "https://fabro.example.com/api/v1".to_string(), - tls: None, - }; + let target = + user_config::ServerTarget::HttpUrl("https://fabro.example.com/api/v1".to_string()); assert!(!local_dev_token_fallback(&target)); } @@ -1916,10 +1908,7 @@ mod tests { async fn refresh_access_token_rejects_plain_http_non_loopback_targets() { let temp = tempfile::tempdir().unwrap(); let auth_store = AuthStore::new(temp.path().join("auth.json")); - let target = user_config::ServerTarget::HttpUrl { - api_url: "http://fabro.example.com".to_string(), - tls: None, - }; + let target = user_config::ServerTarget::HttpUrl("http://fabro.example.com".to_string()); let key = ServerTargetKey::new(&target).unwrap(); auth_store.put(&key, oauth_entry("octocat")).unwrap(); diff --git a/lib/crates/fabro-cli/src/user_config.rs b/lib/crates/fabro-cli/src/user_config.rs index 671105d75..e82cf759c 100644 --- a/lib/crates/fabro-cli/src/user_config.rs +++ b/lib/crates/fabro-cli/src/user_config.rs @@ -1,27 +1,13 @@ -#![expect( - clippy::disallowed_methods, - reason = "CLI user config: sync file I/O loading user config" -)] - use std::fmt; use std::path::{Path, PathBuf}; -use anyhow::{Context, Result, bail}; +use anyhow::{Result, bail}; pub(crate) use fabro_config::user::*; use fabro_types::settings::cli::CliTargetSettings; use fabro_types::settings::{CliSettings, SettingsLayer}; use fabro_util::version::FABRO_VERSION; -use serde::{Deserialize, Serialize}; use tracing::debug; -/// Client-side TLS material for the CLI's remote server target. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -pub(crate) struct ClientTlsSettings { - pub cert: PathBuf, - pub key: PathBuf, - pub ca: PathBuf, -} - use crate::args::ServerTargetArgs; pub(crate) fn load_settings() -> anyhow::Result { @@ -76,17 +62,14 @@ pub(crate) fn apply_storage_dir_override( #[derive(Debug, Clone, PartialEq)] pub(crate) enum ServerTarget { - HttpUrl { - api_url: String, - tls: Option, - }, + HttpUrl(String), UnixSocket(PathBuf), } impl fmt::Display for ServerTarget { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::HttpUrl { api_url, .. } => f.write_str(api_url), + Self::HttpUrl(api_url) => f.write_str(api_url), Self::UnixSocket(path) => write!(f, "unix://{}", path.display()), } } @@ -101,8 +84,8 @@ pub(crate) fn build_public_http_client( target: &ServerTarget, ) -> Result<(fabro_http::HttpClient, String)> { match target { - ServerTarget::HttpUrl { api_url, tls } => { - let http_client = build_server_client_builder(tls.as_ref())?.build()?; + ServerTarget::HttpUrl(api_url) => { + let http_client = cli_http_client_builder().build()?; Ok((http_client, normalized_http_base_url(api_url).to_string())) } ServerTarget::UnixSocket(path) => { @@ -124,29 +107,21 @@ pub(crate) fn build_public_http_client( } /// Pull the resolved CLI target configuration out of `[cli.target]`. -/// Returns `(target_string, tls)` where `target_string` is either an -/// http(s) URL or a unix socket path. -fn cli_target_from_settings(settings: &CliSettings) -> Option<(String, Option)> { +/// Returns either an http(s) URL or a unix socket path. +fn cli_target_from_settings(settings: &CliSettings) -> Option { let target = settings.target.as_ref()?; match target { - CliTargetSettings::Http { url, tls } => { - let tls_settings = tls.as_ref().map(|tls| ClientTlsSettings { - cert: PathBuf::from(tls.cert.as_source()), - key: PathBuf::from(tls.key.as_source()), - ca: PathBuf::from(tls.ca.as_source()), - }); - Some((url.as_source(), tls_settings)) - } - CliTargetSettings::Unix { path } => Some((path.as_source(), None)), + CliTargetSettings::Http { url } => Some(url.as_source()), + CliTargetSettings::Unix { path } => Some(path.as_source()), } } fn configured_server_target(settings: &SettingsLayer) -> Result> { let cli_settings = resolve_cli_settings(settings)?; - let Some((value, tls)) = cli_target_from_settings(&cli_settings) else { + let Some(value) = cli_target_from_settings(&cli_settings) else { return Ok(None); }; - parse_server_target(&value, tls).map(Some) + parse_server_target(&value).map(Some) } pub(crate) fn default_server_target() -> ServerTarget { @@ -177,12 +152,9 @@ pub(crate) fn storage_dir(settings: &SettingsLayer) -> anyhow::Result { Ok(PathBuf::from(resolved_root.value)) } -fn parse_server_target(value: &str, tls: Option) -> Result { +fn parse_server_target(value: &str) -> Result { if value.starts_with("http://") || value.starts_with("https://") { - return Ok(ServerTarget::HttpUrl { - api_url: value.to_string(), - tls, - }); + return Ok(ServerTarget::HttpUrl(value.to_string())); } let path = Path::new(value); @@ -193,26 +165,15 @@ fn parse_server_target(value: &str, tls: Option) -> Result Result> { - let cli_settings = resolve_cli_settings(settings)?; - args.as_deref() - .map(|value| { - parse_server_target( - value, - cli_target_from_settings(&cli_settings).and_then(|(_, tls)| tls), - ) - }) - .transpose() +fn explicit_server_target(args: &ServerTargetArgs) -> Result> { + args.as_deref().map(parse_server_target).transpose() } pub(crate) fn resolve_nondefault_server_target( args: &ServerTargetArgs, settings: &SettingsLayer, ) -> Result> { - Ok(explicit_server_target(args, settings)?.or(configured_server_target(settings)?)) + Ok(explicit_server_target(args)?.or(configured_server_target(settings)?)) } pub(crate) fn resolve_server_target( @@ -222,11 +183,8 @@ pub(crate) fn resolve_server_target( Ok(resolve_nondefault_server_target(args, settings)?.unwrap_or_else(default_server_target)) } -pub(crate) fn exec_server_target( - args: &ServerTargetArgs, - settings: &SettingsLayer, -) -> Result> { - let target = explicit_server_target(args, settings)?; +pub(crate) fn exec_server_target(args: &ServerTargetArgs) -> Result> { + let target = explicit_server_target(args)?; debug!(?target, "Resolved exec server target"); Ok(target) } @@ -235,37 +193,6 @@ pub(crate) fn cli_http_client_builder() -> fabro_http::HttpClientBuilder { fabro_http::HttpClientBuilder::new().user_agent(format!("fabro-cli/{FABRO_VERSION}")) } -pub(crate) fn build_server_client_builder( - tls: Option<&ClientTlsSettings>, -) -> anyhow::Result { - let Some(tls) = tls else { - return Ok(cli_http_client_builder()); - }; - - let cert_path = fabro_config::expand_tilde(&tls.cert); - let key_path = fabro_config::expand_tilde(&tls.key); - let ca_path = fabro_config::expand_tilde(&tls.ca); - - let cert_pem = std::fs::read(&cert_path) - .with_context(|| format!("reading TLS client certificate {}", cert_path.display()))?; - let key_pem = std::fs::read(&key_path) - .with_context(|| format!("reading TLS client key {}", key_path.display()))?; - let ca_pem = std::fs::read(&ca_path) - .with_context(|| format!("reading TLS CA certificate {}", ca_path.display()))?; - - let mut identity_pem = cert_pem; - identity_pem.push(b'\n'); - identity_pem.extend_from_slice(&key_pem); - - let identity = fabro_http::Identity::from_pem(&identity_pem)?; - let ca_cert = fabro_http::Certificate::from_pem(&ca_pem)?; - - Ok(cli_http_client_builder() - .use_rustls_tls() - .identity(identity) - .add_root_certificate(ca_cert)) -} - #[cfg(test)] mod tests { use fabro_config::parse_settings_layer; @@ -285,53 +212,28 @@ mod tests { #[test] fn exec_has_no_server_target_by_default() { - let settings = SettingsLayer::default(); - assert_eq!( - exec_server_target(&server_target_args(None), &settings).unwrap(), - None - ); + assert_eq!(exec_server_target(&server_target_args(None)).unwrap(), None); } #[test] fn exec_uses_cli_server_target() { - let settings = SettingsLayer::default(); assert_eq!( - exec_server_target( - &server_target_args(Some("https://cli.example.com")), - &settings - ) - .unwrap(), - Some(ServerTarget::HttpUrl { - api_url: "https://cli.example.com".to_string(), - tls: None, - }) + exec_server_target(&server_target_args(Some("https://cli.example.com"))).unwrap(), + Some(ServerTarget::HttpUrl("https://cli.example.com".to_string())) ); } #[test] fn exec_supports_explicit_unix_socket_target() { - let settings = SettingsLayer::default(); assert_eq!( - exec_server_target(&server_target_args(Some("/tmp/fabro.sock")), &settings).unwrap(), + exec_server_target(&server_target_args(Some("/tmp/fabro.sock"))).unwrap(), Some(ServerTarget::UnixSocket(PathBuf::from("/tmp/fabro.sock"))) ); } #[test] fn exec_ignores_configured_server_target_without_cli_override() { - let settings = parse_v2( - r#" -_version = 1 - -[cli.target] -type = "http" -url = "https://config.example.com" -"#, - ); - assert_eq!( - exec_server_target(&server_target_args(None), &settings).unwrap(), - None - ); + assert_eq!(exec_server_target(&server_target_args(None)).unwrap(), None); } #[test] @@ -347,10 +249,7 @@ url = "https://config.example.com" ); assert_eq!( resolve_server_target(&server_target_args(None), &settings).unwrap(), - ServerTarget::HttpUrl { - api_url: "https://config.example.com".to_string(), - tls: None, - } + ServerTarget::HttpUrl("https://config.example.com".to_string()) ); } @@ -371,10 +270,7 @@ url = "https://config.example.com" &settings ) .unwrap(), - ServerTarget::HttpUrl { - api_url: "https://cli.example.com".to_string(), - tls: None, - } + ServerTarget::HttpUrl("https://cli.example.com".to_string()) ); } @@ -404,52 +300,13 @@ url = "https://config.example.com" &settings ) .unwrap(), - ServerTarget::HttpUrl { - api_url: "https://cli.example.com".to_string(), - tls: None, - } - ); - } - - #[test] - fn remote_target_uses_tls_from_config() { - let expected_tls = ClientTlsSettings { - cert: PathBuf::from("cert.pem"), - key: PathBuf::from("key.pem"), - ca: PathBuf::from("ca.pem"), - }; - let settings = parse_v2( - r#" -_version = 1 - -[cli.target] -type = "http" -url = "https://config.example.com" - -[cli.target.tls] -cert = "cert.pem" -key = "key.pem" -ca = "ca.pem" -"#, - ); - assert_eq!( - exec_server_target( - &server_target_args(Some("https://cli.example.com")), - &settings - ) - .unwrap(), - Some(ServerTarget::HttpUrl { - api_url: "https://cli.example.com".to_string(), - tls: Some(expected_tls), - }) + ServerTarget::HttpUrl("https://cli.example.com".to_string()) ); } #[test] fn invalid_server_target_is_rejected() { - let settings = SettingsLayer::default(); - let error = - exec_server_target(&server_target_args(Some("fabro.internal")), &settings).unwrap_err(); + let error = exec_server_target(&server_target_args(Some("fabro.internal"))).unwrap_err(); assert_eq!( error.to_string(), "server target must be an http(s) URL or absolute Unix socket path" diff --git a/lib/crates/fabro-config/src/resolve/cli.rs b/lib/crates/fabro-config/src/resolve/cli.rs index de7c8303a..4905359d1 100644 --- a/lib/crates/fabro-config/src/resolve/cli.rs +++ b/lib/crates/fabro-config/src/resolve/cli.rs @@ -1,7 +1,7 @@ use fabro_types::settings::cli::{ CliAuthSettings, CliExecAgentSettings, CliExecLayer, CliExecModelSettings, CliExecSettings, CliLayer, CliLoggingSettings, CliOutputSettings, CliSettings, CliTargetLayer, - CliTargetSettings, CliTargetTlsSettings, CliUpdatesSettings, + CliTargetSettings, CliUpdatesSettings, }; use super::{ResolveError, require_interp}; @@ -46,13 +46,8 @@ fn resolve_target( errors: &mut Vec, ) -> Option { match target { - Some(CliTargetLayer::Http { url, tls }) => Some(CliTargetSettings::Http { + Some(CliTargetLayer::Http { url }) => Some(CliTargetSettings::Http { url: require_interp(url.as_ref(), "cli.target.url", errors), - tls: tls.as_ref().map(|tls| CliTargetTlsSettings { - cert: require_interp(tls.cert.as_ref(), "cli.target.tls.cert", errors), - key: require_interp(tls.key.as_ref(), "cli.target.tls.key", errors), - ca: require_interp(tls.ca.as_ref(), "cli.target.tls.ca", errors), - }), }), Some(CliTargetLayer::Unix { path }) => Some(CliTargetSettings::Unix { path: require_interp(path.as_ref(), "cli.target.path", errors), diff --git a/lib/crates/fabro-config/tests/resolve_cli.rs b/lib/crates/fabro-config/tests/resolve_cli.rs index 9f4c05a65..e23f0fce8 100644 --- a/lib/crates/fabro-config/tests/resolve_cli.rs +++ b/lib/crates/fabro-config/tests/resolve_cli.rs @@ -27,11 +27,6 @@ _version = 1 type = "http" url = "https://config.example.com" -[cli.target.tls] -cert = "cert.pem" -key = "key.pem" -ca = "ca.pem" - [cli.exec] prevent_idle_sleep = true @@ -61,14 +56,10 @@ level = "debug" let cli = resolve_cli_from_file(&settings).expect("cli settings should resolve"); - let CliTargetSettings::Http { url, tls } = cli.target.expect("target") else { + let CliTargetSettings::Http { url } = cli.target.expect("target") else { panic!("expected http target"); }; assert_eq!(url.as_source(), "https://config.example.com"); - let tls = tls.expect("tls"); - assert_eq!(tls.cert.as_source(), "cert.pem"); - assert_eq!(tls.key.as_source(), "key.pem"); - assert_eq!(tls.ca.as_source(), "ca.pem"); assert!(cli.exec.prevent_idle_sleep); assert_eq!( diff --git a/lib/crates/fabro-http/src/lib.rs b/lib/crates/fabro-http/src/lib.rs index aea2fcf63..06d94ad56 100644 --- a/lib/crates/fabro-http/src/lib.rs +++ b/lib/crates/fabro-http/src/lib.rs @@ -9,7 +9,6 @@ use std::path::Path; use std::time::Duration; pub use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; -pub use reqwest::tls::{Certificate, Identity}; pub use reqwest::{ Body, Method, RequestBuilder, Response, StatusCode, Url, header, multipart, tls, }; @@ -143,30 +142,12 @@ macro_rules! define_builder { self } - #[must_use] - pub fn use_rustls_tls(mut self) -> Self { - self.inner = self.inner.use_rustls_tls(); - self - } - #[must_use] pub fn danger_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> Self { self.inner = self.inner.danger_accept_invalid_certs(accept_invalid_certs); self } - #[must_use] - pub fn add_root_certificate(mut self, cert: Certificate) -> Self { - self.inner = self.inner.add_root_certificate(cert); - self - } - - #[must_use] - pub fn identity(mut self, identity: Identity) -> Self { - self.inner = self.inner.identity(identity); - self - } - #[cfg(unix)] #[must_use] pub fn unix_socket

(mut self, path: P) -> Self diff --git a/lib/crates/fabro-types/src/settings/cli.rs b/lib/crates/fabro-types/src/settings/cli.rs index 177484feb..def190005 100644 --- a/lib/crates/fabro-types/src/settings/cli.rs +++ b/lib/crates/fabro-types/src/settings/cli.rs @@ -26,20 +26,8 @@ pub struct CliSettings { #[derive(Debug, Clone, PartialEq, Serialize)] #[serde(tag = "type", rename_all = "lowercase")] pub enum CliTargetSettings { - Http { - url: InterpString, - tls: Option, - }, - Unix { - path: InterpString, - }, -} - -#[derive(Debug, Clone, PartialEq, Serialize)] -pub struct CliTargetTlsSettings { - pub cert: InterpString, - pub key: InterpString, - pub ca: InterpString, + Http { url: InterpString }, + Unix { path: InterpString }, } #[derive(Debug, Clone, Default, PartialEq, Serialize)] @@ -107,8 +95,6 @@ pub enum CliTargetLayer { Http { #[serde(default)] url: Option, - #[serde(default)] - tls: Option, }, Unix { #[serde(default)] @@ -116,17 +102,6 @@ pub enum CliTargetLayer { }, } -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct CliTargetTlsLayer { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub cert: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub key: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub ca: Option, -} - /// `[cli.auth]` — explicit auth strategy selection. #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] @@ -141,7 +116,6 @@ pub struct CliAuthLayer { pub enum CliAuthStrategy { None, Jwt, - Mtls, } /// `[cli.exec]` — `fabro exec` defaults. diff --git a/lib/crates/fabro-types/src/settings/mod.rs b/lib/crates/fabro-types/src/settings/mod.rs index 5a034b8d9..e2851b37d 100644 --- a/lib/crates/fabro-types/src/settings/mod.rs +++ b/lib/crates/fabro-types/src/settings/mod.rs @@ -25,8 +25,7 @@ pub mod workflow; pub use cli::{ CliAuthSettings, CliExecAgentSettings, CliExecModelSettings, CliExecSettings, CliLayer, - CliLoggingSettings, CliOutputSettings, CliSettings, CliTargetSettings, CliTargetTlsSettings, - CliUpdatesSettings, + CliLoggingSettings, CliOutputSettings, CliSettings, CliTargetSettings, CliUpdatesSettings, }; pub use duration::{Duration, ParseDurationError}; pub use features::{FeaturesLayer, FeaturesSettings}; diff --git a/lib/crates/fabro-types/src/settings/resolved.rs b/lib/crates/fabro-types/src/settings/resolved.rs index 16ad7a310..1c196f468 100644 --- a/lib/crates/fabro-types/src/settings/resolved.rs +++ b/lib/crates/fabro-types/src/settings/resolved.rs @@ -23,7 +23,7 @@ mod tests { use serde_json::json; use super::Settings; - use crate::settings::cli::{CliTargetSettings, CliTargetTlsSettings}; + use crate::settings::cli::CliTargetSettings; use crate::settings::interp::InterpString; use crate::settings::run::{ DockerfileSource, McpServerSettings, McpTransport, RunAgentSettings, RunGoal, RunSettings, @@ -42,21 +42,11 @@ mod tests { assert_eq!( serde_json::to_value(CliTargetSettings::Http { url: InterpString::parse("https://api.example.com"), - tls: Some(CliTargetTlsSettings { - cert: InterpString::parse("/tmp/client.crt"), - key: InterpString::parse("/tmp/client.key"), - ca: InterpString::parse("/tmp/ca.pem"), - }), }) .unwrap(), json!({ "type": "http", "url": "https://api.example.com", - "tls": { - "cert": "/tmp/client.crt", - "key": "/tmp/client.key", - "ca": "/tmp/ca.pem" - } }) );