3434import os
3535import sys
3636import yaml
37+ import collections
3738
3839import salt .utils
3940from salt .exceptions import CommandExecutionError
4041
4142log = logging .getLogger (__name__ )
4243
43- __version__ = 'v2017.8.3 '
44+ __version__ = 'v2017.9.0 '
4445__virtualname__ = 'nebula'
4546
4647
@@ -74,13 +75,34 @@ def queries(query_group,
7475 salt '*' nebula.queries hour verbose=True
7576 salt '*' nebula.queries hour pillar_key=sec_osqueries
7677 '''
78+ query_data = {}
7779 MAX_FILE_SIZE = 104857600
7880 if query_file is None :
7981 if salt .utils .is_windows ():
8082 query_file = 'salt://hubblestack_nebula/hubblestack_nebula_win_queries.yaml'
8183 else :
8284 query_file = 'salt://hubblestack_nebula/hubblestack_nebula_queries.yaml'
83- if not salt .utils .which ('osqueryi' ):
85+ if not isinstance (query_file , list ):
86+ query_file = [query_file ]
87+ for fh in query_file :
88+ if 'salt://' in fh :
89+ orig_fh = fh
90+ fh = __salt__ ['cp.cache_file' ](fh )
91+ if fh is None :
92+ log .error ('Could not find file {0}.' .format (orig_fh ))
93+ return None
94+ if os .path .isfile (fh ):
95+ with open (fh , 'r' ) as f :
96+ f_data = yaml .safe_load (f )
97+ if not isinstance (f_data , dict ):
98+ raise CommandExecutionError ('File data is not formed as a dict {0}'
99+ .format (f_data ))
100+ query_data = _dict_update (query_data ,
101+ f_data ,
102+ recursive_update = True ,
103+ merge_lists = True )
104+
105+ if 'osquerybinpath' not in __grains__ :
84106 if query_group == 'day' :
85107 log .warning ('osquery not installed on this host. Returning baseline data' )
86108 # Match the formatting of normal osquery results. Not super
@@ -140,19 +162,6 @@ def queries(query_group,
140162 else :
141163 return None
142164
143-
144- orig_filename = query_file
145- query_file = __salt__ ['cp.cache_file' ](query_file )
146- if query_file is None :
147- log .error ('Could not find file {0}.' .format (orig_filename ))
148- return None
149- with open (query_file , 'r' ) as fh :
150- query_data = yaml .safe_load (fh )
151-
152- if not isinstance (query_data , dict ):
153- raise CommandExecutionError ('Query data is not formed as a dict {0}'
154- .format (query_data ))
155-
156165 query_data = query_data .get (query_group , [])
157166
158167 if not query_data :
@@ -170,7 +179,7 @@ def queries(query_group,
170179 'result' : True ,
171180 }
172181
173- cmd = ['osqueryi' , '--read_max' , MAX_FILE_SIZE , '--json' , query_sql ]
182+ cmd = [__grains__ [ 'osquerybinpath' ] , '--read_max' , MAX_FILE_SIZE , '--json' , query_sql ]
174183 res = __salt__ ['cmd.run_all' ](cmd )
175184 if res ['retcode' ] == 0 :
176185 query_ret ['data' ] = json .loads (res ['stdout' ])
@@ -192,7 +201,7 @@ def queries(query_group,
192201 for query_name , query_ret in r .iteritems ():
193202 for result in query_ret ['data' ]:
194203 for key , value in result .iteritems ():
195- if value .startswith ('__JSONIFY__' ):
204+ if value and isinstance ( value , str ) and value .startswith ('__JSONIFY__' ):
196205 result [key ] = json .loads (value [len ('__JSONIFY__' ):])
197206
198207 return ret
@@ -268,3 +277,97 @@ def hubble_versions():
268277
269278 return {'hubble_versions' : {'data' : [versions ],
270279 'result' : True }}
280+
281+
282+ def top (query_group ,
283+ topfile = 'salt://hubblestack_nebula/top.nebula' ,
284+ verbose = False ,
285+ report_version_with_day = True ):
286+
287+ if salt .utils .is_windows ():
288+ topfile = 'salt://hubblestack_nebula/win_top.nebula'
289+
290+ configs = get_top_data (topfile )
291+
292+ configs = ['salt://hubblestack_nebula/' + config .replace ('.' , '/' ) + '.yaml'
293+ for config in configs ]
294+
295+ return queries (query_group ,
296+ query_file = configs ,
297+ verbose = False ,
298+ report_version_with_day = True )
299+
300+
301+ def get_top_data (topfile ):
302+
303+ topfile = __salt__ ['cp.cache_file' ](topfile )
304+
305+ try :
306+ with open (topfile ) as handle :
307+ topdata = yaml .safe_load (handle )
308+ except Exception as e :
309+ raise CommandExecutionError ('Could not load topfile: {0}' .format (e ))
310+
311+ if not isinstance (topdata , dict ) or 'nebula' not in topdata or \
312+ not (isinstance (topdata ['nebula' ], dict )):
313+ raise CommandExecutionError ('Nebula topfile not formatted correctly' )
314+
315+ topdata = topdata ['nebula' ]
316+
317+ ret = []
318+
319+ for match , data in topdata .iteritems ():
320+ if __salt__ ['match.compound' ](match ):
321+ ret .extend (data )
322+
323+ return ret
324+
325+
326+ def _dict_update (dest , upd , recursive_update = True , merge_lists = False ):
327+ '''
328+ Recursive version of the default dict.update
329+
330+ Merges upd recursively into dest
331+
332+ If recursive_update=False, will use the classic dict.update, or fall back
333+ on a manual merge (helpful for non-dict types like FunctionWrapper)
334+
335+ If merge_lists=True, will aggregate list object types instead of replace.
336+ This behavior is only activated when recursive_update=True. By default
337+ merge_lists=False.
338+ '''
339+ if (not isinstance (dest , collections .Mapping )) \
340+ or (not isinstance (upd , collections .Mapping )):
341+ raise TypeError ('Cannot update using non-dict types in dictupdate.update()' )
342+ updkeys = list (upd .keys ())
343+ if not set (list (dest .keys ())) & set (updkeys ):
344+ recursive_update = False
345+ if recursive_update :
346+ for key in updkeys :
347+ val = upd [key ]
348+ try :
349+ dest_subkey = dest .get (key , None )
350+ except AttributeError :
351+ dest_subkey = None
352+ if isinstance (dest_subkey , collections .Mapping ) \
353+ and isinstance (val , collections .Mapping ):
354+ ret = update (dest_subkey , val , merge_lists = merge_lists )
355+ dest [key ] = ret
356+ elif isinstance (dest_subkey , list ) \
357+ and isinstance (val , list ):
358+ if merge_lists :
359+ dest [key ] = dest .get (key , []) + val
360+ else :
361+ dest [key ] = upd [key ]
362+ else :
363+ dest [key ] = upd [key ]
364+ return dest
365+ else :
366+ try :
367+ for k in upd .keys ():
368+ dest [k ] = upd [k ]
369+ except AttributeError :
370+ # this mapping is not a dict
371+ for k in upd :
372+ dest [k ] = upd [k ]
373+ return dest
0 commit comments