use std::net::Ipv4Addr;
use std::sync::Mutex;
use openmoat_core::{EvalContext, Policy};
use super::*;
use crate::audit::RecordError;
struct Local;
impl Resolve for Local {
fn resolve(&self, _: &str, port: u16) -> io::Result<Vec<SocketAddr>> {
Ok(vec![SocketAddr::from((Ipv4Addr::LOCALHOST, port))])
}
}
#[derive(Default)]
struct Rules(Mutex<Vec<Vec<String>>>);
impl Recorder for Rules {
fn record(&self, c: &Connection<'_>) -> Result<(), RecordError> {
self.0.lock().unwrap().push(c.decision.rules.clone());
Ok(())
}
}
fn ctx() -> EvalContext {
EvalContext {
home: "/home/u".to_owned(),
project: None,
real_home: None,
real_project: None,
moved_dirs: Vec::new(),
cwd: "/home/u".to_owned(),
case_insensitive_paths: false,
}
}
fn pair() -> (TcpStream, TcpStream) {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let client = TcpStream::connect(listener.local_addr().unwrap()).unwrap();
let (proxy_end, _) = listener.accept().unwrap();
(proxy_end, client)
}
#[test]
fn a_tunnel_whose_client_cannot_be_answered_is_recorded_and_gets_no_bytes() {
let policy =
Policy::parse("version: 1\nallow:\n - { id: hosts, net: [allowed.test] }\n").unwrap();
let compiled = CompiledPolicy::compile(&policy, &ctx()).unwrap();
let address_policy = decide::address_policy(&policy);
let addresses = CompiledPolicy::compile(&address_policy, &ctx()).unwrap();
let upstream = TcpListener::bind("127.0.0.1:0").unwrap();
let rules = Rules::default();
let proxy = Proxy {
policy: &compiled,
resolver: &Local,
recorder: &rules,
limits: &Limits::default(),
broker: &Broker::default(),
loopback_ok: &[upstream.local_addr().unwrap()],
};
let (proxy_end, mut client) = pair();
let port = upstream.local_addr().unwrap().port();
write!(client, "CONNECT allowed.test:{port} HTTP/1.1\r\n\r\n").unwrap();
proxy_end.shutdown(Shutdown::Write).unwrap();
proxy.handle(&proxy_end, &addresses);
assert_eq!(*rules.0.lock().unwrap(), [[RULE_SNI]]);
let (mut opened, _) = upstream.accept().unwrap();
let mut forwarded = Vec::new();
opened.read_to_end(&mut forwarded).unwrap();
assert!(forwarded.is_empty());
}
#[test]
fn limits_that_cannot_serve_a_connection_are_refused() {
assert!(Limits::default().check().is_ok());
let cases = [
Limits {
max_head_bytes: 0,
..Limits::default()
},
Limits {
max_connections: 0,
..Limits::default()
},
Limits {
idle_timeout: Duration::ZERO,
..Limits::default()
},
Limits {
handshake_timeout: Duration::MAX,
..Limits::default()
},
];
for limits in cases {
let err = limits.check().unwrap_err();
assert_eq!(err.kind(), ErrorKind::InvalidInput, "{limits:?}");
}
}
#[test]
fn a_refusal_echoes_no_control_byte() {
let (proxy_end, mut client) = pair();
respond(&proxy_end, 403, "a.test\r\nX-Injected: 1");
drop(proxy_end);
let mut reply = String::new();
client.read_to_string(&mut reply).unwrap();
assert!(
reply.ends_with("\r\n\r\nmoat proxy: a.test??X-Injected: 1\n"),
"{reply:?}"
);
}