mirror of
https://github.com/usestrix/firewall.git
synced 2026-11-09 13:31:32 -10:00
* Hold a held-back download open so allowing it lets it through * Allow a held-back package once from the app, releasing the waiting download
123 lines
5.9 KiB
Rust
123 lines
5.9 KiB
Rust
use firewall::config::{path, AllowedPackage};
|
|
use firewall::policies::filters::consult;
|
|
use firewall::policies::{screen, Package};
|
|
use firewall::{Firewall, FirewallConfig};
|
|
|
|
#[test]
|
|
fn a_file_only_has_to_set_what_it_changes() {
|
|
let config: FirewallConfig = serde_json::from_str(r#"{"fail_open": false}"#).expect("parses");
|
|
assert!(!config.fail_open);
|
|
assert_eq!(config.port.get(), firewall::DEFAULT_PORT);
|
|
}
|
|
|
|
#[test]
|
|
fn a_misspelled_setting_is_an_error() {
|
|
assert!(serde_json::from_str::<FirewallConfig>(r#"{"fail-open": false}"#).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn nonsense_values_are_errors() {
|
|
let file = std::env::temp_dir().join(format!("firewall-nonsense-{}.json", std::process::id()));
|
|
for bad in [
|
|
r#"{"allow": {"nmp": [{"name": "left-pad"}]}}"#,
|
|
r#"{"denylist": {"poll_secs": 0}}"#,
|
|
r#"{"denylist": {"base": "raw.githubusercontent.com/usestrix"}}"#,
|
|
r#"{"denylist": {"revision": "https://"}}"#,
|
|
r#"{"port": 0}"#,
|
|
r#"{"min_age_hours": 18446744073709551615}"#,
|
|
r#"{"registries": {"npm": ["http://npm.example.com/"]}}"#,
|
|
r#"{"registries": {"npm": ["https://npm.example.com"]}}"#,
|
|
r#"{"registries": {"npm": ["https://npm.example.com:8443/"]}}"#,
|
|
r#"{"registries": {"npm": ["https://[email protected]/"]}}"#,
|
|
r#"{"registries": {"npm": ["https://npm.example.com/?a=/"]}}"#,
|
|
] {
|
|
std::fs::write(&file, bad).expect("write");
|
|
assert!(FirewallConfig::load_from(std::slice::from_ref(&file)).is_err(), "{bad}");
|
|
}
|
|
std::fs::remove_file(&file).ok();
|
|
}
|
|
|
|
#[test]
|
|
fn the_config_lives_under_the_config_directory() {
|
|
let path = path().expect("a home directory");
|
|
assert!(path.ends_with("config.json"), "{}", path.display());
|
|
}
|
|
|
|
#[test]
|
|
fn a_later_file_wins_and_allow_lists_add_up() {
|
|
let dir = std::env::temp_dir().join(format!("firewall-layers-{}", std::process::id()));
|
|
std::fs::create_dir_all(&dir).expect("temp dir");
|
|
let user = dir.join("user.json");
|
|
let system = dir.join("system.json");
|
|
std::fs::write(&user, r#"{"min_age_hours": 1, "fail_open": false, "allow": {"npm": [{"name": "a"}]}}"#).expect("write");
|
|
std::fs::write(&system, r#"{"min_age_hours": 24, "allow": {"npm": [{"name": "b"}]}}"#).expect("write");
|
|
let missing = dir.join("missing.json");
|
|
let config = FirewallConfig::load_from(&[user, system, missing]).expect("loads");
|
|
std::fs::remove_dir_all(&dir).ok();
|
|
assert_eq!(config.min_age_hours, 24);
|
|
assert!(!config.fail_open);
|
|
let names: Vec<&str> = config.allow["npm"].iter().map(|p| p.name.as_str()).collect();
|
|
assert_eq!(names, ["a", "b"]);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn a_mirror_is_screened_like_the_registry_it_copies() {
|
|
let config = r#"{"registries": {"npm": ["https://npm.example.com/"]}}"#;
|
|
let firewall = Firewall::new(serde_json::from_str(config).expect("parses"));
|
|
assert!(firewall.hosts().contains(&"npm.example.com"));
|
|
let url = "https://npm.example.com/left-pad/-/left-pad-1.3.0.tgz";
|
|
let (registry, rest) = firewall.match_on(url, |r| r.upstream).expect("the mirror");
|
|
assert_eq!(firewall.resolve(registry, rest).pkg.map(|p| p.label()), Some("[email protected]".into()));
|
|
let unknown = std::env::temp_dir().join(format!("firewall-mirror-{}.json", std::process::id()));
|
|
std::fs::write(&unknown, r#"{"registries": {"nmp": ["https://npm.example.com/"]}}"#).expect("write");
|
|
let loaded = FirewallConfig::load_from(std::slice::from_ref(&unknown));
|
|
std::fs::remove_file(&unknown).ok();
|
|
assert!(loaded.is_err());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn an_allowed_version_is_still_checked_for_malware() {
|
|
// The unreachable denylist shows that it still runs.
|
|
let config = r#"{"allow": {"go": [{"name": "golang.org/x/net", "version": "v0.20.0"}]}, "denylist": {"base": "http://127.0.0.1:1"}}"#;
|
|
let firewall = Firewall::new(serde_json::from_str(config).expect("parses"));
|
|
let (registry, _) = firewall.match_on("/go/golang.org/x/net/@v/list", |r| r.prefix).expect("go");
|
|
let pkg = Package { name: "golang.org/x/net".into(), version: Some("0.20.0".into()) };
|
|
let verdict = consult(&firewall, registry, &pkg).await;
|
|
assert_eq!(verdict.map(|(filter, _)| filter), Some("denylist"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn failing_closed_blocks_what_could_not_be_checked() {
|
|
let config = r#"{"fail_open": false, "denylist": {"base": "http://127.0.0.1:1"}}"#;
|
|
let firewall = Firewall::new(serde_json::from_str(config).expect("parses"));
|
|
let url = "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz";
|
|
let blocked = screen(url, &firewall).await.unwrap_or_default();
|
|
assert!(blocked.contains("could not be checked"), "{blocked:?}");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn lifting_the_age_check_still_checks_for_malware() {
|
|
let config = r#"{"denylist": {"base": "http://127.0.0.1:1"}}"#;
|
|
let firewall = Firewall::new(serde_json::from_str(config).expect("parses"));
|
|
let (registry, _) = firewall.match_on("/go/golang.org/x/net/@v/list", |r| r.prefix).expect("go");
|
|
let pkg = Package { name: "golang.org/x/net".into(), version: Some("0.20.0".into()) };
|
|
firewall.lift_age_check("go", AllowedPackage { name: pkg.name.clone(), version: None }, None);
|
|
let (filter, _) = consult(&firewall, registry, &pkg).await.expect("the denylist still runs");
|
|
assert_eq!(filter, "denylist");
|
|
}
|
|
|
|
#[test]
|
|
fn allowing_keeps_the_rest_of_the_file() {
|
|
let path = std::env::temp_dir().join(format!("firewall-allow-{}.json", std::process::id()));
|
|
std::fs::write(&path, r#"{"fail_open": false}"#).expect("write");
|
|
for _ in 0..2 {
|
|
firewall::config::allow(&path, "npm", "left-pad", "1.3.0").expect("allow");
|
|
}
|
|
let text = std::fs::read_to_string(&path).expect("read");
|
|
std::fs::remove_file(&path).ok();
|
|
let config: FirewallConfig = serde_json::from_str(&text).expect("parses");
|
|
assert!(!config.fail_open);
|
|
assert_eq!(config.allow["npm"].len(), 1);
|
|
assert_eq!(config.allow["npm"][0].version.as_deref(), Some("1.3.0"));
|
|
}
|