mirror of
https://github.com/usestrix/firewall.git
synced 2026-11-09 05:19:35 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10fdd0f272 | ||
|
|
bdcc2e93dc |
No files matched your search
@@ -3,6 +3,7 @@ name = "strix-firewall"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
authors = ["Strix"]
|
||||
description = "Screens package downloads for known malware before they reach your machine"
|
||||
repository = "https://github.com/usestrix/firewall"
|
||||
homepage = "https://github.com/usestrix/firewall"
|
||||
@@ -18,6 +19,12 @@ x86_64-apple-darwin = ["firewall"]
|
||||
aarch64-unknown-linux-gnu = ["firewall"]
|
||||
x86_64-unknown-linux-gnu = ["firewall"]
|
||||
|
||||
[package.metadata.wix]
|
||||
upgrade-guid = "D944C2F6-BF55-4B21-A60E-1BBDB1A66293"
|
||||
path-guid = "033B1050-23D0-4F5A-A0D3-F4757B4B67E0"
|
||||
license = false
|
||||
eula = false
|
||||
|
||||
[lib]
|
||||
name = "firewall"
|
||||
# The macOS extension links a staticlib, built on demand:
|
||||
@@ -32,6 +39,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"
|
||||
|
||||
@@ -2,14 +2,25 @@
|
||||
|
||||
#[cfg(windows)]
|
||||
fn main() {
|
||||
if let Err(e) = firewall::os::windows::run_service() {
|
||||
tracing::error!("could not run the service: {e}");
|
||||
firewall::telemetry::error("service");
|
||||
firewall::telemetry::flush();
|
||||
std::process::exit(1)
|
||||
// Run by the MSI as it removes the firewall.
|
||||
if std::env::args().nth(1).as_deref() == Some("uninstall") {
|
||||
firewall::init_logging("info");
|
||||
if let Err(e) = firewall::os::windows::uninstall() {
|
||||
fail("uninstall", &format!("could not uninstall: {e}"))
|
||||
}
|
||||
} else if let Err(e) = firewall::os::windows::run_service() {
|
||||
fail("service", &format!("could not run the service: {e}"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn fail(kind: &'static str, message: &str) -> ! {
|
||||
tracing::error!("{message}");
|
||||
firewall::telemetry::error(kind);
|
||||
firewall::telemetry::flush();
|
||||
std::process::exit(1)
|
||||
}
|
||||
|
||||
/// The service is Windows-only.
|
||||
#[cfg(not(windows))]
|
||||
fn main() {}
|
||||
@@ -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() {}
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -7,9 +7,11 @@ use schannel::cert_context::CertContext;
|
||||
use schannel::cert_store::{CertAdd, CertStore};
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use tracing::warn;
|
||||
use windows::Win32::Security::Authorization::{GetNamedSecurityInfoW, SE_FILE_OBJECT};
|
||||
use windows::Win32::Security::*;
|
||||
use windows_registry::Type;
|
||||
|
||||
/// SYSTEM and admins write, users only read, so no one else can plant a CA for node.
|
||||
const DATA_DIR_ACL: &str = concat!(
|
||||
@@ -20,6 +22,9 @@ const DATA_DIR_ACL: &str = concat!(
|
||||
"(A;OICI;FRFX;;;BU)", // allow Users read and execute only
|
||||
);
|
||||
|
||||
/// 1970 in FILETIME, which counts 100 ns from 1601.
|
||||
const FILETIME_UNIX_EPOCH: u128 = 116_444_736_000_000_000;
|
||||
|
||||
/// Trusted machine-wide so restarts keep it; replaced if the hosts change.
|
||||
pub fn load_or_create_ca(
|
||||
hosts: &[&str],
|
||||
@@ -34,6 +39,16 @@ pub fn load_or_create_ca(
|
||||
LOCAL_MACHINE
|
||||
.create(REGISTRY_KEY)?
|
||||
.set_string("ca", ca.to_blob(hosts))?;
|
||||
// Windows discards CRLs it cached before now, in case an old CA's is cached at our URL.
|
||||
let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos() / 100
|
||||
+ FILETIME_UNIX_EPOCH;
|
||||
let chain = r"SOFTWARE\Microsoft\Cryptography\OID\EncodingType 0\CertDllCreateCertificateChainEngine\Config";
|
||||
let resync = (now as u64).to_le_bytes();
|
||||
LOCAL_MACHINE.create(chain)?.set_bytes(
|
||||
"ChainCacheResyncFiletime",
|
||||
Type::Bytes,
|
||||
&resync,
|
||||
)?;
|
||||
ca
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
mod ca;
|
||||
mod service;
|
||||
mod tpm;
|
||||
mod uninstall;
|
||||
|
||||
pub use service::run as run_service;
|
||||
pub use tpm::CaKey;
|
||||
pub use uninstall::uninstall;
|
||||
|
||||
use rama::error::BoxError;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
@@ -27,6 +27,13 @@ impl CaKey {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn delete_machine_key() -> Result<(), BoxError> {
|
||||
if let Some(key) = open_key(KEY_NAME, NCRYPT_MACHINE_KEY_FLAG)? {
|
||||
key.delete_from_tpm()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A per-user key under a random name, which needs no elevation; `delete_from_tpm` it after.
|
||||
pub fn create_test_key() -> WinResult<Self> {
|
||||
let name = format!("Strix Firewall test {:016x}", rand::random::<u64>());
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
use super::ca::remove_our_cas_except;
|
||||
use super::tpm::CaKey;
|
||||
use super::*;
|
||||
use std::io::ErrorKind;
|
||||
use std::path::Path;
|
||||
use tracing::info;
|
||||
|
||||
/// Run by the MSI after it deletes the service, which would restore the hosts entries. Does
|
||||
/// nothing for what is already gone.
|
||||
pub fn uninstall() -> Result<(), BoxError> {
|
||||
write_hosts_entries(&[])?;
|
||||
remove_our_cas_except(None)?;
|
||||
none_if_missing(LOCAL_MACHINE.remove_tree(REGISTRY_KEY))?;
|
||||
CaKey::delete_machine_key()?;
|
||||
let entries = match std::fs::read_dir(data_dir()) {
|
||||
Err(error) if error.kind() == ErrorKind::NotFound => Vec::new(),
|
||||
entries => entries?.collect::<Result<Vec<_>, _>>()?,
|
||||
};
|
||||
for path in entries.into_iter().map(|entry| entry.path()) {
|
||||
// The admin's config stays for a reinstall, as on macOS.
|
||||
if path != config_file() {
|
||||
remove_path(&path)?;
|
||||
}
|
||||
}
|
||||
// Fails while the config is in it.
|
||||
let _ = std::fs::remove_dir(data_dir());
|
||||
info!("removed the CA, its key and the data folder");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A file or a folder, if it is there.
|
||||
fn remove_path(path: &Path) -> std::io::Result<()> {
|
||||
let removed = match path.is_dir() {
|
||||
true => std::fs::remove_dir_all(path),
|
||||
false => std::fs::remove_file(path),
|
||||
};
|
||||
match removed {
|
||||
Err(error) if error.kind() == ErrorKind::NotFound => Ok(()),
|
||||
removed => removed,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
<?xml version='1.0' encoding='windows-1252'?>
|
||||
<!--
|
||||
Copyright (C) 2017 Christopher R. Field.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64 ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
<?else ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
|
||||
<?endif ?>
|
||||
|
||||
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
|
||||
|
||||
<Product
|
||||
Id='*'
|
||||
Name='strix-firewall'
|
||||
UpgradeCode='D944C2F6-BF55-4B21-A60E-1BBDB1A66293'
|
||||
Manufacturer='Strix'
|
||||
Language='1033'
|
||||
Codepage='1252'
|
||||
Version='$(var.Version)'>
|
||||
|
||||
<Package Id='*'
|
||||
Keywords='Installer'
|
||||
Description='Screens package downloads for known malware before they reach your machine'
|
||||
Manufacturer='Strix'
|
||||
InstallerVersion='500'
|
||||
Languages='1033'
|
||||
Compressed='yes'
|
||||
InstallScope='perMachine'
|
||||
SummaryCodepage='1252'
|
||||
/>
|
||||
|
||||
<MajorUpgrade
|
||||
Schedule='afterInstallInitialize'
|
||||
DowngradeErrorMessage='A newer version of [ProductName] is already installed. Setup will now exit.'/>
|
||||
|
||||
<Media Id='1' Cabinet='media1.cab' EmbedCab='yes' DiskPrompt='CD-ROM #1'/>
|
||||
<Property Id='DiskPrompt' Value='strix-firewall Installation'/>
|
||||
|
||||
<Directory Id='TARGETDIR' Name='SourceDir'>
|
||||
<Directory Id='$(var.PlatformProgramFilesFolder)' Name='PFiles'>
|
||||
<Directory Id='APPLICATIONFOLDER' Name='Strix Firewall'>
|
||||
<Component Id='Path' Guid='033B1050-23D0-4F5A-A0D3-F4757B4B67E0' KeyPath='yes'>
|
||||
<Environment
|
||||
Id='PATH'
|
||||
Name='PATH'
|
||||
Value='[APPLICATIONFOLDER]'
|
||||
Permanent='no'
|
||||
Part='last'
|
||||
Action='set'
|
||||
System='yes'/>
|
||||
</Component>
|
||||
<Component Id='binary0' Guid='*'>
|
||||
<File
|
||||
Id='exe0'
|
||||
Name='firewall.exe'
|
||||
DiskId='1'
|
||||
Source='$(var.CargoTargetBinDir)\firewall.exe'
|
||||
KeyPath='yes'/>
|
||||
</Component>
|
||||
<Component Id='binary1' Guid='*'>
|
||||
<File
|
||||
Id='exe1'
|
||||
Name='firewall-service.exe'
|
||||
DiskId='1'
|
||||
Source='$(var.CargoTargetBinDir)\firewall-service.exe'
|
||||
KeyPath='yes'/>
|
||||
<ServiceInstall
|
||||
Name='StrixFirewall'
|
||||
DisplayName='Strix Firewall'
|
||||
Description='Screens package downloads for known malware'
|
||||
Type='ownProcess'
|
||||
Start='auto'
|
||||
ErrorControl='normal'
|
||||
Vital='yes'>
|
||||
<!-- The default, except that interactive users (IU) may also start (RP), stop (WP) and pause (DT) it, as the tray does. -->
|
||||
<PermissionEx Sddl='D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWRPWPDTLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)'/>
|
||||
</ServiceInstall>
|
||||
<!-- Waits, so a service that cannot start fails the install. -->
|
||||
<ServiceControl Id='StrixFirewall' Name='StrixFirewall' Start='install' Stop='both' Remove='uninstall' Wait='yes'/>
|
||||
<!-- For clients that read no trust store. SSL_CERT_FILE would replace OpenSSL's bundle. -->
|
||||
<Environment Id='NODE_EXTRA_CA_CERTS' Name='NODE_EXTRA_CA_CERTS' Value='[CommonAppDataFolder]Strix Firewall\ca.pem' Action='set' System='yes'/>
|
||||
<Environment Id='UV_SYSTEM_CERTS' Name='UV_SYSTEM_CERTS' Value='1' Action='set' System='yes'/>
|
||||
</Component>
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<Feature
|
||||
Id='Binaries'
|
||||
Title='Application'
|
||||
Description='Installs the firewall and its service.'
|
||||
Level='1'
|
||||
ConfigurableDirectory='APPLICATIONFOLDER'
|
||||
AllowAdvertise='no'
|
||||
Display='expand'
|
||||
Absent='disallow'>
|
||||
<ComponentRef Id='binary0'/>
|
||||
<ComponentRef Id='binary1'/>
|
||||
|
||||
<Feature
|
||||
Id='Environment'
|
||||
Title='PATH Environment Variable'
|
||||
Description='Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.'
|
||||
Level='1'
|
||||
Absent='allow'>
|
||||
<ComponentRef Id='Path'/>
|
||||
</Feature>
|
||||
</Feature>
|
||||
|
||||
<!-- Removing the app must not leave the hosts file pointing at a service that is gone. -->
|
||||
<CustomAction
|
||||
Id='UninstallFirewall'
|
||||
FileKey='exe1'
|
||||
ExeCommand='uninstall'
|
||||
Execute='deferred'
|
||||
Impersonate='no'
|
||||
Return='check'/>
|
||||
<!--
|
||||
Registry hosts lead nowhere while the service is down, so bring it back, also when it fails to
|
||||
start. Through sc.exe, as MsiServiceConfigFailureActions cannot restart a service.
|
||||
-->
|
||||
<CustomAction
|
||||
Id='SetFailureActions'
|
||||
Directory='APPLICATIONFOLDER'
|
||||
ExeCommand='"[System64Folder]sc.exe" failure StrixFirewall reset= 3600 actions= restart/5000/restart/5000/restart/5000'
|
||||
Execute='deferred'
|
||||
Impersonate='no'
|
||||
Return='check'/>
|
||||
<CustomAction
|
||||
Id='SetFailureFlag'
|
||||
Directory='APPLICATIONFOLDER'
|
||||
ExeCommand='"[System64Folder]sc.exe" failureflag StrixFirewall 1'
|
||||
Execute='deferred'
|
||||
Impersonate='no'
|
||||
Return='check'/>
|
||||
<InstallExecuteSequence>
|
||||
<!-- An upgrade removes the old version too, but should keep screening. -->
|
||||
<Custom Action='UninstallFirewall' After='DeleteServices'>REMOVE~="ALL" AND NOT UPGRADINGPRODUCTCODE</Custom>
|
||||
<Custom Action='SetFailureActions' After='InstallServices'>NOT REMOVE~="ALL"</Custom>
|
||||
<Custom Action='SetFailureFlag' After='SetFailureActions'>NOT REMOVE~="ALL"</Custom>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<SetProperty Id='ARPINSTALLLOCATION' Value='[APPLICATIONFOLDER]' After='CostFinalize'/>
|
||||
<Property Id='ARPHELPLINK' Value='https://github.com/usestrix/firewall'/>
|
||||
|
||||
<UI>
|
||||
<UIRef Id='WixUI_FeatureTree'/>
|
||||
<Publish Dialog='WelcomeDlg' Control='Next' Event='NewDialog' Value='CustomizeDlg' Order='99'>1</Publish>
|
||||
<Publish Dialog='CustomizeDlg' Control='Back' Event='NewDialog' Value='WelcomeDlg' Order='99'>1</Publish>
|
||||
</UI>
|
||||
</Product>
|
||||
|
||||
</Wix>
|
||||
+3
-1
@@ -4,7 +4,9 @@ members = ["cargo:core"]
|
||||
[dist]
|
||||
cargo-dist-version = "0.32.0"
|
||||
ci = "github"
|
||||
installers = ["shell", "powershell", "homebrew"]
|
||||
installers = ["shell", "powershell", "homebrew", "msi"]
|
||||
# core/wix/main.wxs is edited to install the service.
|
||||
allow-dirty = ["msi"]
|
||||
tap = "usestrix/homebrew-tap"
|
||||
publish-jobs = ["homebrew", "./publish-crates"]
|
||||
targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]
|
||||
@@ -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
@@ -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
@@ -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/"
|
||||
Reference in new issue
Block a user