mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-09 20:09:57 -06:00
Security: the --control server requires an auth token and closes bad connections (#777)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
fc45b7f6d8
commit
fef1f4ddb1
+49
-2
@@ -363,6 +363,11 @@ fn remote_backend_over_tcp() {
|
||||
let stream = stream.unwrap();
|
||||
let mut w = stream.try_clone().unwrap();
|
||||
let mut r = BufReader::new(stream);
|
||||
// Every connection starts with the token, as the app requires.
|
||||
let mut first = String::new();
|
||||
r.read_line(&mut first).unwrap();
|
||||
let hello = crate::control_auth::check_first_line(&first, "secret-token-0123456789").unwrap();
|
||||
w.write_all(format!("{hello}\n").as_bytes()).unwrap();
|
||||
loop {
|
||||
let mut line = String::new();
|
||||
if r.read_line(&mut line).unwrap_or(0) == 0 {
|
||||
@@ -382,7 +387,7 @@ fn remote_backend_over_tcp() {
|
||||
});
|
||||
let addr = control_addr(&port.to_string());
|
||||
assert_eq!(addr, format!("127.0.0.1:{port}"));
|
||||
let mut s = Server::new(Box::new(Remote::connect(&addr).unwrap()));
|
||||
let mut s = Server::new(Box::new(Remote::connect_with_token(&addr, "secret-token-0123456789".into()).unwrap()));
|
||||
assert_eq!(ok(&mut s, "evaluate_formula", json!({"formula": "=1+1"}))["result"], json!(2.0));
|
||||
// Second call reconnects transparently.
|
||||
ok(&mut s, "set_cell", json!({"cell": "A1", "input": "7"}));
|
||||
@@ -391,7 +396,49 @@ fn remote_backend_over_tcp() {
|
||||
assert!(msg.contains("unknown command"), "{msg}");
|
||||
drop(s);
|
||||
handle.join().unwrap();
|
||||
assert!(Remote::connect("127.0.0.1:1").is_err());
|
||||
assert!(Remote::connect_with_token("127.0.0.1:1", "x".into()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn control_auth_handshake_and_compare() {
|
||||
use crate::control_auth::{auth_line, check_first_line, constant_time_eq, hex};
|
||||
let token = "0123456789abcdef0123456789abcdef";
|
||||
let ok = check_first_line(&auth_line(7, token), token).unwrap();
|
||||
assert_eq!((ok["id"].clone(), ok["ok"].clone()), (json!(7), json!(true)));
|
||||
// Wrong or missing token, another method, and non-JSON are all refused the same way.
|
||||
let wrong = check_first_line(&auth_line(1, "0123456789abcdef0123456789abcdee"), token).unwrap_err();
|
||||
for bad in [
|
||||
auth_line(1, ""),
|
||||
r#"{"id":1,"method":"auth","params":{}}"#.into(),
|
||||
r#"{"id":1,"method":"app.save","params":{"path":"/tmp/x","token":"0123456789abcdef0123456789abcdef"}}"#.into(),
|
||||
"POST / HTTP/1.1".into(),
|
||||
String::new(),
|
||||
] {
|
||||
assert_eq!(check_first_line(&bad, token).unwrap_err(), wrong, "{bad}");
|
||||
}
|
||||
// An empty configured token never matches an empty given one.
|
||||
assert!(check_first_line(&auth_line(1, ""), "").is_err());
|
||||
assert!(constant_time_eq(b"abc", b"abc"));
|
||||
assert!(!constant_time_eq(b"abc", b"abd"));
|
||||
assert!(!constant_time_eq(b"abc", b"abcd"));
|
||||
assert_eq!(hex(&[0x00, 0xab, 0xff]), "00abff");
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn control_token_file_is_private() {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
// Unique port per run so parallel tests don't share a file; the folder is the real one.
|
||||
let port = 40_000 + (std::process::id() % 20_000) as u16;
|
||||
let path = crate::control_auth::write_token_file(port, "tok-0123456789abcdef").unwrap();
|
||||
assert_eq!(std::fs::metadata(&path).unwrap().permissions().mode() & 0o777, 0o600);
|
||||
assert_eq!(std::fs::metadata(path.parent().unwrap()).unwrap().permissions().mode() & 0o777, 0o700);
|
||||
// A looser leftover is replaced, not reused.
|
||||
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o666)).unwrap();
|
||||
crate::control_auth::write_token_file(port, "tok-1123456789abcdef").unwrap();
|
||||
assert_eq!(std::fs::metadata(&path).unwrap().permissions().mode() & 0o777, 0o600);
|
||||
assert_eq!(std::fs::read_to_string(&path).unwrap().trim(), "tok-1123456789abcdef");
|
||||
std::fs::remove_file(&path).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user