Skip to content

Commit 2b8ce01

Browse files
authored
Merge pull request #113 from hubblestack/develop
Merge to master (prep v2017.9.0)
2 parents 6a89ca8 + 3172d5a commit 2b8ce01

17 files changed

+1515
-735
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ gitfs_remotes:
5959
- https://github.com/hubblestack/hubblestack_data.git:
6060
- root: ''
6161
- https://github.com/hubblestack/hubble-salt.git:
62-
- base: v2017.8.3
62+
- base: v2017.9.0
6363
- root: ''
6464
```
6565

_beacons/pulsar.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
DEFAULT_MASK = None
4040

4141
__virtualname__ = 'pulsar'
42-
__version__ = 'v2017.8.3'
42+
__version__ = 'v2017.9.0'
4343
CONFIG = None
4444
CONFIG_STALENESS = 0
4545

_beacons/win_pulsar.py

-606
This file was deleted.

_grains/osqueryinfo.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import salt.utils
4+
import salt.modules.cmdmod
5+
6+
__salt__ = { 'cmd.run': salt.modules.cmdmod._run_quiet }
7+
8+
def osquerygrain():
9+
'''
10+
Return osquery version in grain
11+
'''
12+
# Provides:
13+
# osqueryversion
14+
# osquerybinpath
15+
grains = {}
16+
option = '--version'
17+
18+
# Prefer our /opt/osquery/osqueryi if present
19+
osqueryipaths = ('/opt/osquery/osqueryi', 'osqueryi', '/usr/bin/osqueryi')
20+
for path in osqueryipaths:
21+
if salt.utils.which(path):
22+
for item in __salt__['cmd.run']('{0} {1}'.format(path, option)).split():
23+
if item[:1].isdigit():
24+
grains['osqueryversion'] = item
25+
grains['osquerybinpath'] = salt.utils.which(path)
26+
break
27+
break
28+
return grains

_modules/hubble.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
from nova_loader import NovaLazyLoader
3636

3737
__nova__ = {}
38-
__version__ = 'v2017.8.3'
38+
__version__ = 'v2017.9.0'
3939

4040

4141
def audit(configs=None,
@@ -112,7 +112,7 @@ def audit(configs=None,
112112
show_success=show_success,
113113
show_compliance=show_compliance)
114114

115-
if __salt__['config.get']('hubblestack:nova:autoload', True):
115+
if not called_from_top and __salt__['config.get']('hubblestack:nova:autoload', True):
116116
load()
117117
if not __nova__:
118118
return False, 'No nova modules/data have been loaded.'
@@ -465,9 +465,14 @@ def top(topfile='top.nova',
465465
else:
466466
if 'Errors' not in results:
467467
results['Errors'] = {}
468-
results['Errors'][topfile] = {'error': 'topfile malformed, list '
469-
'entries must be strings or dicts'}
470-
return results
468+
error_log = 'topfile malformed, list entries must be strings or '\
469+
'dicts: {0}'.format(data)
470+
results['Errors'][topfile] = {'error': error_log}
471+
log.error(error_log)
472+
continue
473+
474+
if not data_by_tag:
475+
return results
471476

472477
# Run the audits
473478
for tag, data in data_by_tag.iteritems():

_modules/nebula_osquery.py

+120-17
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,14 @@
3434
import os
3535
import sys
3636
import yaml
37+
import collections
3738

3839
import salt.utils
3940
from salt.exceptions import CommandExecutionError
4041

4142
log = 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

_modules/win_pulsar.py

+54-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
CONFIG = None
2929
CONFIG_STALENESS = 0
3030

31-
__version__ = 'v2017.8.3'
31+
__version__ = 'v2017.9.0'
3232

3333

3434
def __virtual__():
@@ -158,9 +158,10 @@ def process(configfile='salt://hubblestack_pulsar/hubblestack_pulsar_win_config.
158158
# Validate ACLs on watched folders/files and add if needed
159159
if update_acls:
160160
for path in config:
161-
if path == 'win_notify_interval' or path == 'return' or path == 'batch' or path == 'checksum' or path == 'stats':
161+
if path in ['win_notify_interval', 'return', 'batch', 'checksum', 'stats', 'paths', 'verbose']:
162162
continue
163163
if not os.path.exists(path):
164+
log.info('The folder path {0} does not exist'.format(path))
164165
continue
165166
if isinstance(config[path], dict):
166167
mask = config[path].get('mask', DEFAULT_MASK)
@@ -223,6 +224,21 @@ def process(configfile='salt://hubblestack_pulsar/hubblestack_pulsar_win_config.
223224
return ret
224225

225226

227+
def canary(change_file=None):
228+
'''
229+
Simple module to change a file to trigger a FIM event (daily, etc)
230+
231+
THE SPECIFIED FILE WILL BE CREATED AND DELETED
232+
233+
Defaults to CONF_DIR/fim_canary.tmp, i.e. /etc/hubble/fim_canary.tmp
234+
'''
235+
if change_file is None:
236+
conf_dir = os.path.dirname(__opts__['conf_file'])
237+
change_file = os.path.join(conf_dir, 'fim_canary.tmp')
238+
__salt__['file.touch'](change_file)
239+
__salt__['file.remove'](change_file)
240+
241+
226242
def _check_acl(path, mask, wtype, recurse):
227243
audit_dict = {}
228244
success = True
@@ -551,3 +567,39 @@ def _dict_update(dest, upd, recursive_update=True, merge_lists=False):
551567
for k in upd:
552568
dest[k] = upd[k]
553569
return dest
570+
571+
572+
def top(topfile='salt://hubblestack_pulsar/win_top.pulsar',
573+
verbose=False):
574+
575+
configs = get_top_data(topfile)
576+
577+
configs = ['salt://hubblestack_pulsar/' + config.replace('.','/') + '.yaml'
578+
for config in configs]
579+
580+
return process(configs, verbose=verbose)
581+
582+
583+
def get_top_data(topfile):
584+
585+
topfile = __salt__['cp.cache_file'](topfile)
586+
587+
try:
588+
with open(topfile) as handle:
589+
topdata = yaml.safe_load(handle)
590+
except Exception as e:
591+
raise CommandExecutionError('Could not load topfile: {0}'.format(e))
592+
593+
if not isinstance(topdata, dict) or 'pulsar' not in topdata or \
594+
not(isinstance(topdata['pulsar'], dict)):
595+
raise CommandExecutionError('Pulsar topfile not formatted correctly')
596+
597+
topdata = topdata['pulsar']
598+
599+
ret = []
600+
601+
for match, data in topdata.iteritems():
602+
if __salt__['match.compound'](match):
603+
ret.extend(data)
604+
605+
return ret

_returners/slack_pulsar_returner.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
# Import Salt Libs
7070
import salt.returners
7171

72-
__version__ = 'v2017.8.3'
72+
__version__ = 'v2017.9.0'
7373

7474
log = logging.getLogger(__name__)
7575

0 commit comments

Comments
 (0)