fix: require explicit runtime trust replacement
This commit is contained in:
parent
d994268a6b
commit
9a14f1b6fc
|
|
@ -257,7 +257,9 @@ fn run_identity_command(args: Vec<String>) -> Result<(), Box<dyn std::error::Err
|
||||||
_ => return Err(Box::new(CliError(format!("unknown identity init argument `{flag}`")))),
|
_ => return Err(Box::new(CliError(format!("unknown identity init argument `{flag}`")))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let server_id = server_id.unwrap_or_else(|| "server-main".to_string());
|
let server_id = server_id.ok_or_else(|| {
|
||||||
|
CliError("identity init requires --server-id".to_string())
|
||||||
|
})?;
|
||||||
let path = server_identity_path();
|
let path = server_identity_path();
|
||||||
if read_server_identity_file(&path)?.is_some() && !replace {
|
if read_server_identity_file(&path)?.is_some() && !replace {
|
||||||
return Err(Box::new(CliError(format!(
|
return Err(Box::new(CliError(format!(
|
||||||
|
|
@ -316,6 +318,7 @@ fn run_trust_runtime_command(args: Vec<String>) -> Result<(), Box<dyn std::error
|
||||||
let mut base_url = None;
|
let mut base_url = None;
|
||||||
let mut public_key = None;
|
let mut public_key = None;
|
||||||
let mut display_name = None;
|
let mut display_name = None;
|
||||||
|
let mut replace = false;
|
||||||
while let Some(arg) = args.pop_front() {
|
while let Some(arg) = args.pop_front() {
|
||||||
let (flag, inline_value) = split_flag_value(arg)?;
|
let (flag, inline_value) = split_flag_value(arg)?;
|
||||||
match flag.as_str() {
|
match flag.as_str() {
|
||||||
|
|
@ -323,6 +326,10 @@ fn run_trust_runtime_command(args: Vec<String>) -> Result<(), Box<dyn std::error
|
||||||
"--base-url" | "--endpoint" => base_url = Some(take_value(&flag, inline_value, &mut args)?),
|
"--base-url" | "--endpoint" => base_url = Some(take_value(&flag, inline_value, &mut args)?),
|
||||||
"--public-key" => public_key = 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)?),
|
"--display-name" => display_name = Some(take_value(&flag, inline_value, &mut args)?),
|
||||||
|
"--replace" => {
|
||||||
|
ensure_no_inline_value(&flag, inline_value.as_deref())?;
|
||||||
|
replace = true;
|
||||||
|
}
|
||||||
_ => return Err(Box::new(CliError(format!("unknown trust-runtime add argument `{flag}`")))),
|
_ => return Err(Box::new(CliError(format!("unknown trust-runtime add argument `{flag}`")))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -330,6 +337,7 @@ fn run_trust_runtime_command(args: Vec<String>) -> Result<(), Box<dyn std::error
|
||||||
let base_url = base_url.ok_or_else(|| CliError("trust-runtime add requires --base-url".to_string()))?;
|
let base_url = base_url.ok_or_else(|| CliError("trust-runtime add requires --base-url".to_string()))?;
|
||||||
let public_key = public_key.ok_or_else(|| CliError("trust-runtime add requires --public-key".to_string()))?;
|
let public_key = public_key.ok_or_else(|| CliError("trust-runtime add requires --public-key".to_string()))?;
|
||||||
decode_public_key(&public_key)?;
|
decode_public_key(&public_key)?;
|
||||||
|
ensure_trusted_runtime_replace_allowed(&store, &runtime_id, replace)?;
|
||||||
let now = Utc::now().to_rfc3339();
|
let now = Utc::now().to_rfc3339();
|
||||||
store.upsert_trusted_runtime(&TrustedRuntimeRecord {
|
store.upsert_trusted_runtime(&TrustedRuntimeRecord {
|
||||||
runtime_id: runtime_id.clone(),
|
runtime_id: runtime_id.clone(),
|
||||||
|
|
@ -398,6 +406,24 @@ fn run_trust_runtime_command(args: Vec<String>) -> Result<(), Box<dyn std::error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ensure_trusted_runtime_replace_allowed(
|
||||||
|
store: &SqliteWorkspaceStore,
|
||||||
|
runtime_id: &str,
|
||||||
|
replace: bool,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
if store
|
||||||
|
.list_trusted_runtimes(true)?
|
||||||
|
.iter()
|
||||||
|
.any(|runtime| runtime.runtime_id == runtime_id)
|
||||||
|
&& !replace
|
||||||
|
{
|
||||||
|
return Err(Box::new(CliError(format!(
|
||||||
|
"trusted runtime `{runtime_id}` already exists; pass --replace to update it"
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn split_flag_value(arg: String) -> Result<(String, Option<String>), CliError> {
|
fn split_flag_value(arg: String) -> Result<(String, Option<String>), CliError> {
|
||||||
if let Some((flag, value)) = arg.split_once('=') {
|
if let Some((flag, value)) = arg.split_once('=') {
|
||||||
if flag.is_empty() {
|
if flag.is_empty() {
|
||||||
|
|
@ -743,7 +769,7 @@ fn parse_listen(value: &str) -> Result<SocketAddr, CliError> {
|
||||||
|
|
||||||
fn print_help() {
|
fn print_help() {
|
||||||
println!(
|
println!(
|
||||||
"yoi-workspace-server\n\nUsage:\n yoi-workspace-server init [OPTIONS]\n yoi-workspace-server config <COMMAND> [OPTIONS]\n yoi-workspace-server skills <COMMAND> [OPTIONS]\n yoi-workspace-server serve [OPTIONS]\n\nOptions:\n -h, --help Print help"
|
"yoi-workspace-server\n\nUsage:\n yoi-workspace-server init [OPTIONS]\n yoi-workspace-server config <COMMAND> [OPTIONS]\n yoi-workspace-server identity init --server-id <SERVER_ID> [--replace]\n yoi-workspace-server identity show [--json]\n yoi-workspace-server trust-runtime add --runtime-id <RUNTIME_ID> --base-url <URL> --public-key <KEY> [--display-name <NAME>] [--replace]\n yoi-workspace-server trust-runtime list [--json] [--include-revoked]\n yoi-workspace-server trust-runtime revoke --runtime-id <RUNTIME_ID>\n yoi-workspace-server skills <COMMAND> [OPTIONS]\n yoi-workspace-server serve [OPTIONS]\n\nOptions:\n -h, --help Print help"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -813,6 +839,39 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn server_identity_init_requires_explicit_server_id() {
|
||||||
|
let error = run_identity_command(vec!["init".to_string()]).unwrap_err();
|
||||||
|
assert_eq!(error.to_string(), "identity init requires --server-id");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn trusted_runtime_add_requires_replace_for_existing_record() {
|
||||||
|
let temp = tempfile::tempdir().unwrap();
|
||||||
|
let store = SqliteWorkspaceStore::open(temp.path().join("server.db")).unwrap();
|
||||||
|
let public_key = RuntimeIdentityMaterial::generate("runtime-a")
|
||||||
|
.unwrap()
|
||||||
|
.public_key;
|
||||||
|
store
|
||||||
|
.upsert_trusted_runtime(&TrustedRuntimeRecord {
|
||||||
|
runtime_id: "runtime-a".to_string(),
|
||||||
|
display_name: "Runtime A".to_string(),
|
||||||
|
base_url: "http://127.0.0.1:18080".to_string(),
|
||||||
|
public_key,
|
||||||
|
created_at: "2026-07-26T00:00:00Z".to_string(),
|
||||||
|
updated_at: "2026-07-26T00:00:00Z".to_string(),
|
||||||
|
revoked_at: None,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let error = ensure_trusted_runtime_replace_allowed(&store, "runtime-a", false).unwrap_err();
|
||||||
|
assert_eq!(
|
||||||
|
error.to_string(),
|
||||||
|
"trusted runtime `runtime-a` already exists; pass --replace to update it"
|
||||||
|
);
|
||||||
|
ensure_trusted_runtime_replace_allowed(&store, "runtime-a", true).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn init_creates_identity_local_config_and_server_records() {
|
async fn init_creates_identity_local_config_and_server_records() {
|
||||||
let temp = tempfile::tempdir().unwrap();
|
let temp = tempfile::tempdir().unwrap();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user