Compare commits

...

3 Commits

Author SHA1 Message Date
m0duspwnens
d91dd0dd3c watch some values 2024-04-29 17:14:00 -04:00
m0duspwnens
a0388fd568 engines config for valueWatch 2024-04-29 14:02:10 -04:00
m0duspwnens
05244cfd75 watch files change engine 2024-04-24 13:19:39 -04:00
3 changed files with 346 additions and 0 deletions

View 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()

View 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)

View File

@@ -4,6 +4,50 @@ engines_dirs:
engines: engines:
- checkmine: - checkmine:
interval: 60 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: - pillarWatch:
fpa: fpa:
# these files will be checked in reversed order to replicate the same hierarchy as the pillar top file # 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.run:
cmd: /usr/sbin/so-rule-update cmd: /usr/sbin/so-rule-update
interval: 10 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