Compare commits

..
Author SHA1 Message Date
Ian 4fc0d64771 Move the macOS uninstall out of the CLI into its own binary 2026-10-22 15:09:36 -04:00
ian-at-strixandIan Graham Martinez 9e457c4d69 Screen redirected registry traffic in a Windows service (#10)
* Screen redirected registry traffic in a Windows service

Each registry host gets its own loopback address, where a LocalSystem
service serves mitm_service, connecting upstream through a resolver that
ignores the hosts file. It trusts its CA machine-wide and serves the empty
CRL that schannel needs.

The service writes the hosts-file block once it listens. Pausing it removes
the block and continuing rewrites it; a stop, crash or shutdown leaves it,
so a restart fails closed and picks up a changed host list.

* Explain the data folder ACL and the root store arguments

* Use schannel for the root store

* Free the parsed security descriptor on drop instead of through a closure

* Rotate the service log daily with tracing-appender

---------

Co-authored-by: Ian Graham Martinez <[email protected]>
2026-10-22 14:49:36 -04:00
7 changed files with 29 additions and 28 deletions

No files matched your search

+4
View File
@@ -32,6 +32,10 @@ path = "src/main.rs"
name = "firewall-service"
path = "src/bin/firewall-service.rs"
[[bin]]
name = "firewall-uninstall"
path = "src/bin/firewall-uninstall.rs"
[[example]]
name = "proxy-test"
path = "tests/proxy-test/main.rs"
+16
View File
@@ -0,0 +1,16 @@
//! Run as root by the macOS app's uninstall.
#[cfg(target_os = "macos")]
fn main() {
firewall::init_logging("info");
if let Err(e) = firewall::os::macos::uninstall() {
tracing::error!("could not uninstall: {e}");
firewall::telemetry::error("uninstall");
firewall::telemetry::flush();
std::process::exit(1)
}
}
/// Only macOS installs anything system-wide.
#[cfg(not(target_os = "macos"))]
fn main() {}
-13
View File
@@ -12,10 +12,6 @@ fn main() {
firewall::init_logging("info");
serve()
}
if matches.subcommand_matches("uninstall").is_some() {
firewall::init_logging("info");
uninstall()
}
let mut command = matches.get_many::<String>("COMMAND").unwrap().cloned();
let program = command.next().unwrap();
firewall::init_logging("warn");
@@ -44,8 +40,6 @@ fn options() -> Command {
Command::new("serve")
.about("Serve a local registry for package managers configured to use it"),
)
// Run as root by the macOS app's uninstall.
.subcommand(Command::new("uninstall").hide(true))
.arg(
Arg::new("COMMAND")
.help("A command to run with its package downloads screened")
@@ -76,13 +70,6 @@ fn serve() -> ! {
}
}
fn uninstall() -> ! {
match firewall::os::uninstall() {
Ok(()) => exit(0),
Err(e) => fail("uninstall", &format!("could not uninstall: {e}")),
}
}
fn run(invocation: Invocation) -> ! {
telemetry::set_mode("wrapper");
let command = &invocation.program;
+1 -7
View File
@@ -4,16 +4,10 @@ pub mod macos;
pub mod windows;
#[cfg(target_os = "macos")]
pub use macos::{system_config, uninstall};
pub use macos::system_config;
#[cfg(windows)]
pub use windows::system_config;
/// Elsewhere nothing is installed system-wide.
#[cfg(not(target_os = "macos"))]
pub fn uninstall() -> Result<(), rama::error::BoxError> {
Ok(())
}
#[cfg(not(any(target_os = "macos", windows)))]
pub fn system_config() -> Option<std::path::PathBuf> {
None
+3 -3
View File
@@ -280,10 +280,10 @@ final class FirewallModel: NSObject, ObservableObject {
return true
}
/// The extension wrote these as root, so the bundled CLI removes them behind an admin prompt; returns what to report if it failed.
/// The extension wrote these as root, so the bundled firewall-uninstall removes them behind an admin prompt; returns what to report if it failed.
private func removeStoredCA() -> String? {
let cli = Bundle.main.path(forAuxiliaryExecutable: "firewall") ?? ""
let script = "do shell script quoted form of \"\(cli)\" & \" uninstall\" with administrator privileges"
let uninstaller = Bundle.main.path(forAuxiliaryExecutable: "firewall-uninstall") ?? ""
let script = "do shell script quoted form of \"\(uninstaller)\" with administrator privileges"
var error: NSDictionary?
NSAppleScript(source: script)?.executeAndReturnError(&error)
if let error {
+1 -1
View File
@@ -55,7 +55,7 @@ sign() {
echo "==> Signing with $IDENTITY"
cp "$(profile_for $APP_ID)" "$APP/Contents/embedded.provisionprofile"
cp "$(profile_for $EXT_ID)" "$EXT/Contents/embedded.provisionprofile"
codesign --force --options runtime --timestamp -s "$IDENTITY" "$APP/Contents/MacOS/firewall"
codesign --force --options runtime --timestamp -s "$IDENTITY" "$APP/Contents/MacOS/firewall-uninstall"
codesign --force --options runtime --timestamp \
--entitlements Extension/Extension.entitlements -s "$IDENTITY" "$EXT"
codesign --force --options runtime --timestamp \
+4 -4
View File
@@ -118,14 +118,14 @@ targets:
- target: Extension
embed: true
codeSign: true
# Uninstall runs the CLI as root to remove what the extension left.
# Uninstall runs this as root to remove what the extension left.
postBuildScripts:
- name: Embed the CLI
- name: Embed firewall-uninstall
basedOnDependencyAnalysis: false
script: |
export PATH="$HOME/.cargo/bin:$PATH"
unset CC CXX CFLAGS CXXFLAGS
export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"
cd "$SRCROOT/../core"
cargo build --bin firewall --target aarch64-apple-darwin $([ "$CONFIGURATION" = Debug ] || echo --release)
cp "target/aarch64-apple-darwin/$CARGO_PROFILE/firewall" "$TARGET_BUILD_DIR/$EXECUTABLE_FOLDER_PATH/"
cargo build --bin firewall-uninstall --target aarch64-apple-darwin $([ "$CONFIGURATION" = Debug ] || echo --release)
cp "target/aarch64-apple-darwin/$CARGO_PROFILE/firewall-uninstall" "$TARGET_BUILD_DIR/$EXECUTABLE_FOLDER_PATH/"