mirror of
https://github.com/usestrix/firewall.git
synced 2026-11-09 13:31:32 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ca30e444e |
No files matched your search
Generated
+73
-13
@@ -935,7 +935,7 @@ checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1331,9 +1331,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.105"
|
version = "0.3.106"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
|
checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@@ -1500,7 +1500,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
|
checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1630,6 +1630,24 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-foundation"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-system-configuration"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396"
|
||||||
|
dependencies = [
|
||||||
|
"objc2-core-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oid-registry"
|
name = "oid-registry"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
@@ -3111,6 +3129,7 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"which",
|
"which",
|
||||||
|
"whoami",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3588,6 +3607,15 @@ version = "0.11.1+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.14.7+wasi-0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
|
||||||
|
dependencies = [
|
||||||
|
"wasip2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasip2"
|
name = "wasip2"
|
||||||
version = "1.0.4+wasi-0.2.12"
|
version = "1.0.4+wasi-0.2.12"
|
||||||
@@ -3598,10 +3626,19 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasite"
|
||||||
version = "0.2.128"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
|
checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42"
|
||||||
|
dependencies = [
|
||||||
|
"wasi 0.14.7+wasi-0.2.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.129"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -3612,9 +3649,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.128"
|
version = "0.2.129"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
|
checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@@ -3622,9 +3659,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.128"
|
version = "0.2.129"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
|
checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -3635,13 +3672,23 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.128"
|
version = "0.2.129"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
|
checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.106"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-root-certs"
|
name = "webpki-root-certs"
|
||||||
version = "1.0.9"
|
version = "1.0.9"
|
||||||
@@ -3669,6 +3716,19 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "whoami"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"libredox",
|
||||||
|
"objc2-system-configuration",
|
||||||
|
"wasite",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wildcard"
|
name = "wildcard"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ tokio = { version = "1.53.1", features = ["io-util", "rt", "sync", "time"] }
|
|||||||
tracing = "0.1.44"
|
tracing = "0.1.44"
|
||||||
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
|
||||||
which = "8"
|
which = "8"
|
||||||
|
whoami = "2.1.3"
|
||||||
|
|
||||||
[target.'cfg(target_vendor = "apple")'.dependencies]
|
[target.'cfg(target_vendor = "apple")'.dependencies]
|
||||||
core-foundation = "0.10.1"
|
core-foundation = "0.10.1"
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ pub struct FirewallConfig {
|
|||||||
/// Packages never screened, by ecosystem: `{"npm": [{"name": "left-pad"}]}`.
|
/// Packages never screened, by ecosystem: `{"npm": [{"name": "left-pad"}]}`.
|
||||||
pub allow: HashMap<String, Vec<AllowedPackage>>,
|
pub allow: HashMap<String, Vec<AllowedPackage>>,
|
||||||
pub telemetry: bool,
|
pub telemetry: bool,
|
||||||
|
/// Reports installs to, and takes blocks from, a Strix workspace.
|
||||||
|
pub platform: Option<PlatformConfig>,
|
||||||
/// Not describable in the file yet, so this is always the built-in table.
|
/// Not describable in the file yet, so this is always the built-in table.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub registries: Vec<&'static Registry>,
|
pub registries: Vec<&'static Registry>,
|
||||||
@@ -33,6 +35,15 @@ pub struct AllowedPackage {
|
|||||||
pub version: Option<String>,
|
pub version: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct PlatformConfig {
|
||||||
|
pub api_key: String,
|
||||||
|
/// Where a self-hosted Strix lives.
|
||||||
|
#[serde(default = "hosted")]
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(default, deny_unknown_fields)]
|
#[serde(default, deny_unknown_fields)]
|
||||||
pub struct DenylistSource {
|
pub struct DenylistSource {
|
||||||
@@ -52,6 +63,7 @@ impl Default for FirewallConfig {
|
|||||||
denylist: DenylistSource::default(),
|
denylist: DenylistSource::default(),
|
||||||
allow: HashMap::new(),
|
allow: HashMap::new(),
|
||||||
telemetry: true,
|
telemetry: true,
|
||||||
|
platform: None,
|
||||||
registries: BUILT_IN.to_vec(),
|
registries: BUILT_IN.to_vec(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,6 +125,10 @@ impl FirewallConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hosted() -> String {
|
||||||
|
"https://app.usestrix.com".into()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn path() -> Option<PathBuf> {
|
pub fn path() -> Option<PathBuf> {
|
||||||
let dirs = ProjectDirs::from("com", "usestrix", "firewall")?;
|
let dirs = ProjectDirs::from("com", "usestrix", "firewall")?;
|
||||||
Some(dirs.config_dir().join("config.json"))
|
Some(dirs.config_dir().join("config.json"))
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ pub mod firewall;
|
|||||||
pub mod http;
|
pub mod http;
|
||||||
pub mod mitm;
|
pub mod mitm;
|
||||||
pub mod os;
|
pub mod os;
|
||||||
|
mod platform;
|
||||||
pub mod policies;
|
pub mod policies;
|
||||||
pub mod proxy;
|
pub mod proxy;
|
||||||
pub mod telemetry;
|
pub mod telemetry;
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
use crate::config::PlatformConfig;
|
||||||
|
use crate::policies::Package;
|
||||||
|
use crate::FirewallConfig;
|
||||||
|
use rama::http::client::EasyHttpWebClient;
|
||||||
|
use rama::http::header::{AUTHORIZATION, CONTENT_TYPE};
|
||||||
|
use rama::http::service::client::HttpClientExt;
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
use std::sync::{LazyLock, Mutex};
|
||||||
|
use std::time::Duration;
|
||||||
|
use time::format_description::well_known::Rfc3339;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
|
const TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
|
/// The most events the platform takes in one request.
|
||||||
|
const BATCH: usize = 500;
|
||||||
|
|
||||||
|
/// On when a readable config names a Strix API key.
|
||||||
|
static PLATFORM: LazyLock<Option<Platform>> = LazyLock::new(|| {
|
||||||
|
let config = FirewallConfig::load().ok()?.platform?;
|
||||||
|
Some(Platform { config, events: Mutex::default() })
|
||||||
|
});
|
||||||
|
|
||||||
|
struct Platform {
|
||||||
|
config: PlatformConfig,
|
||||||
|
events: Mutex<Vec<Value>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on() -> bool {
|
||||||
|
PLATFORM.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn record(eco: &str, pkg: &Package, reason: Option<&str>) {
|
||||||
|
let Some(platform) = &*PLATFORM else { return };
|
||||||
|
let event = json!({
|
||||||
|
"ecosystem": eco,
|
||||||
|
"name": pkg.name,
|
||||||
|
"version": pkg.version,
|
||||||
|
"blocked": reason.is_some(),
|
||||||
|
"reason": reason,
|
||||||
|
"occurred_at": OffsetDateTime::now_utc().format(&Rfc3339).ok(),
|
||||||
|
});
|
||||||
|
platform.events.lock().expect("platform lock").push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Events in a batch the platform did not take are dropped, not retried.
|
||||||
|
pub async fn send() {
|
||||||
|
let Some(platform) = &*PLATFORM else { return };
|
||||||
|
let events = std::mem::take(&mut *platform.events.lock().expect("platform lock"));
|
||||||
|
let url = format!("{}/api/v1/firewall/events", platform.config.url);
|
||||||
|
let client = EasyHttpWebClient::default();
|
||||||
|
for batch in events.chunks(BATCH) {
|
||||||
|
let body = json!({
|
||||||
|
"device": whoami::hostname().ok(),
|
||||||
|
"user": whoami::username().ok(),
|
||||||
|
"os": std::env::consts::OS,
|
||||||
|
"firewall_version": env!("CARGO_PKG_VERSION"),
|
||||||
|
"events": batch,
|
||||||
|
});
|
||||||
|
let request = client
|
||||||
|
.post(&url)
|
||||||
|
.header(CONTENT_TYPE, "application/json")
|
||||||
|
.header(AUTHORIZATION, format!("Bearer {}", platform.config.api_key));
|
||||||
|
match tokio::time::timeout(TIMEOUT, request.body(body.to_string()).send()).await {
|
||||||
|
Ok(Ok(response)) if response.status().is_success() => {
|
||||||
|
debug!(events = batch.len(), "installs reported")
|
||||||
|
}
|
||||||
|
Ok(Ok(response)) => warn!(status = %response.status(), %url, "platform refused installs"),
|
||||||
|
Ok(Err(error)) => warn!(%error, %url, "could not report installs"),
|
||||||
|
Err(_) => warn!(%url, "reporting installs timed out"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-3
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::platform;
|
||||||
use crate::policies::Package;
|
use crate::policies::Package;
|
||||||
use crate::{Firewall, FirewallConfig};
|
use crate::{Firewall, FirewallConfig};
|
||||||
use rama::http::client::EasyHttpWebClient;
|
use rama::http::client::EasyHttpWebClient;
|
||||||
@@ -40,7 +41,7 @@ struct Telemetry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn screened(firewall: &Firewall, url: &str, reason: Option<&str>) {
|
pub fn screened(firewall: &Firewall, url: &str, reason: Option<&str>) {
|
||||||
if TELEMETRY.is_none() {
|
if !on() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let Some((registry, rest)) = firewall.match_on(url, |r| r.upstream) else {
|
let Some((registry, rest)) = firewall.match_on(url, |r| r.upstream) else {
|
||||||
@@ -52,6 +53,7 @@ pub fn screened(firewall: &Firewall, url: &str, reason: Option<&str>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn screened_package(eco: &str, pkg: &Package, reason: Option<&str>) {
|
pub fn screened_package(eco: &str, pkg: &Package, reason: Option<&str>) {
|
||||||
|
platform::record(eco, pkg, reason);
|
||||||
let mut properties = json!({ "eco": eco, "blocked": reason.is_some() });
|
let mut properties = json!({ "eco": eco, "blocked": reason.is_some() });
|
||||||
if let Some(reason) = reason {
|
if let Some(reason) = reason {
|
||||||
properties["package"] = pkg.label().into();
|
properties["package"] = pkg.label().into();
|
||||||
@@ -69,7 +71,7 @@ pub fn error(kind: &'static str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn heartbeat() {
|
pub fn heartbeat() {
|
||||||
if TELEMETRY.is_none() {
|
if !on() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tokio::spawn(async {
|
tokio::spawn(async {
|
||||||
@@ -81,7 +83,7 @@ pub fn heartbeat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn flush() {
|
pub fn flush() {
|
||||||
if TELEMETRY.is_none() {
|
if !on() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match Runtime::try_new() {
|
match Runtime::try_new() {
|
||||||
@@ -90,6 +92,11 @@ pub fn flush() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// PostHog, or the Strix platform when the config names one.
|
||||||
|
fn on() -> bool {
|
||||||
|
TELEMETRY.is_some() || platform::on()
|
||||||
|
}
|
||||||
|
|
||||||
fn record(event: &str, mut properties: Value) {
|
fn record(event: &str, mut properties: Value) {
|
||||||
let Some(telemetry) = &*TELEMETRY else { return };
|
let Some(telemetry) = &*TELEMETRY else { return };
|
||||||
properties["distinct_id"] = telemetry.session.clone().into();
|
properties["distinct_id"] = telemetry.session.clone().into();
|
||||||
@@ -103,6 +110,7 @@ fn record(event: &str, mut properties: Value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send() {
|
pub async fn send() {
|
||||||
|
platform::send().await;
|
||||||
let Some(telemetry) = &*TELEMETRY else { return };
|
let Some(telemetry) = &*TELEMETRY else { return };
|
||||||
let batch = std::mem::take(&mut *telemetry.events.lock().expect("telemetry lock"));
|
let batch = std::mem::take(&mut *telemetry.events.lock().expect("telemetry lock"));
|
||||||
if batch.is_empty() {
|
if batch.is_empty() {
|
||||||
|
|||||||
Reference in new issue
Block a user