use super::*;
use std::io::{Read, Write};
#[test]
#[ignore = "isolated real process launched by ingress_token_process"]
fn ingress_token_child() {
let path = PathBuf::from(std::env::var_os("SADDLE_TOKEN_TEST_CONFIG").unwrap());
let config = ProcessConfig::<()>::load(&path).unwrap();
let (app, listener) = saddle_core::BootstrapRendezvousIssuer::issue()
.freeze_application(saddle_core::GeneratedApplicationFreezeSource::new(
"token-test",
b"token-test",
&["token.call"],
))
.unwrap();
coordinate_profusegw_process(
config,
saddle_boundary::ingress::ProfuseGwListenerAdapter::new("token-test").unwrap(),
app,
listener,
|_| async { Ok(()) },
move |_, (), _, _| {
std::fs::write(path.with_file_name("handler-called"), b"called").unwrap();
async {
Ok(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\n{}".to_vec())
}
},
)
.unwrap();
}
fn address() -> SocketAddr {
std::net::TcpListener::bind("127.0.0.1:0")
.unwrap()
.local_addr()
.unwrap()
}
struct Child {
process: std::process::Child,
root: PathBuf,
}
impl Drop for Child {
fn drop(&mut self) {
let _ = self.process.kill();
let _ = self.process.wait();
let _ = std::fs::remove_dir_all(&self.root);
}
}
fn request(address: SocketAddr, bytes: &[u8]) -> Vec<u8> {
let mut socket = std::net::TcpStream::connect(address).unwrap();
socket
.set_read_timeout(Some(Duration::from_secs(2)))
.unwrap();
socket.write_all(bytes).unwrap();
let mut response = Vec::new();
socket.read_to_end(&mut response).unwrap();
response
}
#[test]
fn ingress_token_process() {
for configured in [true, false] {
let business = address();
let management = address();
let nonce = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let root = std::env::temp_dir().join(format!(
"saddle-ingress-token-{}-{nonce}",
std::process::id()
));
std::fs::create_dir(&root).unwrap();
let path = root.join("saddle.toml");
let auth = if configured {
"[framework.ingress]\nprofusegw_token=\"ingress-only\"\n"
} else {
""
};
std::fs::write(&path, format!("[framework]\nlisten=\"{business}\"\n{auth}[framework.management]\nbind=\"{management}\"\n[framework.admission]\ncpuCores=2\nmemoryMb=512\n[framework.admission.dependencies]\ndatabaseConcurrency=1\nprofusecontractConcurrency=1\n[framework.profusecontract]\nauthority=\"http://localhost:50051\"\ntoken=\"outbound-only\"\n[framework.observability.logging]\ndirectory=\"{}\"\n[secrets]\n",root.join("logs").display())).unwrap();
let output = std::fs::File::create(root.join("child.log")).unwrap();
let mut child = Child {
process: std::process::Command::new(std::env::current_exe().unwrap())
.args([
"--exact",
"process::token_tests::ingress_token_child",
"--ignored",
"--nocapture",
])
.env("SADDLE_TOKEN_TEST_CONFIG", &path)
.stdout(output.try_clone().unwrap())
.stderr(output)
.spawn()
.unwrap(),
root,
};
let mut started = false;
for _ in 0..200 {
if std::net::TcpStream::connect(management).is_ok() {
started = true;
break;
}
assert!(
child.process.try_wait().unwrap().is_none(),
"{}",
std::fs::read_to_string(child.root.join("child.log")).unwrap()
);
std::thread::sleep(Duration::from_millis(10));
}
assert!(started);
for endpoint in ["live", "ready", "metrics"] {
let response = request(
management,
format!("GET /{endpoint} HTTP/1.1\r\nHost: localhost\r\n\r\n").as_bytes(),
);
assert!(response.starts_with(b"HTTP/1.1 200"));
}
if configured {
for header in [
"",
"Authorization: Bearer ingress-only\r\n",
"X-ProfuseGW-Token: outbound-only\r\n",
"X-ProfuseGW-Token: ingress-only\r\nx-profusegw-token: ingress-only\r\n",
] {
let response = request(business, format!("POST /saddle/v1/ingress/profusegw/invoke HTTP/1.1\r\nContent-Type: application/json\r\nContent-Length: 1024\r\nX-Request-Id: r1\r\nX-Call-Id: c1\r\n{header}\r\n").as_bytes());
assert!(response.starts_with(b"HTTP/1.1 401"));
assert!(!child.root.join("handler-called").exists());
}
}
let body = r#"{"target":{"app":"token-test","interfaceId":"token.call"},"profuseGwContext":{"userInfo":{"userId":"u1"},"traceInfo":{"traceId":"trace-1","rpcId":"0"},"ldcInfo":{"zone":"z1","idc":"i1","env":"test"}},"requestData":{}}"#;
let header = if configured {
"x-PrOfUsEgW-ToKeN: ingress-only\r\n"
} else {
""
};
let response = request(business, format!("POST /saddle/v1/ingress/profusegw/invoke HTTP/1.1\r\nContent-Type: application/json\r\nContent-Length: {}\r\nX-Request-Id: r2\r\nX-Call-Id: c2\r\n{header}\r\n{body}",body.len()).as_bytes());
assert!(
response.starts_with(b"HTTP/1.1 200"),
"{}",
String::from_utf8_lossy(&response)
);
assert!(child.root.join("handler-called").exists());
assert!(
std::process::Command::new("kill")
.args(["-TERM", &child.process.id().to_string()])
.status()
.unwrap()
.success()
);
let mut ended = false;
for _ in 0..300 {
if let Some(exit) = child.process.try_wait().unwrap() {
assert!(exit.success());
ended = true;
break;
}
std::thread::sleep(Duration::from_millis(10));
}
assert!(ended);
}
}