diff --git a/salt/common/tools/sbin/so-firewall b/salt/common/tools/sbin/so-firewall
index 32fa84f3c..9275a209e 100755
--- a/salt/common/tools/sbin/so-firewall
+++ b/salt/common/tools/sbin/so-firewall
@@ -1,104 +1,148 @@
-#!/usr/bin/bash
+#!/usr/bin/env python3
-# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
-# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
-# https://securityonion.net/license; you may not use this file except in compliance with the
-# Elastic License 2.0.
+# Copyright 2014-2023 Security Onion Solutions, LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
-. /usr/sbin/so-common
+import os
+import re
+import subprocess
+import sys
+import time
+import yaml
-if [[ $# -lt 1 ]]; then
- echo "Usage: $0 --role= --ip= --apply="
- echo ""
- echo " Example: so-firewall --role=sensor --ip=192.168.254.100 --apply=true"
- echo ""
- exit 1
-fi
+lockFile = "/tmp/so-firewall.lock"
+hostgroupsFilename = "/opt/so/saltstack/local/pillar/firewall/soc_firewall.sls"
+defaultsFilename = "/opt/so/saltstack/default/salt/firewall/defaults.yaml"
-for i in "$@"; do
- case $i in
- -r=*|--role=*)
- ROLE="${i#*=}"
- shift
- ;;
- -i=*|--ip=*)
- IP="${i#*=}"
- shift
- ;;
- -a=*|--apply*)
- APPLY="${i#*=}"
- shift
- ;;
- -*|--*)
- echo "Unknown option $i"
- exit 1
- ;;
- *)
- ;;
- esac
-done
+def showUsage(options, args):
+ print('Usage: {} [OPTIONS] [ARGS...]'.format(sys.argv[0]))
+ print(' Options:')
+ print(' --apply - After updating the firewall configuration files, apply the new firewall state')
+ print('')
+ print(' General commands:')
+ print(' help - Prints this usage information.')
+ print(' apply - Apply the firewall state.')
+ print('')
+ print(' Host commands:')
+ print(' includehost - Includes the given IP in the given group. Args: ')
+ print(' addhostgroup - Adds a new, custom host group. Args: ')
+ print('')
+ print(' Where:')
+ print(' GROUP_NAME - The name of an alias group (Ex: analyst)')
+ print(' IP - Either a single IP address (Ex: 8.8.8.8) or a CIDR block (Ex: 10.23.0.0/16).')
+ sys.exit(1)
-ROLE=${ROLE,,}
-APPLY=${APPLY,,}
+def checkApplyOption(options):
+ if "--apply" in options:
+ return apply(None, None)
-function rolecall() {
- THEROLE=$1
- THEROLES="analyst analyst_workstations beats_endpoint beats_endpoint_ssl elastic_agent_endpoint elasticsearch_rest endgame eval fleet heavynodes idh manager managersearch receivers searchnodes sensors standalone strelka_frontend syslog"
+def loadYaml(filename):
+ file = open(filename, "r")
+ content = file.read()
+ return yaml.safe_load(content)
- for AROLE in $THEROLES; do
- if [ "$AROLE" = "$THEROLE" ]; then
- return 0
- fi
- done
- return 1
-}
+def writeYaml(filename, content):
+ file = open(filename, "w")
+ return yaml.dump(content, file)
-# Make sure the required options are specified
-if [ -z "$ROLE" ]; then
- echo "Please specify a role with --role="
- exit 1
-fi
-if [ -z "$IP" ]; then
- echo "Please specify an IP address with --ip="
- exit 1
-fi
+def addIp(name, ip):
+ content = loadYaml(hostgroupsFilename)
+ defaults = loadYaml(defaultsFilename)
+ allowedHostgroups = defaults['firewall']['hostgroups']
+ unallowedHostgroups = ['anywhere', 'dockernet', 'localhost', 'self']
+ for hg in unallowedHostgroups:
+ allowedHostgroups.pop(hg)
+ if not content:
+ content = {'firewall': {'hostgroups': {name: []}}}
+ if name in allowedHostgroups:
+ if name not in content['firewall']['hostgroups']:
+ hostgroup = content['firewall']['hostgroups'].update({name: [ip]})
+ else:
+ hostgroup = content['firewall']['hostgroups'][name]
+ else:
+ print('Host group not defined in salt/firewall/defaults.yaml or hostgroup name is unallowed.', file=sys.stderr)
+ return 4
+ ips = hostgroup
+ if ips is None:
+ ips = []
+ hostgroup = ips
+ if ip not in ips:
+ ips.append(ip)
+ else:
+ print('Already exists', file=sys.stderr)
+ return 3
+ writeYaml(hostgroupsFilename, content)
+ return 0
-# Are we dealing with a role that this script supports?
-if rolecall "$ROLE"; then
- echo "$ROLE is a supported role"
-else
- echo "This is not a supported role"
- exit 1
-fi
+def includehost(options, args):
+ if len(args) != 2:
+ print('Missing host group name or ip argument', file=sys.stderr)
+ showUsage(options, args)
+ result = addIp(args[0], args[1])
+ code = result
+ if code == 0:
+ code = checkApplyOption(options)
+ return code
- # Are we dealing with an IP?
-if verify_ip4 "$IP"; then
- echo "$IP is a valid IP or CIDR"
-else
- echo "$IP is not a valid IP or CIDR"
- exit 1
-fi
+def apply(options, args):
+ proc = subprocess.run(['salt-call', 'state.apply', 'firewall', 'queue=True'])
+ return proc.returncode
-local_salt_dir=/opt/so/saltstack/local/salt/firewall
+def main():
+ options = []
+ args = sys.argv[1:]
+ for option in args:
+ if option.startswith("--"):
+ options.append(option)
+ args.remove(option)
-# Let's see if the file exists and if it does, let's see if the IP exists.
-if [ -f "$local_salt_dir/hostgroups/$ROLE" ]; then
- if grep -q $IP "$local_salt_dir/hostgroups/$ROLE"; then
- echo "Host already exists"
- exit 0
- fi
-fi
+ if len(args) == 0:
+ showUsage(options, None)
-# If you have reached this part of your quest then let's add the IP
-echo "Adding $IP to the $ROLE role"
-echo "$IP" >> $local_salt_dir/hostgroups/$ROLE
+ commands = {
+ "help": showUsage,
+ "includehost": includehost,
+ "apply": apply
+ }
-# Check to see if we are applying this right away.
-if [ "$APPLY" = "true" ]; then
- echo "Applying the firewall rules"
- salt-call state.apply firewall queue=True
- echo "Firewall rules have been applied... Review logs further if there were errors."
- echo ""
-else
- echo "Firewall rules will be applied next salt run"
-fi
+ code=1
+
+ try:
+ lockAttempts = 0
+ maxAttempts = 30
+ while lockAttempts < maxAttempts:
+ lockAttempts = lockAttempts + 1
+ try:
+ f = open(lockFile, "x")
+ f.close()
+ break
+ except:
+ time.sleep(2)
+
+ if lockAttempts == maxAttempts:
+ print("Lock file (" + lockFile + ") could not be created; proceeding without lock.")
+
+ cmd = commands.get(args[0], showUsage)
+ code = cmd(options, args[1:])
+ finally:
+ try:
+ os.remove(lockFile)
+ except:
+ print("Lock file (" + lockFile + ") already removed")
+
+ sys.exit(code)
+
+if __name__ == "__main__":
+ main()
diff --git a/salt/firewall/defaults.yaml b/salt/firewall/defaults.yaml
index 0ddf5a7bb..0e68add72 100644
--- a/salt/firewall/defaults.yaml
+++ b/salt/firewall/defaults.yaml
@@ -13,9 +13,11 @@ firewall:
fleet: []
heavynodes: []
idh: []
+ import: []
localhost:
- 127.0.0.1
manager: []
+ managersearch: []
receivers: []
searchnodes: []
securityonion_desktops: []
diff --git a/salt/firewall/soc_firewall.yaml b/salt/firewall/soc_firewall.yaml
index c54d3011e..5b76c581e 100644
--- a/salt/firewall/soc_firewall.yaml
+++ b/salt/firewall/soc_firewall.yaml
@@ -33,8 +33,10 @@ firewall:
fleet: *hostgroupsettings
heavynodes: *hostgroupsettings
idh: *hostgroupsettings
+ import: *hostgroupsettings
localhost: *ROhostgroupsettingsadv
manager: *hostgroupsettings
+ managersearch: *hostgroupsettings
receivers: *hostgroupsettings
searchnodes: *hostgroupsettings
securityonion_desktops: *hostgroupsettings
diff --git a/setup/so-functions b/setup/so-functions
index 9e45fabb8..942ca4671 100755
--- a/setup/so-functions
+++ b/setup/so-functions
@@ -2291,18 +2291,18 @@ set_initial_firewall_policy() {
case "$install_type" in
'EVAL' | 'MANAGER' | 'MANAGERSEARCH' | 'STANDALONE' | 'IMPORT')
- $default_salt_dir/salt/common/tools/sbin/so-firewall --role=$install_type --ip=$MAINIP --apply=true
+ $default_salt_dir/salt/common/tools/sbin/so-firewall includehost $minion_type $MAINIP --apply
;;
esac
}
set_initial_firewall_access() {
if [[ ! -z "$ALLOW_CIDR" ]]; then
- $default_salt_dir/salt/common/tools/sbin/so-firewall --role=analyst --ip=$ALLOW_CIDR --apply=true
+ $default_salt_dir/salt/common/tools/sbin/so-firewall includehost analyst $ALLOW_CIDR --apply
fi
if [[ ! -z "$MINION_CIDR" ]]; then
- $default_salt_dir/salt/common/tools/sbin/so-firewall --role=sensors --ip=$MINION_CIDR --apply=false
- $default_salt_dir/salt/common/tools/sbin/so-firewall --role=searchnodes --ip=$MINION_CIDR --apply=true
+ $default_salt_dir/salt/common/tools/sbin/so-firewall includehost sensors $MINION_CIDR
+ $default_salt_dir/salt/common/tools/sbin/so-firewall includehost searchnodes $MINION_CIDR --apply
fi
}