34
34
import os
35
35
import sys
36
36
import yaml
37
+ import collections
37
38
38
39
import salt .utils
39
40
from salt .exceptions import CommandExecutionError
40
41
41
42
log = logging .getLogger (__name__ )
42
43
43
- __version__ = 'v2017.8.3 '
44
+ __version__ = 'v2017.9.0 '
44
45
__virtualname__ = 'nebula'
45
46
46
47
@@ -74,13 +75,34 @@ def queries(query_group,
74
75
salt '*' nebula.queries hour verbose=True
75
76
salt '*' nebula.queries hour pillar_key=sec_osqueries
76
77
'''
78
+ query_data = {}
77
79
MAX_FILE_SIZE = 104857600
78
80
if query_file is None :
79
81
if salt .utils .is_windows ():
80
82
query_file = 'salt://hubblestack_nebula/hubblestack_nebula_win_queries.yaml'
81
83
else :
82
84
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__ :
84
106
if query_group == 'day' :
85
107
log .warning ('osquery not installed on this host. Returning baseline data' )
86
108
# Match the formatting of normal osquery results. Not super
@@ -140,19 +162,6 @@ def queries(query_group,
140
162
else :
141
163
return None
142
164
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
-
156
165
query_data = query_data .get (query_group , [])
157
166
158
167
if not query_data :
@@ -170,7 +179,7 @@ def queries(query_group,
170
179
'result' : True ,
171
180
}
172
181
173
- cmd = ['osqueryi' , '--read_max' , MAX_FILE_SIZE , '--json' , query_sql ]
182
+ cmd = [__grains__ [ 'osquerybinpath' ] , '--read_max' , MAX_FILE_SIZE , '--json' , query_sql ]
174
183
res = __salt__ ['cmd.run_all' ](cmd )
175
184
if res ['retcode' ] == 0 :
176
185
query_ret ['data' ] = json .loads (res ['stdout' ])
@@ -192,7 +201,7 @@ def queries(query_group,
192
201
for query_name , query_ret in r .iteritems ():
193
202
for result in query_ret ['data' ]:
194
203
for key , value in result .iteritems ():
195
- if value .startswith ('__JSONIFY__' ):
204
+ if value and isinstance ( value , str ) and value .startswith ('__JSONIFY__' ):
196
205
result [key ] = json .loads (value [len ('__JSONIFY__' ):])
197
206
198
207
return ret
@@ -268,3 +277,97 @@ def hubble_versions():
268
277
269
278
return {'hubble_versions' : {'data' : [versions ],
270
279
'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