mirror of
https://github.com/Security-Onion-Solutions/securityonion.git
synced 2025-12-06 09:12:45 +01:00
Compare commits
3 Commits
TOoSmOotH-
...
kaffytaffy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d91dd0dd3c | ||
|
|
a0388fd568 | ||
|
|
05244cfd75 |
120
salt/salt/engines/master/valWatch.py
Normal file
120
salt/salt/engines/master/valWatch.py
Normal file
@@ -0,0 +1,120 @@
|
||||
# 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.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
import re
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# will need this in future versions of this engine
|
||||
import salt.client
|
||||
local = salt.client.LocalClient()
|
||||
|
||||
def start(fpa, interval=10):
|
||||
|
||||
def getValue(content, key):
|
||||
pieces = key.split(".", 1)
|
||||
if len(pieces) > 1:
|
||||
getValue(content[pieces[0]], pieces[1])
|
||||
else:
|
||||
#log.info("ck: %s" % content[key])
|
||||
return content[key]
|
||||
#content.pop(key, None)
|
||||
|
||||
log.info("valWatch engine: ##### checking watched values for changes #####")
|
||||
|
||||
# try to open the file that stores the previous runs data
|
||||
# if the file doesn't exist, create a blank one
|
||||
try:
|
||||
# maybe change this location
|
||||
dataFile = open("/opt/so/state/valWatch.txt", "r+")
|
||||
except FileNotFoundError:
|
||||
log.warn("valWatch engine: No previous valWatch data saved")
|
||||
dataFile = open("/opt/so/state/valWatch.txt", "w+")
|
||||
|
||||
df = dataFile.read()
|
||||
for i in fpa:
|
||||
log.info("valWatch engine: i: %s" % i)
|
||||
log.trace("valWatch engine: map: %s" % i['map'])
|
||||
log.trace("valWatch engine: value: %s" % i['value'])
|
||||
log.trace("valWatch engine: targets: %s" % i['targets'])
|
||||
log.trace("valWatch engine: actions: %s" % i['actions'])
|
||||
mapFile = i['map']
|
||||
value = i['value']
|
||||
targets = i['targets']
|
||||
# target type
|
||||
ttype = i['ttype']
|
||||
actions = i['actions']
|
||||
# these are the keys that we are going to look for as we traverse the pillarFiles
|
||||
patterns = value.split(".")
|
||||
mainDict = patterns.pop(0)
|
||||
# patterns = value.split(".")
|
||||
for target in targets:
|
||||
# tell targets to render mapfile and return value split
|
||||
mapRender = local.cmd(target, fun='jinja.load_map', arg=[mapFile, mainDict], tgt_type=ttype)
|
||||
|
||||
currentValue = ''
|
||||
previousValue = ''
|
||||
# this var is used to track how many times the pattern has been found in the pillar file so that we can access the proper index later
|
||||
patternFound = 0
|
||||
#with open(pillarFile, "r") as file:
|
||||
# log.debug("pillarWatch engine: checking file: %s" % pillarFile)
|
||||
mapRenderKeys = list(mapRender.keys())
|
||||
if len(mapRenderKeys) > 0:
|
||||
log.info(mapRenderKeys)
|
||||
log.info("valWatch engine: mapRender: %s" % mapRender)
|
||||
minion = mapRenderKeys[0]
|
||||
currentValue = getValue(mapRender[minion],value.split('.', 1)[1])
|
||||
log.info("valWatch engine: currentValue: %s: %s: %s" % (minion, value, currentValue))
|
||||
for l in df.splitlines():
|
||||
if value in l:
|
||||
previousPillarValue = str(l.split(":")[1].strip())
|
||||
log.info("valWatch engine: previousValue: %s: %s: %s" % (minion, value, previousValue))
|
||||
|
||||
'''
|
||||
for key in mapRender[minion]:
|
||||
log.info("pillarWatch engine: inspecting key: %s in mainDict: %s" % (key, mainDict))
|
||||
log.info("pillarWatch engine: looking for: %s" % patterns[patternFound])
|
||||
# since we are looping line by line through a pillar file, the next line will check if each line matches the progression of keys through the pillar
|
||||
# ex. if we are looking for the value of global.pipeline, then this will loop through the pillar file until 'global' is found, then it will look
|
||||
# for pipeline. once pipeline is found, it will record the value
|
||||
#if re.search(patterns[patternFound], key):
|
||||
if patterns[patternFound] == key:
|
||||
# strip the newline because it makes the logs u-g-l-y
|
||||
log.info("pillarWatch engine: found: %s" % key)
|
||||
patternFound += 1
|
||||
# we have found the final key in the pillar that we are looking for, get the previous value then the current value
|
||||
if patternFound == len(patterns):
|
||||
# at this point, df is equal to the contents of the pillarWatch file that is used to tract the previous values of the pillars
|
||||
previousPillarValue = 'PREVIOUSPILLARVALUENOTSAVEDINDATAFILE'
|
||||
# check the contents of the dataFile that stores the previousPillarValue(s).
|
||||
# find if the pillar we are checking for changes has previously been saved. if so, grab it's prior value
|
||||
for l in df.splitlines():
|
||||
if value in l:
|
||||
previousPillarValue = str(l.split(":")[1].strip())
|
||||
currentPillarValue = mapRender[minion][key]
|
||||
log.info("pillarWatch engine: %s currentPillarValue: %s" % (value, currentPillarValue))
|
||||
log.info("pillarWatch engine: %s previousPillarValue: %s" % (value, previousPillarValue))
|
||||
# if the pillar we are checking for changes has been defined in the dataFile,
|
||||
# replace the previousPillarValue with the currentPillarValue. if it isn't in there, append it.
|
||||
if value in df:
|
||||
df = re.sub(r"\b{}\b.*".format(pillar), pillar + ': ' + currentPillarValue, df)
|
||||
else:
|
||||
df += value + ': ' + currentPillarValue + '\n'
|
||||
log.info("pillarWatch engine: df: %s" % df)
|
||||
# we have found the pillar so we dont need to loop through the file anymore
|
||||
break
|
||||
# if key and value was found in the first file, then we don't want to look in
|
||||
# any more files since we use the first file as the source of truth.
|
||||
if patternFound == len(patterns):
|
||||
break
|
||||
'''
|
||||
|
||||
|
||||
dataFile.seek(0)
|
||||
dataFile.write(df)
|
||||
dataFile.truncate()
|
||||
dataFile.close()
|
||||
141
salt/salt/engines/master/valueWatch.py
Normal file
141
salt/salt/engines/master/valueWatch.py
Normal file
@@ -0,0 +1,141 @@
|
||||
# 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.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
import re
|
||||
from pathlib import Path
|
||||
import os
|
||||
import glob
|
||||
import json
|
||||
from time import sleep
|
||||
|
||||
import sys
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
import salt.client
|
||||
local = salt.client.LocalClient()
|
||||
|
||||
def start(watched, interval=10):
|
||||
# this 20 second sleep allows enough time for the minion to reconnect during testing of the script when the salt-master is restarted
|
||||
sleep(20)
|
||||
log.info("valueWatch engine: started")
|
||||
# this dict will be used to store the files that we are watching and their modification times for the current iteration though a loop
|
||||
fileModTimesCurrent = {}
|
||||
# same as fileModTimesCurrent, but stores the previous values through the loop.
|
||||
# the combination of these two variables is used to determine if a files has changed.
|
||||
fileModTimesPrevious = {}
|
||||
#
|
||||
currentValues = {}
|
||||
|
||||
def getValue(content, key):
|
||||
pieces = key.split(".", 1)
|
||||
if len(pieces) > 1:
|
||||
getValue(content[pieces[0]], pieces[1])
|
||||
else:
|
||||
#log.info("ck: %s" % content[key])
|
||||
return content[key]
|
||||
#content.pop(key, None)
|
||||
|
||||
def updateModTimesCurrent(files):
|
||||
# this dict will be used to store the files that we are watching and their modification times for the current iteration though a loop
|
||||
fileModTimesCurrent.clear()
|
||||
for f in files:
|
||||
#log.warn(f)
|
||||
fileName = Path(f).name
|
||||
filePath = Path(f).parent
|
||||
if '*' in fileName:
|
||||
#log.info(fileName)
|
||||
#log.info(filePath)
|
||||
slsFiles = glob.glob(f)
|
||||
for slsFile in slsFiles:
|
||||
#log.info(slsFile)
|
||||
fileModTimesCurrent.update({slsFile: os.path.getmtime(slsFile)})
|
||||
else:
|
||||
fileModTimesCurrent.update({f: os.path.getmtime(f)})
|
||||
|
||||
def compareFileModTimes():
|
||||
ret = []
|
||||
for f in fileModTimesCurrent:
|
||||
log.info(f)
|
||||
if f in fileModTimesPrevious:
|
||||
log.info("valueWatch engine: fileModTimesCurrent: %s" % fileModTimesCurrent[f])
|
||||
log.info("valueWatch engine: fileModTimesPrevious: %s" % fileModTimesPrevious[f])
|
||||
if fileModTimesCurrent[f] != fileModTimesPrevious[f]:
|
||||
log.error("valueWatch engine: fileModTimesCurrent[f] != fileModTimesPrevious[f]")
|
||||
log.error("valueWatch engine: " + str(fileModTimesCurrent[f]) + " != " + str(fileModTimesPrevious[f]))
|
||||
ret.append(f)
|
||||
return ret
|
||||
|
||||
# this will set the current value of 'value' from engines.conf and save it to the currentValues dict
|
||||
def updateCurrentValues():
|
||||
for target in targets:
|
||||
log.info("valueWatch engine: refreshing pillars on %s" % target)
|
||||
refreshPillar = local.cmd(target, fun='saltutil.refresh_pillar', tgt_type=ttype)
|
||||
log.info("valueWatch engine: pillar refresh results: %s" % refreshPillar)
|
||||
# check if the result was True for the pillar refresh
|
||||
# will need to add a recheck incase the minion was just temorarily unavailable
|
||||
try:
|
||||
if next(iter(refreshPillar.values())):
|
||||
sleep(5)
|
||||
# render the map file for the variable passed in from value.
|
||||
mapRender = local.cmd(target, fun='jinja.load_map', arg=[mapFile, mainDict], tgt_type=ttype)
|
||||
log.info("mR: %s" % mapRender)
|
||||
currentValue = ''
|
||||
previousValue = ''
|
||||
mapRenderKeys = list(mapRender.keys())
|
||||
if len(mapRenderKeys) > 0:
|
||||
log.info(mapRenderKeys)
|
||||
log.info("valueWatch engine: mapRender: %s" % mapRender)
|
||||
minion = mapRenderKeys[0]
|
||||
# if not isinstance(mapRender[minion], bool):
|
||||
currentValue = getValue(mapRender[minion],value.split('.', 1)[1])
|
||||
log.info("valueWatch engine: currentValue: %s: %s: %s" % (minion, value, currentValue))
|
||||
currentValues.update({value: {minion: currentValue}})
|
||||
# we have rendered the value so we don't need to have any more target render it
|
||||
break
|
||||
except StopIteration:
|
||||
log.info("valueWatch engine: target %s did not respond or does not exist" % target)
|
||||
|
||||
log.info("valueWatch engine: currentValues: %s" % currentValues)
|
||||
|
||||
|
||||
# run the main loop
|
||||
while True:
|
||||
log.info("valueWatch engine: checking watched files for changes")
|
||||
for v in watched:
|
||||
value = v['value']
|
||||
files = v['files']
|
||||
mapFile = v['map']
|
||||
targets = v['targets']
|
||||
ttype = v['ttype']
|
||||
actions = v['actions']
|
||||
|
||||
patterns = value.split(".")
|
||||
mainDict = patterns.pop(0)
|
||||
|
||||
log.info("valueWatch engine: value: %s" % value)
|
||||
# the call to this function will update fileModtimesCurrent
|
||||
updateModTimesCurrent(files)
|
||||
#log.trace("valueWatch engine: fileModTimesCurrent: %s" % fileModTimesCurrent)
|
||||
#log.trace("valueWatch engine: fileModTimesPrevious: %s" % fileModTimesPrevious)
|
||||
|
||||
# compare with the previous checks file modification times
|
||||
modFilesDiff = compareFileModTimes()
|
||||
# if there were changes in the pillar files, then we need to have the minion render the map file to determine if the value changed
|
||||
if modFilesDiff:
|
||||
log.info("valueWatch engine: change in files detetected, updating currentValues: %s" % modFilesDiff)
|
||||
updateCurrentValues()
|
||||
elif value not in currentValues:
|
||||
log.info("valueWatch engine: %s not in currentValues, updating currentValues." % value)
|
||||
updateCurrentValues()
|
||||
else:
|
||||
log.info("valueWatch engine: no files changed, no update for currentValues")
|
||||
|
||||
# save this iteration's values to previous so we can compare next run
|
||||
fileModTimesPrevious.update(fileModTimesCurrent)
|
||||
sleep(interval)
|
||||
@@ -4,6 +4,50 @@ engines_dirs:
|
||||
engines:
|
||||
- checkmine:
|
||||
interval: 60
|
||||
- valueWatch:
|
||||
watched:
|
||||
- value: GLOBALMERGED.pipeline
|
||||
files:
|
||||
- /opt/so/saltstack/local/pillar/global/soc_global.sls
|
||||
- /opt/so/saltstack/local/pillar/global/adv_global.sls
|
||||
map: global/map.jinja
|
||||
targets:
|
||||
- G@role:so-notanodetype
|
||||
- G@role:so-manager
|
||||
- G@role:so-searchnode
|
||||
ttype: compound
|
||||
actions:
|
||||
from:
|
||||
'*':
|
||||
to:
|
||||
KAFKA:
|
||||
- cmd.run:
|
||||
cmd: /usr/sbin/so-yaml.py replace /opt/so/saltstack/local/pillar/kafka/soc_kafka.sls kafka.enabled True
|
||||
KAFKA:
|
||||
to:
|
||||
'*':
|
||||
- cmd.run:
|
||||
cmd: /usr/sbin/so-yaml.py replace /opt/so/saltstack/local/pillar/kafka/soc_kafka.sls kafka.enabled False
|
||||
|
||||
# - value: FIREWALL_MERGED
|
||||
# files:
|
||||
# - /opt/so/saltstack/local/pillar/firewall/soc_firewall.sls
|
||||
# - /opt/so/saltstack/local/pillar/firewall/adv_firewall.sls
|
||||
# - /opt/so/saltstack/local/pillar/minions/*.sls
|
||||
# map: firewall/map.jinja
|
||||
# targets:
|
||||
# - so-*
|
||||
# ttype: compound
|
||||
# actions:
|
||||
# from:
|
||||
# '*':
|
||||
# to:
|
||||
# '*':
|
||||
# - cmd.run:
|
||||
# cmd: date
|
||||
interval: 10
|
||||
|
||||
|
||||
- pillarWatch:
|
||||
fpa:
|
||||
# these files will be checked in reversed order to replicate the same hierarchy as the pillar top file
|
||||
@@ -43,3 +87,44 @@ engines:
|
||||
- cmd.run:
|
||||
cmd: /usr/sbin/so-rule-update
|
||||
interval: 10
|
||||
- valWatch:
|
||||
fpa:
|
||||
- value: GLOBALMERGED.pipeline
|
||||
map: global/map.jinja
|
||||
targets:
|
||||
- so-manager
|
||||
- so-managersearch
|
||||
- so-standalone
|
||||
|
||||
actions:
|
||||
from:
|
||||
'*':
|
||||
to:
|
||||
KAFKA:
|
||||
- cmd.run:
|
||||
cmd: /usr/sbin/so-yaml.py replace /opt/so/saltstack/local/pillar/kafka/soc_kafka.sls kafka.enabled True
|
||||
# - cmd.run:
|
||||
# cmd: salt-call saltutil.kill_all_jobs
|
||||
# - cmd.run:
|
||||
# cmd: salt-call state.highstate &
|
||||
KAFKA:
|
||||
to:
|
||||
'*':
|
||||
- cmd.run:
|
||||
cmd: /usr/sbin/so-yaml.py replace /opt/so/saltstack/local/pillar/kafka/soc_kafka.sls kafka.enabled False
|
||||
# - cmd.run:
|
||||
# cmd: salt-call saltutil.kill_all_jobs
|
||||
# - cmd.run:
|
||||
# cmd: salt-call state.highstate &
|
||||
# - files:
|
||||
# - /opt/so/saltstack/local/pillar/idstools/soc_idstools.sls
|
||||
# - /opt/so/saltstack/local/pillar/idstools/adv_idstools.sls
|
||||
# pillar: idstools.config.ruleset
|
||||
# actions:
|
||||
# from:
|
||||
# '*':
|
||||
# to:
|
||||
# '*':
|
||||
# - cmd.run:
|
||||
# cmd: /usr/sbin/so-rule-update
|
||||
interval: 10
|
||||
|
||||
Reference in New Issue
Block a user