diff --git a/crates/worker-runtime/src/auth.rs b/crates/worker-runtime/src/auth.rs index 689d2a67..ba233d36 100644 --- a/crates/worker-runtime/src/auth.rs +++ b/crates/worker-runtime/src/auth.rs @@ -51,8 +51,8 @@ pub struct RuntimeIdentityMaterial { impl RuntimeIdentityMaterial { pub fn generate(identity_id: impl Into) -> Result { let rng = SystemRandom::new(); - let pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng) - .map_err(|_| RuntimeAuthError::KeyGeneration)?; + let pkcs8 = + Ed25519KeyPair::generate_pkcs8(&rng).map_err(|_| RuntimeAuthError::KeyGeneration)?; let pair = Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()) .map_err(|_| RuntimeAuthError::InvalidPrivateKey)?; Ok(Self { @@ -131,7 +131,10 @@ impl CapabilityTokenSigner { let payload = URL_SAFE_NO_PAD.encode(payload); let signing_input = format!("{SIGNING_INPUT_PREFIX}{payload}"); let signature = pair.sign(signing_input.as_bytes()); - Ok(format!("{TOKEN_PREFIX}.{payload}.{}", URL_SAFE_NO_PAD.encode(signature.as_ref()))) + Ok(format!( + "{TOKEN_PREFIX}.{payload}.{}", + URL_SAFE_NO_PAD.encode(signature.as_ref()) + )) } } @@ -185,7 +188,11 @@ pub fn verify_capability_token( return Err(RuntimeAuthError::Expired); } if let Some(required) = required_permission { - if !claims.permissions.iter().any(|permission| permission == required) { + if !claims + .permissions + .iter() + .any(|permission| permission == required) + { return Err(RuntimeAuthError::MissingPermission(required.to_string())); } } diff --git a/crates/worker-runtime/src/http_server.rs b/crates/worker-runtime/src/http_server.rs index 7382f1bb..4ba47685 100644 --- a/crates/worker-runtime/src/http_server.rs +++ b/crates/worker-runtime/src/http_server.rs @@ -6,8 +6,10 @@ //! Runtime process directly; a backend is expected to own any browser-facing //! credentials, registration, and policy. -use crate::auth::{RuntimeAuthContext, RuntimeHttpAuthConfig, unix_now_seconds, verify_capability_token}; use crate::Runtime; +use crate::auth::{ + RuntimeAuthContext, RuntimeHttpAuthConfig, unix_now_seconds, verify_capability_token, +}; use crate::catalog::{ ConfigBundleRef, CreateWorkerRequest, WorkerDetail, WorkerLifecycleAck, WorkerSummary, WorkingDirectoryRequest, WorkingDirectoryStatus, @@ -124,7 +126,11 @@ pub async fn serve_runtime_http_with_auth( local_token: Option, auth: Option, ) -> Result<(), RuntimeHttpServerError> { - axum::serve(listener, runtime_http_router_with_auth(runtime, local_token, auth)).await?; + axum::serve( + listener, + runtime_http_router_with_auth(runtime, local_token, auth), + ) + .await?; Ok(()) } diff --git a/crates/worker-runtime/src/main.rs b/crates/worker-runtime/src/main.rs index 019cdfb2..ebf458f4 100644 --- a/crates/worker-runtime/src/main.rs +++ b/crates/worker-runtime/src/main.rs @@ -44,7 +44,10 @@ fn main() -> ExitCode { fn run() -> Result<(), ProcessError> { let args = env::args().skip(1).collect::>(); - if matches!(args.first().map(String::as_str), Some("identity" | "trust-server")) { + if matches!( + args.first().map(String::as_str), + Some("identity" | "trust-server") + ) { return run_auth_command(args); } let Some(mut config) = parse_args(args)? else { @@ -412,9 +415,8 @@ fn read_runtime_auth_file(path: &Path) -> Result return Ok(RuntimeAuthFile::default()); } let contents = std::fs::read_to_string(path)?; - toml::from_str(&contents).map_err(|error| { - ProcessError::Auth(format!("failed to parse {}: {error}", path.display())) - }) + toml::from_str(&contents) + .map_err(|error| ProcessError::Auth(format!("failed to parse {}: {error}", path.display()))) } fn write_runtime_auth_file(path: &Path, auth: &RuntimeAuthFile) -> Result<(), ProcessError> { @@ -442,7 +444,9 @@ fn write_secret_file(path: &Path, contents: &[u8]) -> Result<(), ProcessError> { Ok(()) } -fn load_runtime_http_auth(config: &ProcessConfig) -> Result, ProcessError> { +fn load_runtime_http_auth( + config: &ProcessConfig, +) -> Result, ProcessError> { let path = runtime_auth_path(config); let auth = read_runtime_auth_file(&path)?; let Some(identity) = auth.identity else { @@ -468,7 +472,11 @@ fn parse_auth_storage_flags(args: &mut VecDeque) -> Result { config.fs_runtime_dir = Some(PathBuf::from(take_value(&flag, inline_value, args)?)); } - _ => return Err(ProcessError::usage(format!("unknown auth command argument `{flag}`"))), + _ => { + return Err(ProcessError::usage(format!( + "unknown auth command argument `{flag}`" + ))); + } } } Ok(config) @@ -480,7 +488,9 @@ fn run_auth_command(args: Vec) -> Result<(), ProcessError> { match command.as_str() { "identity" => run_identity_command(args), "trust-server" => run_trust_server_command(args), - _ => Err(ProcessError::usage(format!("unknown auth command `{command}`"))), + _ => Err(ProcessError::usage(format!( + "unknown auth command `{command}`" + ))), } } @@ -496,17 +506,31 @@ fn run_identity_command(mut args: VecDeque) -> Result<(), ProcessError> while let Some(arg) = args.pop_front() { let (flag, inline_value) = split_flag_value(arg)?; match flag.as_str() { - "--runtime-id" => runtime_id = Some(take_value(&flag, inline_value, &mut args)?), + "--runtime-id" => { + runtime_id = Some(take_value(&flag, inline_value, &mut args)?) + } "--replace" => { ensure_no_inline_value(&flag, inline_value.as_deref())?; replace = true; } "--fs-root" | "--fs-runtime-dir" => { rest.push_back(flag); - if let Some(value) = inline_value { rest.push_back(value); } - else { rest.push_back(args.pop_front().ok_or_else(|| ProcessError::usage(format!("{} requires a value", rest.back().unwrap())))?); } + if let Some(value) = inline_value { + rest.push_back(value); + } else { + rest.push_back(args.pop_front().ok_or_else(|| { + ProcessError::usage(format!( + "{} requires a value", + rest.back().unwrap() + )) + })?); + } + } + _ => { + return Err(ProcessError::usage(format!( + "unknown identity init argument `{flag}`" + ))); } - _ => return Err(ProcessError::usage(format!("unknown identity init argument `{flag}`"))), } } let config = parse_auth_storage_flags(&mut rest)?; @@ -521,9 +545,10 @@ fn run_identity_command(mut args: VecDeque) -> Result<(), ProcessError> let runtime_id = runtime_id.ok_or_else(|| { ProcessError::usage("identity init requires --runtime-id".to_string()) })?; - auth.identity = Some(RuntimeIdentityMaterial::generate(runtime_id).map_err(|error| { - ProcessError::Auth(error.to_string()) - })?); + auth.identity = Some( + RuntimeIdentityMaterial::generate(runtime_id) + .map_err(|error| ProcessError::Auth(error.to_string()))?, + ); write_runtime_auth_file(&path, &auth)?; let identity = auth.identity.as_ref().unwrap(); println!("runtime_id={}", identity.identity_id); @@ -543,21 +568,40 @@ fn run_identity_command(mut args: VecDeque) -> Result<(), ProcessError> } "--fs-root" | "--fs-runtime-dir" => { rest.push_back(flag); - if let Some(value) = inline_value { rest.push_back(value); } - else { rest.push_back(args.pop_front().ok_or_else(|| ProcessError::usage(format!("{} requires a value", rest.back().unwrap())))?); } + if let Some(value) = inline_value { + rest.push_back(value); + } else { + rest.push_back(args.pop_front().ok_or_else(|| { + ProcessError::usage(format!( + "{} requires a value", + rest.back().unwrap() + )) + })?); + } + } + _ => { + return Err(ProcessError::usage(format!( + "unknown identity show argument `{flag}`" + ))); } - _ => return Err(ProcessError::usage(format!("unknown identity show argument `{flag}`"))), } } let config = parse_auth_storage_flags(&mut rest)?; let path = runtime_auth_path(&config); let auth = read_runtime_auth_file(&path)?; let Some(identity) = auth.identity else { - return Err(ProcessError::usage(format!("runtime identity is not initialized at {}", path.display()))); + return Err(ProcessError::usage(format!( + "runtime identity is not initialized at {}", + path.display() + ))); }; let view = runtime_public_identity_view(&identity); if json { - println!("{}", serde_json::to_string_pretty(&view).map_err(|error| ProcessError::Auth(error.to_string()))?); + println!( + "{}", + serde_json::to_string_pretty(&view) + .map_err(|error| ProcessError::Auth(error.to_string()))? + ); } else { println!("runtime_id={}", view.identity_id); println!("public_key={}", view.public_key); @@ -565,13 +609,17 @@ fn run_identity_command(mut args: VecDeque) -> Result<(), ProcessError> } Ok(()) } - _ => Err(ProcessError::usage(format!("unknown identity subcommand `{subcommand}`"))), + _ => Err(ProcessError::usage(format!( + "unknown identity subcommand `{subcommand}`" + ))), } } fn run_trust_server_command(mut args: VecDeque) -> Result<(), ProcessError> { let subcommand = args.pop_front().ok_or_else(|| { - ProcessError::usage("trust-server requires subcommand `add`, `list`, or `revoke`".to_string()) + ProcessError::usage( + "trust-server requires subcommand `add`, `list`, or `revoke`".to_string(), + ) })?; match subcommand.as_str() { "add" => { @@ -584,31 +632,64 @@ fn run_trust_server_command(mut args: VecDeque) -> Result<(), ProcessErr let (flag, inline_value) = split_flag_value(arg)?; match flag.as_str() { "--server-id" => server_id = Some(take_value(&flag, inline_value, &mut args)?), - "--public-key" => public_key = Some(take_value(&flag, inline_value, &mut args)?), - "--display-name" => display_name = Some(take_value(&flag, inline_value, &mut args)?), + "--public-key" => { + public_key = Some(take_value(&flag, inline_value, &mut args)?) + } + "--display-name" => { + display_name = Some(take_value(&flag, inline_value, &mut args)?) + } "--replace" => { ensure_no_inline_value(&flag, inline_value.as_deref())?; replace = true; } "--fs-root" | "--fs-runtime-dir" => { rest.push_back(flag); - if let Some(value) = inline_value { rest.push_back(value); } - else { rest.push_back(args.pop_front().ok_or_else(|| ProcessError::usage(format!("{} requires a value", rest.back().unwrap())))?); } + if let Some(value) = inline_value { + rest.push_back(value); + } else { + rest.push_back(args.pop_front().ok_or_else(|| { + ProcessError::usage(format!( + "{} requires a value", + rest.back().unwrap() + )) + })?); + } + } + _ => { + return Err(ProcessError::usage(format!( + "unknown trust-server add argument `{flag}`" + ))); } - _ => return Err(ProcessError::usage(format!("unknown trust-server add argument `{flag}`"))), } } let config = parse_auth_storage_flags(&mut rest)?; let path = runtime_auth_path(&config); let mut auth = read_runtime_auth_file(&path)?; - let server_id = server_id.ok_or_else(|| ProcessError::usage("trust-server add requires --server-id".to_string()))?; - let public_key = public_key.ok_or_else(|| ProcessError::usage("trust-server add requires --public-key".to_string()))?; - decode_public_key(&public_key).map_err(|error| ProcessError::usage(error.to_string()))?; - if auth.trusted_servers.iter().any(|server| server.server_id == server_id) && !replace { - return Err(ProcessError::usage(format!("trusted server `{server_id}` already exists; pass --replace to update it"))); + let server_id = server_id.ok_or_else(|| { + ProcessError::usage("trust-server add requires --server-id".to_string()) + })?; + let public_key = public_key.ok_or_else(|| { + ProcessError::usage("trust-server add requires --public-key".to_string()) + })?; + decode_public_key(&public_key) + .map_err(|error| ProcessError::usage(error.to_string()))?; + if auth + .trusted_servers + .iter() + .any(|server| server.server_id == server_id) + && !replace + { + return Err(ProcessError::usage(format!( + "trusted server `{server_id}` already exists; pass --replace to update it" + ))); } - auth.trusted_servers.retain(|server| server.server_id != server_id); - auth.trusted_servers.push(TrustedServerKey { server_id: server_id.clone(), public_key, display_name }); + auth.trusted_servers + .retain(|server| server.server_id != server_id); + auth.trusted_servers.push(TrustedServerKey { + server_id: server_id.clone(), + public_key, + display_name, + }); write_runtime_auth_file(&path, &auth)?; println!("trusted_server_id={server_id}"); println!("auth_file={}", path.display()); @@ -626,19 +707,40 @@ fn run_trust_server_command(mut args: VecDeque) -> Result<(), ProcessErr } "--fs-root" | "--fs-runtime-dir" => { rest.push_back(flag); - if let Some(value) = inline_value { rest.push_back(value); } - else { rest.push_back(args.pop_front().ok_or_else(|| ProcessError::usage(format!("{} requires a value", rest.back().unwrap())))?); } + if let Some(value) = inline_value { + rest.push_back(value); + } else { + rest.push_back(args.pop_front().ok_or_else(|| { + ProcessError::usage(format!( + "{} requires a value", + rest.back().unwrap() + )) + })?); + } + } + _ => { + return Err(ProcessError::usage(format!( + "unknown trust-server list argument `{flag}`" + ))); } - _ => return Err(ProcessError::usage(format!("unknown trust-server list argument `{flag}`"))), } } let config = parse_auth_storage_flags(&mut rest)?; let auth = read_runtime_auth_file(&runtime_auth_path(&config))?; if json { - println!("{}", serde_json::to_string_pretty(&auth.trusted_servers).map_err(|error| ProcessError::Auth(error.to_string()))?); + println!( + "{}", + serde_json::to_string_pretty(&auth.trusted_servers) + .map_err(|error| ProcessError::Auth(error.to_string()))? + ); } else { for server in auth.trusted_servers { - println!("server_id={} public_key={} display_name={}", server.server_id, server.public_key, server.display_name.unwrap_or_default()); + println!( + "server_id={} public_key={} display_name={}", + server.server_id, + server.public_key, + server.display_name.unwrap_or_default() + ); } } Ok(()) @@ -652,26 +754,45 @@ fn run_trust_server_command(mut args: VecDeque) -> Result<(), ProcessErr "--server-id" => server_id = Some(take_value(&flag, inline_value, &mut args)?), "--fs-root" | "--fs-runtime-dir" => { rest.push_back(flag); - if let Some(value) = inline_value { rest.push_back(value); } - else { rest.push_back(args.pop_front().ok_or_else(|| ProcessError::usage(format!("{} requires a value", rest.back().unwrap())))?); } + if let Some(value) = inline_value { + rest.push_back(value); + } else { + rest.push_back(args.pop_front().ok_or_else(|| { + ProcessError::usage(format!( + "{} requires a value", + rest.back().unwrap() + )) + })?); + } + } + _ => { + return Err(ProcessError::usage(format!( + "unknown trust-server revoke argument `{flag}`" + ))); } - _ => return Err(ProcessError::usage(format!("unknown trust-server revoke argument `{flag}`"))), } } let config = parse_auth_storage_flags(&mut rest)?; let path = runtime_auth_path(&config); let mut auth = read_runtime_auth_file(&path)?; - let server_id = server_id.ok_or_else(|| ProcessError::usage("trust-server revoke requires --server-id".to_string()))?; + let server_id = server_id.ok_or_else(|| { + ProcessError::usage("trust-server revoke requires --server-id".to_string()) + })?; let before = auth.trusted_servers.len(); - auth.trusted_servers.retain(|server| server.server_id != server_id); + auth.trusted_servers + .retain(|server| server.server_id != server_id); if auth.trusted_servers.len() == before { - return Err(ProcessError::usage(format!("trusted server `{server_id}` is not registered"))); + return Err(ProcessError::usage(format!( + "trusted server `{server_id}` is not registered" + ))); } write_runtime_auth_file(&path, &auth)?; println!("revoked_server_id={server_id}"); Ok(()) } - _ => Err(ProcessError::usage(format!("unknown trust-server subcommand `{subcommand}`"))), + _ => Err(ProcessError::usage(format!( + "unknown trust-server subcommand `{subcommand}`" + ))), } } diff --git a/crates/workspace-server/src/hosts.rs b/crates/workspace-server/src/hosts.rs index 121ede60..fad21e8e 100644 --- a/crates/workspace-server/src/hosts.rs +++ b/crates/workspace-server/src/hosts.rs @@ -2239,11 +2239,12 @@ impl RemoteWorkerRuntime { let capability_token = self.runtime_capability_token(path); run_blocking_http(move || { let request = request.header(CONTENT_TYPE, "application/json"); - let request = if let Some(token) = capability_token.as_deref().or(bearer_token.as_deref()) { - request.header(AUTHORIZATION, format!("Bearer {token}")) - } else { - request - }; + let request = + if let Some(token) = capability_token.as_deref().or(bearer_token.as_deref()) { + request.header(AUTHORIZATION, format!("Bearer {token}")) + } else { + request + }; let response = request .send() .map_err(|err| remote_reqwest_diagnostic(&runtime_id, err))?; diff --git a/crates/workspace-server/src/store.rs b/crates/workspace-server/src/store.rs index d2de9fdd..f4e89e65 100644 --- a/crates/workspace-server/src/store.rs +++ b/crates/workspace-server/src/store.rs @@ -541,7 +541,10 @@ impl SqliteWorkspaceStore { }) } - pub fn list_trusted_runtimes(&self, include_revoked: bool) -> Result> { + pub fn list_trusted_runtimes( + &self, + include_revoked: bool, + ) -> Result> { self.with_conn(|conn| { let sql = if include_revoked { r#"SELECT runtime_id, display_name, base_url, public_key, created_at, updated_at, revoked_at