Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@ jobs:
export CARGO_HOME="$HOME/.cargo"
echo "$CARGO_HOME/bin" >> "$GITHUB_PATH"
cargo install --force --path crates/cli --locked --message-format=short
cargo install --force --path crates/standalone --locked --message-format=short
cargo install --force --path crates/standalone --features allow_loopback_http_for_tests --locked --message-format=short
# Add a handy alias using the old binary name, so that we don't have to rewrite all scripts (incl. in submodules).
ln -sf $CARGO_HOME/bin/spacetimedb-cli $CARGO_HOME/bin/spacetime
Expand Down
3 changes: 3 additions & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ nix = { workspace = true, features = ["sched"] }
# Print a warning when doing an unindexed `iter_by_col_range` on a large table.
unindexed_iter_by_col_range_warn = []
default = ["unindexed_iter_by_col_range_warn"]
# Test-only escape hatch used by SDK procedure tests that intentionally call `localhost`.
# Keep this off in production builds.
allow_loopback_http_for_tests = []
# Enable timing for wasm ABI calls
spacetimedb-wasm-instance-env-times = []
# Enable test helpers and utils
Expand Down
708 changes: 707 additions & 1 deletion crates/core/src/host/instance_env.rs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion crates/guard/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub fn ensure_binaries_built() -> PathBuf {
\n\
Or build manually:\n\
\n\
cargo build -p spacetimedb-cli -p spacetimedb-standalone\n\
cargo build -p spacetimedb-cli -p spacetimedb-standalone --features spacetimedb-standalone/allow_loopback_http_for_tests\n\
========================================================================\n",
cli_path.display()
);
Expand Down
4 changes: 2 additions & 2 deletions crates/smoketests/DEVELOP.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ you MUST rebuild before running tests:
cargo smoketest

# Option 2: Manually rebuild, then run tests directly
cargo build -p spacetimedb-cli -p spacetimedb-standalone
cargo build -p spacetimedb-cli -p spacetimedb-standalone --features spacetimedb-standalone/allow_loopback_http_for_tests
cargo nextest run -p spacetimedb-smoketests
```

Expand All @@ -54,7 +54,7 @@ Pre-building avoids this entirely.
Standard `cargo test` also works, but you must rebuild first:

```bash
cargo build -p spacetimedb-cli -p spacetimedb-standalone
cargo build -p spacetimedb-cli -p spacetimedb-standalone --features spacetimedb-standalone/allow_loopback_http_for_tests
cargo test -p spacetimedb-smoketests
```

Expand Down
86 changes: 86 additions & 0 deletions crates/smoketests/tests/http_egress.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use std::io::{Read, Write};
use std::net::TcpListener;
use std::thread::JoinHandle;

use spacetimedb_smoketests::Smoketest;

fn module_code_http_disallowed_ip(addr: &str, port: u16) -> String {
format!(
r#"
use spacetimedb::ProcedureContext;

#[spacetimedb::procedure]
pub fn request_redirect_to_disallowed_ip(ctx: &mut ProcedureContext) -> Result<(), String> {{
match ctx.http.get("http://{addr}:{port}/") {{
Ok(_) => Err("request unexpectedly succeeded".to_owned()),
Err(err) => {{
let message = err.to_string();
if message.contains("refusing to connect to private or special-purpose addresses") {{
Ok(())
}} else {{
Err(format!("unexpected error from http request: {{message}}"))
}}
}}
}}
}}
"#
)
}

fn spawn_redirect_server(location: &str) -> (u16, JoinHandle<std::io::Result<()>>) {
let listener = TcpListener::bind(("127.0.0.1", 0)).expect("failed to bind test redirect server");
let port = listener
.local_addr()
.expect("failed to read test redirect server address")
.port();
let location = location.to_owned();
let handle = std::thread::spawn(move || -> std::io::Result<()> {
let (mut stream, _) = listener.accept()?;
let mut buf = [0u8; 1024];
let _ = stream.read(&mut buf)?;
let response =
format!("HTTP/1.1 302 Found\r\nLocation: {location}\r\nContent-Length: 0\r\nConnection: close\r\n\r\n");
stream.write_all(response.as_bytes())?;
stream.flush()?;
Ok(())
});
(port, handle)
}

#[test]
fn test_http_disallowed_ip_is_blocked() {
let module_code = module_code_http_disallowed_ip("10.0.0.1", 80);
let test = Smoketest::builder().module_code(&module_code).build();

let output = test.call_output("request_disallowed_ip", &[]);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Expected request_disallowed_ip to succeed after observing blocked egress error.\nstdout:\n{}\nstderr:\n{}",
stdout,
stderr
);
}

#[test]
fn test_http_redirect_to_disallowed_ip_is_blocked() {
let (port, redirect_server) = spawn_redirect_server("http://10.0.0.1:80/");
let module_code = module_code_http_disallowed_ip("localhost", port);
let test = Smoketest::builder().module_code(&module_code).build();

let output = test.call_output("request_redirect_to_disallowed_ip", &[]);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Expected request_redirect_to_disallowed_ip to succeed after observing blocked egress error.\nstdout:\n{}\nstderr:\n{}",
stdout,
stderr
);

redirect_server
.join()
.expect("redirect test server thread panicked")
.expect("redirect test server failed");
}
1 change: 1 addition & 0 deletions crates/smoketests/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub mod domains;
pub mod energy;
pub mod fail_initial_publish;
pub mod filtering;
pub mod http_egress;
pub mod module_nested_op;
pub mod modules;
pub mod namespaces;
Expand Down
1 change: 1 addition & 0 deletions crates/standalone/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ required-features = [] # Features required to build this target (N/A for lib)

[features]
unstable = ["spacetimedb-client-api/unstable"]
allow_loopback_http_for_tests = ["spacetimedb-core/allow_loopback_http_for_tests"]
# Perfmaps for profiling modules
perfmap = ["spacetimedb-core/perfmap"]
# Disables core pinning
Expand Down
3 changes: 3 additions & 0 deletions crates/testing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ edition.workspace = true
license-file = "LICENSE"
publish = false

[features]
allow_loopback_http_for_tests = ["spacetimedb-standalone/allow_loopback_http_for_tests"]

[dependencies]
spacetimedb-cli.workspace = true
spacetimedb-data-structures.workspace = true
Expand Down
3 changes: 3 additions & 0 deletions sdks/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ rust-version.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
allow_loopback_http_for_tests = ["spacetimedb-testing/allow_loopback_http_for_tests"]

[dependencies]
spacetimedb-data-structures.workspace = true
spacetimedb-sats.workspace = true
Expand Down
16 changes: 16 additions & 0 deletions tools/ci/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,22 @@ fn main() -> Result<()> {
"--all",
"--exclude",
"spacetimedb-smoketests",
"--exclude",
"spacetimedb-sdk",
"--",
"--test-threads=2",
"--skip",
"unreal"
)
.run()?;
// SDK procedure tests intentionally make localhost HTTP requests.
cmd!(
"cargo",
"test",
"-p",
"spacetimedb-sdk",
"--features",
"allow_loopback_http_for_tests",
"--",
"--test-threads=2",
"--skip",
Expand Down
2 changes: 2 additions & 0 deletions tools/ci/src/smoketest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ fn build_binaries() -> Result<()> {
"spacetimedb-cli",
"-p",
"spacetimedb-standalone",
"--features",
"spacetimedb-standalone/allow_loopback_http_for_tests",
]);

// Remove cargo/rust env vars that could cause fingerprint mismatches
Expand Down