lesavka/tests/api/server/auth/server_auth_rpc_contract.rs

92 lines
3.3 KiB
Rust

// API/security contract for unauthenticated relay RPC rejection.
//
// Scope: preserve the security boundary between transport authentication and
// handler execution for media, HID, and paste RPCs.
// Targets: server entrypoint and relay helper modules.
// Why: without TLS/mTLS on the server builder, any client could stream media or
// HID reports into the remote controlled target.
const ENTRYPOINT: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/server/src/main/entrypoint.rs"
));
const SERVER_SECURITY: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/server/src/security.rs"
));
const INPUT_RPC: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/server/src/main/relay_service/input_stream_rpc.rs"
));
const CAMERA_RPC: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/server/src/main/relay_service/camera_stream_rpc.rs"
));
const MICROPHONE_RPC: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/server/src/main/relay_service/microphone_stream_rpc.rs"
));
const RPC_HELPERS: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/server/src/main/rpc_helpers.rs"
));
#[test]
fn server_installs_tls_before_exposing_relay_services() {
let tls_idx = ENTRYPOINT
.find("security::server_tls_config()?")
.expect("entrypoint should request TLS config");
let relay_idx = ENTRYPOINT
.find(".add_service(RelayServer::new(handler))")
.expect("entrypoint should expose relay service");
assert!(
tls_idx < relay_idx,
"TLS must be configured before media/HID RPC services are exposed"
);
assert!(ENTRYPOINT.contains("server = server.tls_config(tls)?"));
}
#[test]
fn production_mtls_requires_a_client_ca_unless_operator_explicitly_opts_out() {
assert!(SERVER_SECURITY.contains("client_ca_root"));
assert!(SERVER_SECURITY.contains("LESAVKA_TLS_CLIENT_AUTH_OPTIONAL"));
assert!(
SERVER_SECURITY.contains("TLS enabled with required client certificate authentication"),
"required client certificate auth should be the normal CA-backed path"
);
}
#[test]
fn powerful_rpc_handlers_depend_on_transport_auth_boundary() {
for (name, source, lease_marker) in [
("keyboard", INPUT_RPC, "capture_power.acquire_session()"),
("mouse", INPUT_RPC, "capture_power.acquire_session()"),
("camera", CAMERA_RPC, "activate_camera()"),
("microphone", MICROPHONE_RPC, "reserve_microphone_sink"),
] {
assert!(
source.contains(lease_marker),
"{name} RPC should continue using runtime leases after transport auth"
);
assert!(
!source.contains("LESAVKA_ALLOW_INSECURE"),
"{name} RPC should not override transport security inside handler code"
);
}
}
#[test]
fn unauthenticated_paste_payloads_map_to_unauthenticated_status() {
assert!(RPC_HELPERS.contains("paste::decrypt(&req)"));
assert!(RPC_HELPERS.contains("Status::unauthenticated"));
assert!(
RPC_HELPERS
.find("paste::decrypt(&req)")
.expect("decrypt marker")
< RPC_HELPERS
.find("paste::type_text")
.expect("type text marker"),
"paste payloads must authenticate/decrypt before HID typing begins"
);
}