diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47c5b062..75b72072 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,32 @@ jobs: timeout-minutes: 20 run: cargo test --release --workspace --no-fail-fast -- --test-threads=1 + rust-minimal: + name: Rust without optional features + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Rust cache + uses: Swatinem/rust-cache@v2 + + - name: Allow unprivileged user namespaces + run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 + + - name: Minimal build pulls in no HTTP stack + run: | + if cargo tree --workspace --no-default-features -e normal --prefix none \ + | grep -E '^(hyper|rustls|rcgen|bollard) '; then + exit 1 + fi + + - name: Run all tests + timeout-minutes: 20 + run: cargo test --release --workspace --no-default-features --no-fail-fast -- --test-threads=1 + rust-low-abi: name: Rust low-ABI (ubuntu-22.04) runs-on: ubuntu-22.04 diff --git a/crates/sandlock-cli/Cargo.toml b/crates/sandlock-cli/Cargo.toml index b3e0e2e2..02e10434 100644 --- a/crates/sandlock-cli/Cargo.toml +++ b/crates/sandlock-cli/Cargo.toml @@ -12,8 +12,12 @@ description = "CLI for sandlock process sandbox" name = "sandlock" path = "src/main.rs" +[features] +default = ["http"] +http = ["sandlock-core/http"] + [dependencies] -sandlock-core = { version = "0.8.9", path = "../sandlock-core", features = ["cli"] } +sandlock-core = { version = "0.8.9", path = "../sandlock-core", default-features = false, features = ["cli"] } clap = { version = "4", features = ["derive"] } tokio = { version = "1", features = ["rt-multi-thread", "macros"] } anyhow = "1" diff --git a/crates/sandlock-cli/src/learn.rs b/crates/sandlock-cli/src/learn.rs index 6a90a617..11e41ec6 100644 --- a/crates/sandlock-cli/src/learn.rs +++ b/crates/sandlock-cli/src/learn.rs @@ -585,12 +585,15 @@ pub async fn run(args: LearnArgs) -> Result<()> { .max_memory(sandlock_core::sandbox::ByteSize(1 << 43)) // 8 TiB .policy_fn(move |event, _ctx| observer_cb.on_event(event)); - let http_requests_cb = Arc::clone(&observer.http_requests); - let http_log: Arc = - Arc::new(move |method, host, path| { - http_requests_cb.lock().unwrap().insert(format!("{method} {host}{path}")); - }); - builder = builder.http_log_fn(http_log); + #[cfg(feature = "http")] + { + let http_requests_cb = Arc::clone(&observer.http_requests); + let http_log: Arc = + Arc::new(move |method, host, path| { + http_requests_cb.lock().unwrap().insert(format!("{method} {host}{path}")); + }); + builder = builder.http_log_fn(http_log); + } for path in &args.http_inject_ca { builder = builder.http_inject_ca(path); } diff --git a/crates/sandlock-cli/tests/learn_integration.rs b/crates/sandlock-cli/tests/learn_integration.rs index 46d1d358..dfad4d67 100644 --- a/crates/sandlock-cli/tests/learn_integration.rs +++ b/crates/sandlock-cli/tests/learn_integration.rs @@ -464,6 +464,7 @@ fn spawn_http_server() -> u16 { /// Learn HTTP traffic then run with the learned profile: /// - the learned path is allowed and returns the real response /// - a different path that was not observed is blocked (403 from proxy) +#[cfg(feature = "http")] #[test] fn test_learn_then_run_http() { let profile = tempfile::NamedTempFile::new().expect("tempfile"); @@ -533,6 +534,7 @@ fn https_reachable(host: &str) -> bool { /// - the learned HTTPS path is allowed /// - an unlearned path is blocked (403 from proxy) /// Skipped if example.com is unreachable or no system CA bundle is found. +#[cfg(feature = "http")] #[test] fn test_learn_then_run_https() { if !https_reachable("example.com") { @@ -584,6 +586,7 @@ fn test_learn_then_run_https() { /// --merge carries [http].allow and [http].ports from the observed run into the /// existing profile. +#[cfg(feature = "http")] #[test] fn test_learn_then_run_merge_http() { let profile = tempfile::NamedTempFile::new().expect("tempfile"); diff --git a/crates/sandlock-cli/tests/learn_test.rs b/crates/sandlock-cli/tests/learn_test.rs index 3b243123..9ae262a2 100644 --- a/crates/sandlock-cli/tests/learn_test.rs +++ b/crates/sandlock-cli/tests/learn_test.rs @@ -994,6 +994,7 @@ fn spawn_http_server() -> u16 { } /// A plaintext HTTP request is captured and written as an [http] allow rule. +#[cfg(feature = "http")] #[test] fn test_learn_captures_http_request() { let port = spawn_http_server(); @@ -1015,6 +1016,7 @@ fn test_learn_captures_http_request() { } /// --http-port writes only the intercepted port to [http].ports, not port 80. +#[cfg(feature = "http")] #[test] fn test_learn_http_port_not_inflated() { let port = spawn_http_server(); @@ -1033,6 +1035,7 @@ fn test_learn_http_port_not_inflated() { } /// Multiple distinct requests to different paths are all recorded and deduplicated. +#[cfg(feature = "http")] #[test] fn test_learn_captures_http_multiple_paths() { let port = spawn_http_server(); @@ -1082,6 +1085,7 @@ fn https_reachable(host: &str) -> bool { /// Uses a real HTTPS server (example.com) so that TLS termination, MITM cert signing, /// CA injection, and proxy forwarding are all exercised end to end. /// Skipped if example.com is unreachable or no system CA bundle is found. +#[cfg(feature = "http")] #[test] fn test_learn_captures_https_request() { if !https_reachable("example.com") { diff --git a/crates/sandlock-core/Cargo.toml b/crates/sandlock-core/Cargo.toml index c290e013..d8365692 100644 --- a/crates/sandlock-core/Cargo.toml +++ b/crates/sandlock-core/Cargo.toml @@ -26,24 +26,28 @@ ring = "0.17" toml = "0.8" jiff = "0.2" pathdiff = "0.2" -tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] } -rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12", "logging"] } -rcgen = { version = "0.13", features = ["pem", "x509-parser"] } -hyper = { version = "1", features = ["server", "client", "http1"] } -hyper-util = { version = "0.1", features = ["client-legacy", "http1", "tokio"] } -http-body-util = "0.1" -hyper-rustls = { version = "0.27", default-features = false, features = ["ring", "native-tokio", "http1", "tls12", "logging"] } +tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"], optional = true } +rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12", "logging"], optional = true } +rcgen = { version = "0.13", features = ["pem", "x509-parser"], optional = true } +hyper = { version = "1", features = ["server", "client", "http1"], optional = true } +hyper-util = { version = "0.1", features = ["client-legacy", "http1", "tokio"], optional = true } +http-body-util = { version = "0.1", optional = true } +hyper-rustls = { version = "0.27", default-features = false, features = ["ring", "native-tokio", "http1", "tls12", "logging"], optional = true } tar = "0.4" flate2 = { version = "1", default-features = false, features = ["rust_backend"] } ruzstd = "0.9" base64 = "0.22" clap = { version = "4", features = ["derive"], optional = true } -bollard = "0.21" -futures-util = "0.3" +bollard = { version = "0.21", optional = true } +futures-util = { version = "0.3", optional = true } [features] -default = [] +default = ["http"] cli = ["dep:clap"] +http = [ + "dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:hyper-rustls", + "dep:tokio-rustls", "dep:rustls", "dep:rcgen", "dep:bollard", "dep:futures-util", +] [dev-dependencies] tokio = { version = "1", features = ["rt-multi-thread", "macros", "test-util"] } diff --git a/crates/sandlock-core/src/checkpoint/image.rs b/crates/sandlock-core/src/checkpoint/image.rs index f84fde5c..d09259cc 100644 --- a/crates/sandlock-core/src/checkpoint/image.rs +++ b/crates/sandlock-core/src/checkpoint/image.rs @@ -380,6 +380,7 @@ mod tests { assert!(loaded.net_allow_is_active()); } + #[cfg(feature = "http")] #[test] fn image_round_trip_preserves_deny_only_http_as_default_allow() { let policy = Sandbox::builder() @@ -397,6 +398,7 @@ mod tests { assert_eq!(loaded.effective_net_allow().len(), 1); } + #[cfg(feature = "http")] #[test] fn image_round_trip_preserves_http_only_as_restrictive_allowlist() { let policy = Sandbox::builder() diff --git a/crates/sandlock-core/src/credential_disabled.rs b/crates/sandlock-core/src/credential_disabled.rs new file mode 100644 index 00000000..4fe8c431 --- /dev/null +++ b/crates/sandlock-core/src/credential_disabled.rs @@ -0,0 +1,17 @@ +//! Stand-in for credential injection when sandlock is built without the +//! `http` feature: injection happens in the HTTP ACL proxy, which is absent. + +use crate::error::SandboxError; + +#[derive(Debug)] +pub enum InjectRule {} + +pub fn resolve_inject_rules( + credentials: &[String], + inject: &[String], +) -> Result<(Vec, Vec), SandboxError> { + if credentials.is_empty() && inject.is_empty() { + return Ok((Vec::new(), Vec::new())); + } + Err(SandboxError::FeatureDisabled { what: "credential injection".into(), feature: "http" }) +} diff --git a/crates/sandlock-core/src/error.rs b/crates/sandlock-core/src/error.rs index 3453ddfa..ab3866f0 100644 --- a/crates/sandlock-core/src/error.rs +++ b/crates/sandlock-core/src/error.rs @@ -28,6 +28,9 @@ pub enum SandboxError { #[error("confine() only accepts Landlock filesystem policy; unsupported fields: {0}")] UnsupportedForConfine(String), + #[error("{what} needs sandlock built with the \"{feature}\" feature")] + FeatureDisabled { what: String, feature: &'static str }, + #[error("chroot path {path} does not exist or is inaccessible: {source}")] ChrootNotFound { path: std::path::PathBuf, diff --git a/crates/sandlock-core/src/image/mod.rs b/crates/sandlock-core/src/image/mod.rs index b5b59bba..202cbeb6 100644 --- a/crates/sandlock-core/src/image/mod.rs +++ b/crates/sandlock-core/src/image/mod.rs @@ -28,9 +28,11 @@ use serde::{Deserialize, Serialize}; use crate::error::{SandboxRuntimeError, SandlockError}; +#[cfg(feature = "http")] mod docker; mod layer; mod oci; +#[cfg(feature = "http")] mod registry; /// An unpacked image: its root filesystem and how it expects to be run. @@ -66,8 +68,16 @@ impl ImageConfig { pub async fn pull(reference: &str, cache_dir: Option<&Path>) -> Result { let cache = Cache::new(cache_dir); match Source::parse(reference)? { + #[cfg(feature = "http")] Source::Registry(name) => registry::pull(&cache, &name).await, + #[cfg(feature = "http")] Source::DockerDaemon(name) => docker::pull(&cache, &name).await, + #[cfg(not(feature = "http"))] + Source::Registry(_) | Source::DockerDaemon(_) => Err(crate::error::SandboxError::FeatureDisabled { + what: format!("image reference {reference:?}"), + feature: "http", + } + .into()), Source::OciDir { path, tag } => { blocking(move || { let blobs = oci::LayoutDir::open(&path)?; @@ -256,6 +266,15 @@ mod tests { use super::*; use oci::tests::{tar_of, TestLayout}; + #[cfg(not(feature = "http"))] + #[tokio::test] + async fn network_transports_need_the_http_feature() { + for reference in ["docker://alpine", "docker-daemon:alpine"] { + let err = pull(reference, Some(Path::new("/nonexistent"))).await.unwrap_err(); + assert!(err.to_string().contains("\"http\" feature"), "{err}"); + } + } + #[test] fn default_cmd_combines_entrypoint_and_cmd() { let cfg = ImageConfig { diff --git a/crates/sandlock-core/src/image/oci.rs b/crates/sandlock-core/src/image/oci.rs index 45e2f667..14967d96 100644 --- a/crates/sandlock-core/src/image/oci.rs +++ b/crates/sandlock-core/src/image/oci.rs @@ -96,8 +96,10 @@ impl Blobs for LayoutDir { } /// A bare directory of blobs named by their sha256 hex. +#[cfg(feature = "http")] pub(super) struct BlobDir(pub PathBuf); +#[cfg(feature = "http")] impl Blobs for BlobDir { fn index(&self) -> io::Result> { Err(io::Error::new(io::ErrorKind::Unsupported, "a blob directory has no index")) @@ -407,6 +409,7 @@ pub(super) mod tests { use super::*; use std::fs; + #[cfg(feature = "http")] pub(in crate::image) fn host_arch_for_tests() -> &'static str { host_arch() } diff --git a/crates/sandlock-core/src/landlock.rs b/crates/sandlock-core/src/landlock.rs index 23dfd62b..dfbca627 100644 --- a/crates/sandlock-core/src/landlock.rs +++ b/crates/sandlock-core/src/landlock.rs @@ -931,6 +931,7 @@ mod mask_contract_tests { ); } + #[cfg(feature = "http")] #[test] fn net_mask_http_only_keeps_connect_tcp() { // HTTP-only has no explicit allow rules but generates a finite @@ -952,6 +953,7 @@ mod mask_contract_tests { ); } + #[cfg(feature = "http")] #[test] fn net_mask_deny_only_http_stays_wildcard() { // Deny-only + HTTP stays default-allow: HTTP reachability must not @@ -973,6 +975,7 @@ mod mask_contract_tests { ); } + #[cfg(feature = "http")] #[test] fn net_mask_combined_http_keeps_connect_tcp() { let pol = ProtectionPolicy::strict_all(); diff --git a/crates/sandlock-core/src/lib.rs b/crates/sandlock-core/src/lib.rs index d1b9e283..14c1c3bc 100644 --- a/crates/sandlock-core/src/lib.rs +++ b/crates/sandlock-core/src/lib.rs @@ -1,5 +1,9 @@ pub mod error; pub mod http; +#[cfg(feature = "http")] +pub(crate) mod credential; +#[cfg(not(feature = "http"))] +#[path = "credential_disabled.rs"] pub(crate) mod credential; pub mod sandbox; // formerly `policy`; contains Sandbox + SandboxBuilder + Confinement pub mod profile; @@ -34,6 +38,10 @@ pub mod fork; pub(crate) mod ca_inject; pub(crate) mod chroot; pub mod control; +#[cfg(feature = "http")] +mod transparent_proxy; +#[cfg(not(feature = "http"))] +#[path = "transparent_proxy/disabled.rs"] mod transparent_proxy; pub use error::SandlockError; diff --git a/crates/sandlock-core/src/profile.rs b/crates/sandlock-core/src/profile.rs index 18cb96eb..2a9c9a4c 100644 --- a/crates/sandlock-core/src/profile.rs +++ b/crates/sandlock-core/src/profile.rs @@ -979,6 +979,7 @@ mod tests { assert_eq!(parsed.filesystem, FilesystemSection::default()); } + #[cfg(feature = "http")] #[test] fn config_section_maps_to_policy_http_fields() { let toml = r#" @@ -994,6 +995,7 @@ mod tests { assert_eq!(policy.http_key.as_deref(), Some(std::path::Path::new("/tmp/ca.key"))); } + #[cfg(feature = "http")] #[test] fn parses_http_inject_ca_and_ca_out() { let toml = r#" @@ -1068,6 +1070,7 @@ mod tests { assert!(msg.contains("non-empty"), "got: {msg}"); } + #[cfg(feature = "http")] #[test] fn parse_profile_full_example() { let toml = r#" @@ -1246,6 +1249,7 @@ mod tests { assert_eq!(round_tripped.net_deny_bind, policy.net_deny_bind); } + #[cfg(feature = "http")] #[test] fn profile_deny_only_http_policy_does_not_promote_generated_allow_rules() { let toml = r#" @@ -1270,6 +1274,7 @@ mod tests { assert_eq!(round_tripped.effective_net_allow().len(), 1); } + #[cfg(feature = "http")] #[test] fn profile_http_only_round_trips_as_restrictive_allowlist() { let toml = r#" @@ -1293,6 +1298,7 @@ mod tests { ); } + #[cfg(feature = "http")] #[test] fn profile_combined_http_round_trips_without_duplicating_derived_rules() { let toml = r#" diff --git a/crates/sandlock-core/src/sandbox/builder.rs b/crates/sandlock-core/src/sandbox/builder.rs index d344a104..44bd072f 100644 --- a/crates/sandlock-core/src/sandbox/builder.rs +++ b/crates/sandlock-core/src/sandbox/builder.rs @@ -939,6 +939,25 @@ impl SandboxBuilder { )); } + #[cfg(not(feature = "http"))] + { + let set = [ + ("--http-allow", !self.http_allow.is_empty()), + ("--http-deny", !self.http_deny.is_empty()), + ("--http-port", !self.http_ports.is_empty()), + ("--http-ca", self.http_ca.is_some()), + ("--http-key", self.http_key.is_some()), + ("--http-inject-ca", !self.http_inject_ca.is_empty()), + ("--http-ca-out", self.http_ca_out.is_some()), + ("--http-auth", !self.http_auth.is_empty()), + ("--credential", !self.credentials.is_empty()), + ("http_log_fn", self.http_log_fn.is_some()), + ]; + if let Some((what, _)) = set.iter().find(|(_, on)| *on) { + return Err(SandboxError::FeatureDisabled { what: what.to_string(), feature: "http" }); + } + } + // Validate: http_ca and http_key must both be set or both unset if self.http_ca.is_some() != self.http_key.is_some() { return Err(SandboxError::Invalid( diff --git a/crates/sandlock-core/src/sandbox/tests.rs b/crates/sandlock-core/src/sandbox/tests.rs index 9a9fcdaa..6ad536df 100644 --- a/crates/sandlock-core/src/sandbox/tests.rs +++ b/crates/sandlock-core/src/sandbox/tests.rs @@ -2,6 +2,19 @@ use super::*; use std::path::{Path, PathBuf}; use std::str::FromStr; +#[cfg(not(feature = "http"))] +#[test] +fn http_options_need_the_http_feature() { + for builder in [ + Sandbox::builder().http_allow("GET example.com/*"), + Sandbox::builder().http_ca("/ca.pem").http_key("/ca.key"), + Sandbox::builder().credential("openai", "env:OPENAI_API_KEY"), + ] { + let err = builder.build().unwrap_err(); + assert!(matches!(err, SandboxError::FeatureDisabled { feature: "http", .. }), "{err}"); + } +} + #[test] fn run_as_parses_uid_and_gid() { let r = RunAs::from_str("1000:2000").unwrap(); @@ -49,6 +62,7 @@ fn resolve_sandbox_path_mount_takes_precedence() { assert_eq!(r, PathBuf::from("/host/ssl/x.pem")); } +#[cfg(feature = "http")] #[tokio::test] async fn inject_ca_nonexistent_path_errors_at_run() { // Wildcard host rule avoids DNS; the missing inject path must error @@ -64,6 +78,7 @@ async fn inject_ca_nonexistent_path_errors_at_run() { // --- SandboxBuilder integration --- +#[cfg(feature = "http")] #[test] fn builder_http_rules() { let policy = Sandbox::builder() @@ -109,6 +124,7 @@ fn builder_http_key_without_ca_returns_error() { assert!(result.is_err()); } +#[cfg(feature = "http")] #[test] fn builder_http_ca_and_key_together_ok() { let policy = Sandbox::builder() @@ -120,6 +136,7 @@ fn builder_http_ca_and_key_together_ok() { assert!(policy.http_key.is_some()); } +#[cfg(feature = "http")] #[test] fn inject_ca_adds_443_and_requires_http_rule() { // No http rule -> error. @@ -138,6 +155,7 @@ fn inject_ca_adds_443_and_requires_http_rule() { assert_eq!(policy.http_inject_ca.len(), 1); } +#[cfg(feature = "http")] #[test] fn http_ca_out_requires_trigger() { let err = Sandbox::builder() @@ -309,6 +327,7 @@ fn builder_combines_net_allow_and_net_deny() { assert!(policy.net_allow_is_active()); } +#[cfg(feature = "http")] #[test] fn builder_keeps_http_reachability_out_of_net_allow() { // `net_allow` holds only explicit rules; HTTP reachability is generated @@ -323,6 +342,7 @@ fn builder_keeps_http_reachability_out_of_net_allow() { assert_eq!(policy.effective_net_allow().len(), 1); } +#[cfg(feature = "http")] #[test] fn builder_http_only_stays_restrictive_allowlist() { let policy = Sandbox::builder() @@ -335,6 +355,7 @@ fn builder_http_only_stays_restrictive_allowlist() { assert_eq!(policy.effective_net_allow().len(), 1); } +#[cfg(feature = "http")] #[test] fn builder_deny_only_http_stays_default_allow() { let policy = Sandbox::builder() @@ -349,6 +370,7 @@ fn builder_deny_only_http_stays_default_allow() { assert_eq!(policy.effective_net_allow().len(), 1); } +#[cfg(feature = "http")] #[test] fn builder_combined_http_merges_at_resolution_time() { let policy = Sandbox::builder() @@ -363,6 +385,7 @@ fn builder_combined_http_merges_at_resolution_time() { assert_eq!(policy.effective_net_allow().len(), 3); } +#[cfg(feature = "http")] #[test] fn outbound_mode_survives_policy_bincode_round_trip() { for builder in [ diff --git a/crates/sandlock-core/src/transparent_proxy/disabled.rs b/crates/sandlock-core/src/transparent_proxy/disabled.rs new file mode 100644 index 00000000..178d6edd --- /dev/null +++ b/crates/sandlock-core/src/transparent_proxy/disabled.rs @@ -0,0 +1,42 @@ +//! Stand-in for the HTTP ACL proxy when sandlock is built without the `http` +//! feature. The builder rejects every HTTP option first; these errors only +//! guard a policy that skipped the builder, such as a deserialized one. + +use std::collections::HashMap; +use std::io; +use std::net::{IpAddr, SocketAddr}; +use std::path::Path; +use std::sync::Arc; + +use crate::http::HttpRule; + +pub(crate) type OrigDestMap = Arc>>; + +pub(crate) struct CaMaterial { + pub(crate) cert_pem: String, + pub(crate) key_pem: String, +} + +pub(crate) struct HttpAclProxyHandle { + pub(crate) addr: SocketAddr, + pub(crate) orig_dest: OrigDestMap, +} + +fn disabled() -> io::Error { + io::Error::new(io::ErrorKind::Unsupported, "sandlock was built without the \"http\" feature") +} + +pub(crate) fn resolve_ca(_: Option<&Path>, _: Option<&Path>, _: bool) -> io::Result> { + Err(disabled()) +} + +pub(crate) async fn spawn_transparent_proxy( + _: Vec, + _: Vec, + _: Arc>, + _: Option<&str>, + _: Option<&str>, + _: Option>, +) -> io::Result { + Err(disabled()) +} diff --git a/crates/sandlock-core/tests/integration.rs b/crates/sandlock-core/tests/integration.rs index d21c1a63..c1ec3165 100644 --- a/crates/sandlock-core/tests/integration.rs +++ b/crates/sandlock-core/tests/integration.rs @@ -58,6 +58,7 @@ mod test_chroot; #[path = "integration/test_branch_action.rs"] mod test_branch_action; +#[cfg(feature = "http")] #[path = "integration/test_http_acl.rs"] mod test_http_acl; @@ -70,9 +71,11 @@ mod test_control; #[path = "integration/test_protection.rs"] mod test_protection; +#[cfg(feature = "http")] #[path = "integration/test_http_inject_ca.rs"] mod test_http_inject_ca; +#[cfg(feature = "http")] #[path = "integration/test_http_strict_tls.rs"] mod test_http_strict_tls; diff --git a/crates/sandlock-core/tests/integration/test_network.rs b/crates/sandlock-core/tests/integration/test_network.rs index 4933272c..f22ac5a8 100644 --- a/crates/sandlock-core/tests/integration/test_network.rs +++ b/crates/sandlock-core/tests/integration/test_network.rs @@ -1551,6 +1551,7 @@ async fn test_combined_hostname_resolving_into_denied_cidr_refused() { /// HTTP-derived rules live outside `net_allow` and must not promote the /// policy to combined. A live listener on an address outside both the HTTP /// allow host and the denylist must still connect. +#[cfg(feature = "http")] #[tokio::test] async fn test_deny_only_with_http_allow_stays_deny_only() { let listener = TcpListener::bind("127.0.0.2:0").unwrap(); diff --git a/crates/sandlock-ffi/Cargo.toml b/crates/sandlock-ffi/Cargo.toml index e71257d6..c00fcad5 100644 --- a/crates/sandlock-ffi/Cargo.toml +++ b/crates/sandlock-ffi/Cargo.toml @@ -11,9 +11,13 @@ description = "C ABI for sandlock process sandbox" [lib] crate-type = ["cdylib", "staticlib", "rlib"] +[features] +default = ["http"] +http = ["sandlock-core/http"] + [dependencies] libc = "0.2" -sandlock-core = { version = "0.8.9", path = "../sandlock-core" } +sandlock-core = { version = "0.8.9", path = "../sandlock-core", default-features = false } serde_json = "1" tokio = { version = "1", features = ["rt-multi-thread"] } diff --git a/crates/sandlock-oci/Cargo.toml b/crates/sandlock-oci/Cargo.toml index b08967a9..f1802f3e 100644 --- a/crates/sandlock-oci/Cargo.toml +++ b/crates/sandlock-oci/Cargo.toml @@ -16,8 +16,12 @@ path = "src/lib.rs" name = "sandlock-oci" path = "src/main.rs" +[features] +default = ["http"] +http = ["sandlock-core/http"] + [dependencies] -sandlock-core = { version = "0.8.9", path = "../sandlock-core" } +sandlock-core = { version = "0.8.9", path = "../sandlock-core", default-features = false } anyhow = "1" clap = { version = "4", features = ["derive"] } tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "io-util"] } @@ -29,4 +33,4 @@ oci-spec = { version = "0.7", features = ["runtime"] } [dev-dependencies] tempfile = "3" tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] } -sandlock-core = { path = "../sandlock-core" } \ No newline at end of file +sandlock-core = { path = "../sandlock-core", default-features = false } \ No newline at end of file