Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions app/security/opensnitch/manage-rules.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
OPENSNITCH_RULES_DIR="/etc/opensnitchd/rules"
OPENSNITCH_CONFIGS_DIR="$MANJIKAZE_DIR/configs/opensnitch"

if ! is_installed "opensnitch"; then
status "OpenSnitch is not installed. Please install it first."
return
fi

if [ ! -d "$OPENSNITCH_RULES_DIR" ]; then
status "OpenSnitch rules directory not found at $OPENSNITCH_RULES_DIR"
return
fi

# List available application rule sets from configs/opensnitch/
AVAILABLE_APPS=($(ls -d "$OPENSNITCH_CONFIGS_DIR"/*/ 2>/dev/null | xargs -n1 basename))

if [ ${#AVAILABLE_APPS[@]} -eq 0 ]; then
status "No application rule sets found in $OPENSNITCH_CONFIGS_DIR"
return
fi

# Determine current status for each app (active/inactive)
OPTIONS=()
for app in "${AVAILABLE_APPS[@]}"; do
# Count rule files for this app
rule_count=$(ls "$OPENSNITCH_CONFIGS_DIR/$app/"*.json 2>/dev/null | wc -l)

# Check if rules are currently symlinked (active)
first_rule=$(ls "$OPENSNITCH_CONFIGS_DIR/$app/"*.json 2>/dev/null | head -1)
if [ -n "$first_rule" ]; then
rule_name=$(basename "$first_rule")
if [ -L "$OPENSNITCH_RULES_DIR/$rule_name" ]; then
OPTIONS+=("$app ($rule_count rules, active)")
else
OPTIONS+=("$app ($rule_count rules, inactive)")
fi
fi
done

ACTION=$(gum choose "Activate rules" "Deactivate rules" "Show status" --header "OpenSnitch Firewall Rules")

case "$ACTION" in
"Activate rules")
# Show only inactive apps
INACTIVE_APPS=()
for app in "${AVAILABLE_APPS[@]}"; do
first_rule=$(ls "$OPENSNITCH_CONFIGS_DIR/$app/"*.json 2>/dev/null | head -1)
if [ -n "$first_rule" ]; then
rule_name=$(basename "$first_rule")
if [ ! -L "$OPENSNITCH_RULES_DIR/$rule_name" ]; then
INACTIVE_APPS+=("$app")
fi
fi
done

if [ ${#INACTIVE_APPS[@]} -eq 0 ]; then
status "All application rules are already active."
return
fi

SELECTED=$(gum choose "${INACTIVE_APPS[@]}" --no-limit --height 20 --header "Select applications to activate firewall rules for")

if [ -z "$SELECTED" ]; then
status "No applications selected."
return
fi

for app in $SELECTED; do
status "Activating rules for $app..."
for rule_file in "$OPENSNITCH_CONFIGS_DIR/$app/"*.json; do
rule_name=$(basename "$rule_file")
sudo ln -sf "$rule_file" "$OPENSNITCH_RULES_DIR/$rule_name"
status " Linked $rule_name"
done
done
status "Restarting OpenSnitch daemon to load new rules..."
sudo systemctl restart opensnitchd.service
status "Rules activated."
;;

"Deactivate rules")
# Show only active apps
ACTIVE_APPS=()
for app in "${AVAILABLE_APPS[@]}"; do
first_rule=$(ls "$OPENSNITCH_CONFIGS_DIR/$app/"*.json 2>/dev/null | head -1)
if [ -n "$first_rule" ]; then
rule_name=$(basename "$first_rule")
if [ -L "$OPENSNITCH_RULES_DIR/$rule_name" ]; then
ACTIVE_APPS+=("$app")
fi
fi
done

if [ ${#ACTIVE_APPS[@]} -eq 0 ]; then
status "No application rules are currently active."
return
fi

SELECTED=$(gum choose "${ACTIVE_APPS[@]}" --no-limit --height 20 --header "Select applications to deactivate firewall rules for")

if [ -z "$SELECTED" ]; then
status "No applications selected."
return
fi

for app in $SELECTED; do
status "Deactivating rules for $app..."
for rule_file in "$OPENSNITCH_CONFIGS_DIR/$app/"*.json; do
rule_name=$(basename "$rule_file")
if [ -L "$OPENSNITCH_RULES_DIR/$rule_name" ]; then
sudo rm "$OPENSNITCH_RULES_DIR/$rule_name"
status " Removed $rule_name"
fi
done
done
status "Restarting OpenSnitch daemon to apply changes..."
sudo systemctl restart opensnitchd.service
status "Rules deactivated."
;;

"Show status")
echo ""
gum style --border normal --padding "0 1" --border-foreground 212 "OpenSnitch Firewall Rules Status"
echo ""
for app in "${AVAILABLE_APPS[@]}"; do
rule_count=$(ls "$OPENSNITCH_CONFIGS_DIR/$app/"*.json 2>/dev/null | wc -l)
first_rule=$(ls "$OPENSNITCH_CONFIGS_DIR/$app/"*.json 2>/dev/null | head -1)
if [ -n "$first_rule" ]; then
rule_name=$(basename "$first_rule")
if [ -L "$OPENSNITCH_RULES_DIR/$rule_name" ]; then
gum style --foreground 2 " ✓ $app ($rule_count rules, active)"
else
gum style --foreground 1 " ✗ $app ($rule_count rules, inactive)"
fi
fi
done
echo ""

# Show any unmanaged rules in /etc/opensnitchd/rules/
UNMANAGED=0
for rule_file in "$OPENSNITCH_RULES_DIR"/*.json; do
[ -f "$rule_file" ] || continue
if [ ! -L "$rule_file" ]; then
if [ $UNMANAGED -eq 0 ]; then
echo ""
gum style --foreground 3 " Unmanaged rules (not from manjikaze):"
fi
UNMANAGED=$((UNMANAGED + 1))
gum style --foreground 3 " - $(basename "$rule_file")"
fi
done
if [ $UNMANAGED -gt 0 ]; then
echo ""
gum style --foreground 3 " $UNMANAGED unmanaged rule(s) found."
gum style --faint " These were created manually via the OpenSnitch UI."
fi
;;
esac
36 changes: 36 additions & 0 deletions configs/opensnitch/aws/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# AWS

Rules for AWS tooling: `aws-vault`, AWS CLI v2, and SAM CLI.

## Rules

### 000 – Allow AWS services

Allow `aws-vault` and the AWS CLI to connect to AWS:

| Binary | Path | Purpose |
|---|---|---|
| `aws-vault` | `/usr/bin/aws-vault` | STS credential management, MFA |
| `aws` (CLI v2) | `/usr/local/aws-cli/*/aws` | All AWS service interactions |

Allowed domains:

| Domain | Purpose |
|---|---|
| `*.amazonaws.com` | All AWS service endpoints (S3, STS, CloudFormation, etc.) |
| `*.amazoncognito.com` | Cognito authentication |
| `*.amazonwebservices.com` | AWS console, documentation lookups |

The CLI path uses a version-independent regex to survive CLI updates.

### 001 – Allow CLI downloads

Allow `curl` to download AWS CLI updates from `awscli.amazonaws.com`.
Separate rule because `curl` is a shared binary — only this specific
domain is matched.

## SAM CLI

SAM CLI runs via Python and makes CloudFormation calls to `*.amazonaws.com`.
These are covered by the Python LAN rule for local invocations, and via
popup for remote deployments (which use the system Python or AWS CLI).
33 changes: 33 additions & 0 deletions configs/opensnitch/aws/aws-000-allow-amazonaws.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"created": "2026-02-24T13:13:00+01:00",
"updated": "2026-02-24T13:13:00+01:00",
"name": "aws-000-allow-amazonaws",
"description": "Allow AWS tools to connect to AWS services. aws-vault handles STS credential management and MFA. The AWS CLI v2 (installed at /usr/local/aws-cli/) manages all AWS service interactions. Both only need *.amazonaws.com. Uses version-independent path regex for the CLI.",
"action": "allow",
"duration": "always",
"operator": {
"operand": "list",
"data": "",
"type": "list",
"list": [
{
"operand": "process.path",
"data": "^(/usr/bin/aws-vault|/usr/local/aws-cli/.*/aws)$",
"type": "regexp",
"list": null,
"sensitive": false
},
{
"operand": "dest.host",
"data": "^(|.*\\.)(amazonaws\\.com|amazoncognito\\.com|amazonwebservices\\.com)$",
"type": "regexp",
"list": null,
"sensitive": false
}
],
"sensitive": false
},
"enabled": true,
"precedence": false,
"nolog": false
}
33 changes: 33 additions & 0 deletions configs/opensnitch/aws/aws-001-allow-cli-downloads.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"created": "2026-02-24T13:13:00+01:00",
"updated": "2026-02-24T13:13:00+01:00",
"name": "aws-001-allow-cli-downloads",
"description": "Allow curl to download AWS CLI updates from awscli.amazonaws.com. This is used by the manjikaze update script to install/update the AWS CLI v2.",
"action": "allow",
"duration": "always",
"operator": {
"operand": "list",
"data": "",
"type": "list",
"list": [
{
"operand": "process.path",
"data": "/usr/bin/curl",
"type": "simple",
"list": null,
"sensitive": false
},
{
"operand": "dest.host",
"data": "awscli.amazonaws.com",
"type": "simple",
"list": null,
"sensitive": false
}
],
"sensitive": false
},
"enabled": true,
"precedence": false,
"nolog": false
}
28 changes: 28 additions & 0 deletions configs/opensnitch/chrome/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Google Chrome

Chrome is installed at `/opt/google/chrome/chrome`.

Similar to Firefox, a web browser needs unrestricted network access since it connects to arbitrary destinations on the internet.

## Why no telemetry block rule?

Unlike Firefox, which uses a separate `pingsender` binary for telemetry, Chrome bundles its crash reporter (`crashpad`) and metrics (UMA) into the main `/opt/google/chrome/chrome` process.

Furthermore, Chrome sends its telemetry to `clients2.google.com` and `clients4.google.com`. We cannot block these domains in OpenSnitch without breaking core functionality, because Google also uses these exact same domains for:
- Chrome Sync (bookmarks, passwords)
- Extensions updates
- Google Safe Browsing checking

Blocking them at the network level will break Chrome.

## Best practices

Instead of an OpenSnitch deny rule, you should disable telemetry inside Chrome:
1. Go to `chrome://settings/syncSetup` and disable "Help improve Chrome's features and performance"
2. Install **uBlock Origin** to block generic web trackers and telemetry from third-party websites.

## Rules

### 000 – Allow all

Allow all connections from `/opt/google/chrome/chrome`.
18 changes: 18 additions & 0 deletions configs/opensnitch/chrome/chrome-000-allow-all.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"created": "2026-02-24T13:35:00+01:00",
"updated": "2026-02-24T13:35:00+01:00",
"name": "chrome-000-allow-all",
"description": "Allow all connections from Google Chrome. As a web browser, it requires unrestricted network access. Telemetry blocking should be done via Chrome's settings and uBlock Origin, as Google mixes telemetry endpoints with essential services like Safe Browsing and Chrome Sync.",
"action": "allow",
"duration": "always",
"operator": {
"operand": "process.path",
"data": "/opt/google/chrome/chrome",
"type": "simple",
"list": null,
"sensitive": false
},
"enabled": true,
"precedence": false,
"nolog": false
}
19 changes: 19 additions & 0 deletions configs/opensnitch/dbeaver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# DBeaver

DBeaver uses the **system JRE** (`/usr/lib/jvm/java-*-openjdk/bin/java`),
not a bundled one. This means rules on this process path apply to all Java
apps using the system JRE — so we keep them restrictive.

## Rules

### 000 – Allow updates

Allow `dbeaver.io` for update checks and plugin marketplace downloads.
Uses a version-independent JRE path regex to survive Java upgrades.

## Database connections

Database connections are **not** covered by permanent rules. They vary per
project and per client, so it's better to approve them via the OpenSnitch
popup on a per-connection basis. Use "Allow for 12h" or "Allow for session"
for the duration of your work.
33 changes: 33 additions & 0 deletions configs/opensnitch/dbeaver/dbeaver-000-allow-updates.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"created": "2026-02-24T12:15:00+01:00",
"updated": "2026-02-24T12:15:00+01:00",
"name": "dbeaver-000-allow-updates",
"description": "Allow the system JRE to connect to dbeaver.io for update checks and plugin marketplace. Note: DBeaver uses the system JRE, so this rule technically applies to any Java app. The domain restriction ensures only dbeaver.io traffic is matched. Database connections should be handled via temporary popup rules since they vary per project.",
"action": "allow",
"duration": "always",
"operator": {
"operand": "list",
"data": "",
"type": "list",
"list": [
{
"operand": "process.path",
"data": "^/usr/lib/jvm/java-[0-9]+-openjdk/bin/java$",
"type": "regexp",
"list": null,
"sensitive": false
},
{
"operand": "dest.host",
"data": "^(|.*\\.)(dbeaver\\.io)$",
"type": "regexp",
"list": null,
"sensitive": false
}
],
"sensitive": false
},
"enabled": true,
"precedence": false,
"nolog": false
}
Loading