Skip to content

Commit 526d02d

Browse files
authored
Add integration tests for auth and end-to-end proxying (#4)
* Add authentication handshake tests * Add basic proxy test * Add mismatched secret failure test * Add a failure test for invalid addresses
1 parent 23db404 commit 526d02d

File tree

4 files changed

+169
-0
lines changed

4 files changed

+169
-0
lines changed

Cargo.lock

+30
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,7 @@ tokio = { version = "1.17.0", features = ["full"] }
2828
tracing = "0.1.32"
2929
tracing-subscriber = "0.3.10"
3030
uuid = { version = "0.8.2", features = ["serde", "v4"] }
31+
32+
[dev-dependencies]
33+
lazy_static = "1.4.0"
34+
rstest = "0.12.0"

tests/auth_test.rs

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use anyhow::Result;
2+
use bore_cli::auth::Authenticator;
3+
use tokio::io::{self, BufReader};
4+
5+
#[tokio::test]
6+
async fn auth_handshake() -> Result<()> {
7+
let auth = Authenticator::new("some secret string");
8+
9+
let (client, server) = io::duplex(8); // Ensure correctness with limited capacity.
10+
let mut client = BufReader::new(client);
11+
let mut server = BufReader::new(server);
12+
13+
tokio::try_join!(
14+
auth.client_handshake(&mut client),
15+
auth.server_handshake(&mut server),
16+
)?;
17+
18+
Ok(())
19+
}
20+
21+
#[tokio::test]
22+
async fn auth_handshake_fail() {
23+
let auth = Authenticator::new("client secret");
24+
let auth2 = Authenticator::new("different server secret");
25+
26+
let (client, server) = io::duplex(8); // Ensure correctness with limited capacity.
27+
let mut client = BufReader::new(client);
28+
let mut server = BufReader::new(server);
29+
30+
let result = tokio::try_join!(
31+
auth.client_handshake(&mut client),
32+
auth2.server_handshake(&mut server),
33+
);
34+
assert!(result.is_err());
35+
}

tests/e2e_test.rs

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use std::net::SocketAddr;
2+
use std::time::Duration;
3+
4+
use anyhow::{anyhow, Result};
5+
use bore_cli::{client::Client, server::Server};
6+
use lazy_static::lazy_static;
7+
use rstest::*;
8+
use tokio::io::{AsyncReadExt, AsyncWriteExt};
9+
use tokio::net::{TcpListener, TcpStream};
10+
use tokio::sync::Mutex;
11+
use tokio::time;
12+
13+
lazy_static! {
14+
/// Guard to make sure that tests are run serially, not concurrently.
15+
static ref SERIAL_GUARD: Mutex<()> = Mutex::new(());
16+
}
17+
18+
/// Spawn the server, giving some time for the control port TcpListener to start.
19+
async fn spawn_server(secret: Option<&str>) {
20+
tokio::spawn(Server::new(1024, secret).listen());
21+
time::sleep(Duration::from_millis(50)).await;
22+
}
23+
24+
/// Spawns a client with randomly assigned ports, returning the listener and remote address.
25+
async fn spawn_client(secret: Option<&str>) -> Result<(TcpListener, SocketAddr)> {
26+
let listener = TcpListener::bind("localhost:0").await?;
27+
let client = Client::new(listener.local_addr()?.port(), "localhost", 0, secret).await?;
28+
let remote_addr = ([0, 0, 0, 0], client.remote_port()).into();
29+
tokio::spawn(client.listen());
30+
Ok((listener, remote_addr))
31+
}
32+
33+
#[rstest]
34+
#[tokio::test]
35+
async fn basic_proxy(#[values(None, Some(""), Some("abc"))] secret: Option<&str>) -> Result<()> {
36+
let _guard = SERIAL_GUARD.lock().await;
37+
38+
spawn_server(secret).await;
39+
let (listener, addr) = spawn_client(secret).await?;
40+
41+
tokio::spawn(async move {
42+
let (mut stream, _) = listener.accept().await?;
43+
let mut buf = [0u8; 11];
44+
stream.read_exact(&mut buf).await?;
45+
assert_eq!(&buf, b"hello world");
46+
47+
stream.write_all(b"I can send a message too!").await?;
48+
anyhow::Ok(())
49+
});
50+
51+
let mut stream = TcpStream::connect(addr).await?;
52+
stream.write_all(b"hello world").await?;
53+
54+
let mut buf = [0u8; 25];
55+
stream.read_exact(&mut buf).await?;
56+
assert_eq!(&buf, b"I can send a message too!");
57+
58+
// Ensure that the client end of the stream is closed now.
59+
assert_eq!(stream.read(&mut buf).await?, 0);
60+
61+
// Also ensure that additional connections do not produce any data.
62+
let mut stream = TcpStream::connect(addr).await?;
63+
assert_eq!(stream.read(&mut buf).await?, 0);
64+
65+
Ok(())
66+
}
67+
68+
#[rstest]
69+
#[case(None, Some("my secret"))]
70+
#[case(Some("my secret"), None)]
71+
#[tokio::test]
72+
async fn mismatched_secret(
73+
#[case] server_secret: Option<&str>,
74+
#[case] client_secret: Option<&str>,
75+
) {
76+
let _guard = SERIAL_GUARD.lock().await;
77+
78+
spawn_server(server_secret).await;
79+
assert!(spawn_client(client_secret).await.is_err());
80+
}
81+
82+
#[tokio::test]
83+
async fn invalid_address() -> Result<()> {
84+
// We don't need the serial guard for this test because it doesn't create a server.
85+
async fn check_address(to: &str, use_secret: bool) -> Result<()> {
86+
match Client::new(5000, to, 0, use_secret.then(|| "a secret")).await {
87+
Ok(_) => Err(anyhow!("expected error for {to}, use_secret={use_secret}")),
88+
Err(_) => Ok(()),
89+
}
90+
}
91+
tokio::try_join!(
92+
check_address("google.com", false),
93+
check_address("google.com", true),
94+
check_address("nonexistent.domain.for.demonstration", false),
95+
check_address("nonexistent.domain.for.demonstration", true),
96+
check_address("malformed !$uri$%", false),
97+
check_address("malformed !$uri$%", true),
98+
)?;
99+
Ok(())
100+
}

0 commit comments

Comments
 (0)