diff --git a/src/opnsense/mvc/app/controllers/OPNsense/IDS/Api/SettingsController.php b/src/opnsense/mvc/app/controllers/OPNsense/IDS/Api/SettingsController.php
index fe42bdacd..e500dd15d 100644
--- a/src/opnsense/mvc/app/controllers/OPNsense/IDS/Api/SettingsController.php
+++ b/src/opnsense/mvc/app/controllers/OPNsense/IDS/Api/SettingsController.php
@@ -108,6 +108,12 @@ class SettingsController extends ApiControllerBase
$searchPhrase .= " classtype/".$searchTag.' ';
}
+ // add filter for action
+ if ($this->request->getPost("action", "string", '') != "") {
+ $searchTag = $filter->sanitize($this->request->getPost('action'), "query");
+ $searchPhrase .= " installed_action/".$searchTag.' ';
+ }
+
// request list of installed rules
$backend = new Backend();
$response = $backend->configdpRun("ids query rules", array($itemsPerPage,
diff --git a/src/opnsense/mvc/app/views/OPNsense/IDS/index.volt b/src/opnsense/mvc/app/views/OPNsense/IDS/index.volt
index c4c1814da..8a8d55dc9 100644
--- a/src/opnsense/mvc/app/views/OPNsense/IDS/index.volt
+++ b/src/opnsense/mvc/app/views/OPNsense/IDS/index.volt
@@ -59,6 +59,12 @@ POSSIBILITY OF SUCH DAMAGE.
}
});
}
+ /**
+ * link on change event for alert "action" selectionbox
+ */
+ $('#ruleaction').on('change', function(){
+ $('#grid-installedrules').bootgrid('reload');
+ });
/**
* update list of available alert logs
@@ -84,13 +90,17 @@ POSSIBILITY OF SUCH DAMAGE.
}
/**
- * Add classtype to rule filter
+ * Add classtype / action to rule filter
*/
function addRuleFilters(request) {
var selected =$('#ruleclass').find("option:selected").val();
if ( selected != "") {
request['classtype'] = selected;
}
+ var selected =$('#ruleaction').find("option:selected").val();
+ if ( selected != "") {
+ request['action'] = selected;
+ }
return request;
}
@@ -557,8 +567,15 @@ POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/opnsense/scripts/suricata/installRules.py b/src/opnsense/scripts/suricata/installRules.py
index d2fe8bcfb..64087f8cb 100755
--- a/src/opnsense/scripts/suricata/installRules.py
+++ b/src/opnsense/scripts/suricata/installRules.py
@@ -30,28 +30,18 @@
Install suricata ruleset into opnsense.rules directory
"""
import os.path
-from ConfigParser import ConfigParser
import lib.rulecache
from lib import rule_source_directory
if __name__ == '__main__':
RuleCache = lib.rulecache.RuleCache()
- rule_config_fn = ('%s../rules.config' % rule_source_directory)
rule_target_dir = ('%s../opnsense.rules' % rule_source_directory)
rule_yaml_list = ('%s../installed_rules.yaml' % rule_source_directory)
+ rule_config_fn = ('%s../rules.config' % rule_source_directory)
# parse OPNsense rule config
- rule_updates = {}
- if os.path.exists(rule_config_fn):
- cnf = ConfigParser()
- cnf.read(rule_config_fn)
- for section in cnf.sections():
- if section[0:5] == 'rule_':
- sid = section[5:]
- rule_updates[sid] = {}
- for rule_item in cnf.items(section):
- rule_updates[sid][rule_item[0]] = rule_item[1]
+ rule_updates = RuleCache.list_local_changes()
# create target rule directory if not existing
if not os.path.exists(rule_target_dir):
diff --git a/src/opnsense/scripts/suricata/lib/rulecache.py b/src/opnsense/scripts/suricata/lib/rulecache.py
index bf8f6128e..0c42ac7af 100644
--- a/src/opnsense/scripts/suricata/lib/rulecache.py
+++ b/src/opnsense/scripts/suricata/lib/rulecache.py
@@ -34,6 +34,7 @@ import glob
import sqlite3
import shlex
import fcntl
+from ConfigParser import ConfigParser
from lib import rule_source_directory
@@ -55,6 +56,24 @@ class RuleCache(object):
return all_rule_files
+ @staticmethod
+ def list_local_changes():
+ # parse OPNsense rule config
+ rule_config_fn = ('%s../rules.config' % rule_source_directory)
+ rule_config_mtime = os.stat(rule_config_fn).st_mtime
+ rule_updates = {}
+ if os.path.exists(rule_config_fn):
+ cnf = ConfigParser()
+ cnf.read(rule_config_fn)
+ for section in cnf.sections():
+ if section[0:5] == 'rule_':
+ sid = section[5:]
+ rule_updates[sid] = {'mtime': rule_config_mtime}
+ for rule_item in cnf.items(section):
+ rule_updates[sid][rule_item[0]] = rule_item[1]
+ return rule_updates
+
+
def list_rules(self, filename):
""" generator function to list rule file content including metadata
:param filename:
@@ -151,11 +170,11 @@ class RuleCache(object):
db = sqlite3.connect(self.cachefile)
cur = db.cursor()
- cur.execute("CREATE TABLE stats (timestamp number, files number)")
- cur.execute("""CREATE TABLE rules (sid number, msg TEXT, classtype TEXT,
+ cur.execute("create table stats (timestamp number, files number)")
+ cur.execute("""create table rules (sid number, msg TEXT, classtype TEXT,
rev INTEGER, gid INTEGER, reference TEXT,
enabled BOOLEAN, action text, source TEXT)""")
-
+ cur.execute("create table local_rule_changes(sid number primary key, action text, last_mtime number)")
last_mtime = 0
all_rule_files = self.list_local()
for filename in all_rule_files:
@@ -175,6 +194,36 @@ class RuleCache(object):
# release lock
fcntl.flock(lock, fcntl.LOCK_UN)
+ def update_local_changes(self):
+ """ read local rules.config containing changes on installed ruleset and update to "local_rule_changes" table
+ """
+ if os.path.exists(self.cachefile):
+ db = sqlite3.connect(self.cachefile)
+ cur = db.cursor()
+ cur.execute('select max(last_mtime) from local_rule_changes')
+ last_mtime = cur.fetchall()[0][0]
+ rule_config_mtime = os.stat(('%s../rules.config' % rule_source_directory)).st_mtime
+ if rule_config_mtime != last_mtime:
+ # make sure only one process is updating this table
+ lock = open(self.cachefile + '.LCK', 'w')
+ try:
+ fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ except IOError:
+ # other process is already creating the cache, wait, let the other process do it's work and return.
+ fcntl.flock(lock, fcntl.LOCK_EX)
+ fcntl.flock(lock, fcntl.LOCK_UN)
+ return
+ # delete and insert local changes
+ cur.execute('delete from local_rule_changes')
+ local_changes = self.list_local_changes()
+ for sid in local_changes:
+ sql_params = (sid, local_changes[sid]['action'], local_changes[sid]['mtime'])
+ cur.execute('insert into local_rule_changes(sid, action, last_mtime) values (?,?,?)', sql_params)
+ db.commit()
+ # release lock
+ fcntl.flock(lock, fcntl.LOCK_UN)
+
+
def search(self, limit, offset, filter_txt, sort_by):
""" search installed rules
:param limit: limit number of rows
@@ -189,9 +238,15 @@ class RuleCache(object):
cur = db.cursor()
# construct query including filters
- sql = 'select * from rules '
+ sql = """select *
+ from (
+ select rules.*, case when rc.action is null then rules.action else rc.action end installed_action
+ from rules
+ left join local_rule_changes rc on rules.sid = rc.sid
+ ) a
+ """
sql_filters = {}
-
+ additional_search_fields = ['installed_action']
for filtertag in shlex.split(filter_txt):
fieldnames = filtertag.split('/')[0]
searchcontent = '/'.join(filtertag.split('/')[1:])
@@ -200,7 +255,7 @@ class RuleCache(object):
else:
sql += ' where ( '
for fieldname in map(lambda x: x.lower().strip(), fieldnames.split(',')):
- if fieldname in self._rule_fields:
+ if fieldname in self._rule_fields or fieldname in additional_search_fields:
if fieldname != fieldnames.split(',')[0].strip():
sql += ' or '
if searchcontent.find('*') == -1:
@@ -216,7 +271,7 @@ class RuleCache(object):
# apply sort order (if any)
sql_sort = []
for sortField in sort_by.split(','):
- if sortField.split(' ')[0] in self._rule_fields:
+ if sortField.split(' ')[0] in self._rule_fields or sortField.split(' ')[0] in additional_search_fields:
if sortField.split(' ')[-1].lower() == 'desc':
sql_sort.append('%s desc' % sortField.split()[0])
else:
diff --git a/src/opnsense/scripts/suricata/queryInstalledRules.py b/src/opnsense/scripts/suricata/queryInstalledRules.py
index d3267b5ef..7bacdb872 100755
--- a/src/opnsense/scripts/suricata/queryInstalledRules.py
+++ b/src/opnsense/scripts/suricata/queryInstalledRules.py
@@ -45,6 +45,9 @@ if __name__ == '__main__':
if rc.is_changed():
rc.create()
+ # import local changes (if changed)
+ rc.update_local_changes()
+
# load parameters, ignore validation here the search method only processes valid input
parameters = {'limit': '0', 'offset': '0', 'sort_by': '', 'filter': ''}
update_params(parameters)