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
This commit is contained in:
Eric Zhang 2022-04-09 02:54:52 -04:00 committed by GitHub
parent 23db4047ff
commit 526d02d789
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 169 additions and 0 deletions

30
Cargo.lock generated
View File

@ -91,6 +91,8 @@ dependencies = [
"dashmap", "dashmap",
"hex", "hex",
"hmac", "hmac",
"lazy_static",
"rstest",
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2",
@ -461,12 +463,34 @@ dependencies = [
"bitflags", "bitflags",
] ]
[[package]]
name = "rstest"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d912f35156a3f99a66ee3e11ac2e0b3f34ac85a07e05263d05a7e2c8810d616f"
dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"rustc_version",
"syn",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.21" version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.9" version = "1.0.9"
@ -479,6 +503,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "semver"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.136" version = "1.0.136"

View File

@ -28,3 +28,7 @@ tokio = { version = "1.17.0", features = ["full"] }
tracing = "0.1.32" tracing = "0.1.32"
tracing-subscriber = "0.3.10" tracing-subscriber = "0.3.10"
uuid = { version = "0.8.2", features = ["serde", "v4"] } uuid = { version = "0.8.2", features = ["serde", "v4"] }
[dev-dependencies]
lazy_static = "1.4.0"
rstest = "0.12.0"

35
tests/auth_test.rs Normal file
View File

@ -0,0 +1,35 @@
use anyhow::Result;
use bore_cli::auth::Authenticator;
use tokio::io::{self, BufReader};
#[tokio::test]
async fn auth_handshake() -> Result<()> {
let auth = Authenticator::new("some secret string");
let (client, server) = io::duplex(8); // Ensure correctness with limited capacity.
let mut client = BufReader::new(client);
let mut server = BufReader::new(server);
tokio::try_join!(
auth.client_handshake(&mut client),
auth.server_handshake(&mut server),
)?;
Ok(())
}
#[tokio::test]
async fn auth_handshake_fail() {
let auth = Authenticator::new("client secret");
let auth2 = Authenticator::new("different server secret");
let (client, server) = io::duplex(8); // Ensure correctness with limited capacity.
let mut client = BufReader::new(client);
let mut server = BufReader::new(server);
let result = tokio::try_join!(
auth.client_handshake(&mut client),
auth2.server_handshake(&mut server),
);
assert!(result.is_err());
}

100
tests/e2e_test.rs Normal file
View File

@ -0,0 +1,100 @@
use std::net::SocketAddr;
use std::time::Duration;
use anyhow::{anyhow, Result};
use bore_cli::{client::Client, server::Server};
use lazy_static::lazy_static;
use rstest::*;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::Mutex;
use tokio::time;
lazy_static! {
/// Guard to make sure that tests are run serially, not concurrently.
static ref SERIAL_GUARD: Mutex<()> = Mutex::new(());
}
/// Spawn the server, giving some time for the control port TcpListener to start.
async fn spawn_server(secret: Option<&str>) {
tokio::spawn(Server::new(1024, secret).listen());
time::sleep(Duration::from_millis(50)).await;
}
/// Spawns a client with randomly assigned ports, returning the listener and remote address.
async fn spawn_client(secret: Option<&str>) -> Result<(TcpListener, SocketAddr)> {
let listener = TcpListener::bind("localhost:0").await?;
let client = Client::new(listener.local_addr()?.port(), "localhost", 0, secret).await?;
let remote_addr = ([0, 0, 0, 0], client.remote_port()).into();
tokio::spawn(client.listen());
Ok((listener, remote_addr))
}
#[rstest]
#[tokio::test]
async fn basic_proxy(#[values(None, Some(""), Some("abc"))] secret: Option<&str>) -> Result<()> {
let _guard = SERIAL_GUARD.lock().await;
spawn_server(secret).await;
let (listener, addr) = spawn_client(secret).await?;
tokio::spawn(async move {
let (mut stream, _) = listener.accept().await?;
let mut buf = [0u8; 11];
stream.read_exact(&mut buf).await?;
assert_eq!(&buf, b"hello world");
stream.write_all(b"I can send a message too!").await?;
anyhow::Ok(())
});
let mut stream = TcpStream::connect(addr).await?;
stream.write_all(b"hello world").await?;
let mut buf = [0u8; 25];
stream.read_exact(&mut buf).await?;
assert_eq!(&buf, b"I can send a message too!");
// Ensure that the client end of the stream is closed now.
assert_eq!(stream.read(&mut buf).await?, 0);
// Also ensure that additional connections do not produce any data.
let mut stream = TcpStream::connect(addr).await?;
assert_eq!(stream.read(&mut buf).await?, 0);
Ok(())
}
#[rstest]
#[case(None, Some("my secret"))]
#[case(Some("my secret"), None)]
#[tokio::test]
async fn mismatched_secret(
#[case] server_secret: Option<&str>,
#[case] client_secret: Option<&str>,
) {
let _guard = SERIAL_GUARD.lock().await;
spawn_server(server_secret).await;
assert!(spawn_client(client_secret).await.is_err());
}
#[tokio::test]
async fn invalid_address() -> Result<()> {
// We don't need the serial guard for this test because it doesn't create a server.
async fn check_address(to: &str, use_secret: bool) -> Result<()> {
match Client::new(5000, to, 0, use_secret.then(|| "a secret")).await {
Ok(_) => Err(anyhow!("expected error for {to}, use_secret={use_secret}")),
Err(_) => Ok(()),
}
}
tokio::try_join!(
check_address("google.com", false),
check_address("google.com", true),
check_address("nonexistent.domain.for.demonstration", false),
check_address("nonexistent.domain.for.demonstration", true),
check_address("malformed !$uri$%", false),
check_address("malformed !$uri$%", true),
)?;
Ok(())
}