Skip to content
This repository has been archived by the owner. It is now read-only.

Commit 3e2b066

Browse files
authored
Merge pull request #239 from HubbleStack/develop
Merge to master (prep for 2016.9.0)
2 parents 04ba36f + c5e7994 commit 3e2b066

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+429
-62
lines changed

README.rst

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ it to the minions.
7979
cd hubblestack-nova.git
8080
mkdir -p /srv/salt/_modules/
8181
cp _modules/hubble.py /srv/salt/_modules/
82-
cp -a hubblestack_nova /srv/salt/
82+
cp -a hubblestack_nova_profiles /srv/salt/
83+
cp -a hubblestack_nova_modules /srv/salt/
8384
8485
salt \* saltutil.sync_modules
8586
salt \* hubble.sync
@@ -93,15 +94,15 @@ Usage
9394

9495
There are four primary functions in the hubble.py module:
9596

96-
1. ``hubble.sync`` will sync the ``hubblestack_nova/`` directory to the minion(s).
97+
1. ``hubble.sync`` will sync the ``hubblestack_nova_profiles/`` and ``hubblestack_nova_modules/`` directories to the minion(s).
9798
2. ``hubble.load`` will load the synced audit modules and their yaml configuration files.
9899
3. ``hubble.audit`` will audit the minion(s) using the YAML profile(s) you provide as comma-separated arguments
99100
4. ``hubble.top`` will audit the minion(s) using the ``top.nova`` configuration.
100101

101102
``hubble.audit`` takes two optional arguments. The first is a comma-separated
102103
list of paths. These paths can be files or directories within the
103-
``hubblestack_nova`` directory. The second argument allows for toggling Nova
104-
configuration, such as verbosity, level of detail, etc.
104+
``hubblestack_nova_profiles`` directory. The second argument allows for
105+
toggling Nova configuration, such as verbosity, level of detail, etc.
105106

106107
If ``hubble.audit`` is run without targeting any audit configs or directories,
107108
it will instead run ``hubble.top`` with no arguments.
@@ -119,9 +120,9 @@ Here are some example calls:
119120
# Run hubble.top with the default topfile (top.nova)
120121
salt \* hubble.top
121122
122-
# Run all yaml configs and tags under salt://hubblestack_nova/foo/ and
123-
# salt://hubblestack_nova/bar, but only run audits with tags starting
124-
# with "CIS"
123+
# Run all yaml configs and tags under salt://hubblestack_nova_profiles/foo/
124+
# and salt://hubblestack_nova_profiles/bar, but only run audits with tags
125+
# starting with "CIS"
125126
salt \* hubble.audit foo,bar tags='CIS*'
126127
127128
.. _nova_usage_topfile:
@@ -222,6 +223,7 @@ In order to run the audits once daily, you can use the following schedule:
222223
show_profile: True
223224
returner: splunk_nova_return
224225
return_job: False
226+
run_on_start: False
225227
226228
.. _nova_configuration:
227229

@@ -241,7 +243,8 @@ configurable via pillar. The defaults are shown below:
241243
hubblestack:
242244
nova:
243245
saltenv: base
244-
dir: salt://hubblestack_nova
246+
module_dir: salt://hubblestack_nova_modules
247+
profile_dir: salt://hubblestack_nova_profiles
245248
246249
2. By default, ``hubble.audit`` will call ``hubble.load`` (which in turn calls
247250
``hubble.sync``) in order to ensure that it is auditing with the most up-to-date

_modules/hubble.py

Lines changed: 87 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
See README for documentation
1111
1212
Configuration:
13-
- hubblestack:nova:dir
13+
- hubblestack:nova:module_dir
14+
- hubblestack:nova:profile_dir
1415
- hubblestack:nova:saltenv
1516
- hubblestack:nova:autoload
1617
- hubblestack:nova:autosync
@@ -299,7 +300,8 @@ def top(topfile='top.nova',
299300
Arguments:
300301
301302
topfile
302-
The path of the topfile, relative to your hubblestack_nova directory.
303+
The path of the topfile, relative to your hubblestack_nova_profiles
304+
directory.
303305
304306
verbose
305307
Whether to show additional information about audits, including
@@ -413,21 +415,27 @@ def top(topfile='top.nova',
413415
return results
414416

415417

416-
def sync():
418+
def sync(clean=False):
417419
'''
418-
Sync the nova audit modules from the saltstack fileserver.
420+
Sync the nova audit modules and profiles from the saltstack fileserver.
419421
420422
The modules should be stored in the salt fileserver. By default nova will
421-
search the base environment for a top level ``hubblestack_nova`` directory,
422-
unless otherwise specified via pillar or minion config
423-
(``hubblestack:nova:dir``)
423+
search the base environment for a top level ``hubblestack_nova_modules``
424+
directory, unless otherwise specified via pillar or minion config
425+
(``hubblestack:nova:module_dir``)
424426
425-
Modules will just be cached in the normal minion cachedir
427+
The profiles should be stored in the salt fileserver. By default nova will
428+
search the base environment for a top level ``hubblestack_nova_profiles``
429+
directory, unless otherwise specified via pillar or minion config
430+
(``hubblestack:nova:profile_dir``)
426431
427-
Returns the minion's path to the cached directory
432+
Modules and profiles will be cached in the normal minion cachedir
428433
429-
NOTE: This function will also clean out existing files at the cached
430-
location, as cp.cache_dir doesn't clean out old files
434+
Returns a boolean representing success
435+
436+
NOTE: This function will optionally clean out existing files at the cached
437+
location, as cp.cache_dir doesn't clean out old files. Pass ``clean=True``
438+
to enable this behavior
431439
432440
CLI Examples:
433441
@@ -437,35 +445,44 @@ def sync():
437445
salt '*' nova.sync saltenv=hubble
438446
'''
439447
log.debug('syncing nova modules')
440-
nova_dir = __salt__['config.get']('hubblestack:nova:dir', 'salt://hubblestack_nova')
448+
nova_profile_dir = __salt__['config.get']('hubblestack:nova:profile_dir',
449+
'salt://hubblestack_nova_profiles')
450+
nova_module_dir = __salt__['config.get']('hubblestack:nova:module_dir',
451+
'salt://hubblestack_nova_modules')
441452
saltenv = __salt__['config.get']('hubblestack:nova:saltenv', 'base')
442453

443-
# Support optional salt:// in config
444-
if 'salt://' in nova_dir:
445-
path = nova_dir
446-
_, _, nova_dir = nova_dir.partition('salt://')
447-
else:
448-
path = 'salt://{0}'.format(nova_dir)
449-
450454
# Clean previously synced files
451-
__salt__['file.remove'](_hubble_dir())
452-
# Sync the files
453-
cached = __salt__['cp.cache_dir'](path, saltenv=saltenv)
454-
455-
if cached and isinstance(cached, list):
456-
# Success! Trim the paths
457-
cachedir = _hubble_dir()
458-
ret = [relative.partition(cachedir)[2] for relative in cached]
459-
return ret
460-
else:
461-
if isinstance(cached, list):
462-
# Nothing was found
463-
return cached
455+
if clean:
456+
for nova_dir in _hubble_dir():
457+
__salt__['file.remove'](nova_dir)
458+
459+
synced = []
460+
for i, nova_dir in enumerate((nova_module_dir, nova_profile_dir)):
461+
# Support optional salt:// in config
462+
if 'salt://' in nova_dir:
463+
path = nova_dir
464+
_, _, nova_dir = nova_dir.partition('salt://')
465+
else:
466+
path = 'salt://{0}'.format(nova_dir)
467+
468+
# Sync the files
469+
cached = __salt__['cp.cache_dir'](path, saltenv=saltenv)
470+
471+
if cached and isinstance(cached, list):
472+
# Success! Trim the paths
473+
cachedir = os.path.dirname(_hubble_dir()[i])
474+
ret = [relative.partition(cachedir)[2] for relative in cached]
475+
synced.extend(ret)
464476
else:
465-
# Something went wrong, there's likely a stacktrace in the output
466-
# of cache_dir
467-
raise CommandExecutionError('An error occurred while syncing: {0}'
468-
.format(cached))
477+
if isinstance(cached, list):
478+
# Nothing was found
479+
synced.extend(cached)
480+
else:
481+
# Something went wrong, there's likely a stacktrace in the output
482+
# of cache_dir
483+
raise CommandExecutionError('An error occurred while syncing: {0}'
484+
.format(cached))
485+
return synced
469486

470487

471488
def load():
@@ -474,8 +491,10 @@ def load():
474491
'''
475492
if __salt__['config.get']('hubblestack:nova:autosync', True):
476493
sync()
477-
if not os.path.isdir(_hubble_dir()):
478-
return False, 'No synced nova modules found'
494+
495+
for nova_dir in _hubble_dir():
496+
if not os.path.isdir(nova_dir):
497+
return False, 'No synced nova modules/profiles found'
479498

480499
log.debug('loading nova modules')
481500

@@ -491,18 +510,28 @@ def load():
491510

492511
def _hubble_dir():
493512
'''
494-
Generate the local minion directory to which nova modules are synced
513+
Generate the local minion directories to which nova modules and profiles
514+
are synced
515+
516+
Returns a tuple of two paths, the first for nova modules, the second for
517+
nova profiles
495518
'''
496-
nova_dir = __salt__['config.get']('hubblestack:nova:dir', 'hubblestack_nova')
519+
nova_profile_dir = __salt__['config.get']('hubblestack:nova:profile_dir',
520+
'salt://hubblestack_nova_profiles')
521+
nova_module_dir = __salt__['config.get']('hubblestack:nova:module_dir',
522+
'salt://hubblestack_nova_modules')
523+
dirs = []
497524
# Support optional salt:// in config
498-
if 'salt://' in nova_dir:
499-
_, _, nova_dir = nova_dir.partition('salt://')
500-
saltenv = __salt__['config.get']('hubblestack:nova:saltenv', 'base')
501-
cachedir = os.path.join(__opts__.get('cachedir'),
502-
'files',
503-
saltenv,
504-
nova_dir)
505-
return cachedir
525+
for nova_dir in (nova_module_dir, nova_profile_dir):
526+
if 'salt://' in nova_dir:
527+
_, _, nova_dir = nova_dir.partition('salt://')
528+
saltenv = __salt__['config.get']('hubblestack:nova:saltenv', 'base')
529+
cachedir = os.path.join(__opts__.get('cachedir'),
530+
'files',
531+
saltenv,
532+
nova_dir)
533+
dirs.append(cachedir)
534+
return tuple(dirs)
506535

507536

508537
def _calculate_compliance(results):
@@ -526,7 +555,7 @@ def _get_top_data(topfile):
526555
'''
527556
Helper method to retrieve and parse the nova topfile
528557
'''
529-
topfile = os.path.join(_hubble_dir(), topfile)
558+
topfile = os.path.join(_hubble_dir()[1], topfile)
530559

531560
try:
532561
with open(topfile) as handle:
@@ -558,7 +587,7 @@ class NovaLazyLoader(LazyLoader):
558587
'''
559588

560589
def __init__(self):
561-
super(NovaLazyLoader, self).__init__([_hubble_dir()],
590+
super(NovaLazyLoader, self).__init__(_hubble_dir(),
562591
opts=__opts__,
563592
tag='nova')
564593
self.__data__ = {}
@@ -597,6 +626,14 @@ def refresh_file_mapping(self):
597626
# Nova only supports .py and .yaml
598627
if ext not in ['.py', '.yaml']:
599628
continue
629+
# Python only in the modules directory, yaml only
630+
# in the profiles directory. This is hacky but was a
631+
# quick fix.
632+
nova_module_cache, nova_profile_cache = _hubble_dir()
633+
if ext == '.py' and fpath.startswith(nova_profile_cache):
634+
continue
635+
if ext == '.yaml' and fpath.startswith(nova_module_cache):
636+
continue
600637
if f_withext in self.disabled:
601638
#log.trace(
602639
# 'Skipping {0}, it is disabled by configuration'.format(

hubblestack_nova/modules/command.py renamed to hubblestack_nova_modules/command.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
Hubble Nova plugin for running arbitrary commands and checking the output of
44
those commands
55
6+
This module is deprecated, and must be explicitly enabled in pillar/minion
7+
config via the hubblestack:nova:enable_command_module (should be set to True
8+
to enable this module). This allows nova to run arbitrary commands via yaml
9+
profiles.
10+
611
:maintainer: HubbleStack / basepi
712
:maturity: 2016.7.0
813
:platform: All
@@ -101,6 +106,14 @@ def audit(data_list, tags, verbose=False, show_profile=False, debug=False):
101106
log.debug(__tags__)
102107

103108
ret = {'Success': [], 'Failure': [], 'Controlled': []}
109+
110+
if __tags__ and not __salt__['config.get']('hubblestack:nova:enable_command_module',
111+
False):
112+
ret['Error'] = ['command module has not been explicitly enabled in '
113+
'config. Please set hubblestack:nova:enable_command_module '
114+
'to True in pillar or minion config to allow this module.']
115+
return ret
116+
104117
for tag in __tags__:
105118
if fnmatch.fnmatch(tag, tags):
106119
for tag_data in __tags__[tag]:

hubblestack_nova/modules/grep.py renamed to hubblestack_nova_modules/grep.py

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ def audit(data_list, tags, verbose=False, show_profile=False, debug=False):
113113
if isinstance(grep_args, str):
114114
grep_args = [grep_args]
115115

116-
grep_ret = __salt__['file.grep'](name,
117-
tag_data['pattern'],
118-
*grep_args).get('stdout')
116+
grep_ret = _grep(name,
117+
tag_data['pattern'],
118+
*grep_args).get('stdout')
119119

120120
found = False
121121
if grep_ret:
@@ -278,3 +278,61 @@ def _get_tags(data):
278278
formatted_data.pop('data')
279279
ret[tag].append(formatted_data)
280280
return ret
281+
282+
283+
def _grep(path,
284+
pattern,
285+
*opts):
286+
'''
287+
Grep for a string in the specified file
288+
289+
.. note::
290+
This function's return value is slated for refinement in future
291+
versions of Salt
292+
293+
path
294+
Path to the file to be searched
295+
296+
.. note::
297+
Globbing is supported (i.e. ``/var/log/foo/*.log``, but if globbing
298+
is being used then the path should be quoted to keep the shell from
299+
attempting to expand the glob expression.
300+
301+
pattern
302+
Pattern to match. For example: ``test``, or ``a[0-5]``
303+
304+
opts
305+
Additional command-line flags to pass to the grep command. For example:
306+
``-v``, or ``-i -B2``
307+
308+
.. note::
309+
The options should come after a double-dash (as shown in the
310+
examples below) to keep Salt's own argument parser from
311+
interpreting them.
312+
313+
CLI Example:
314+
315+
.. code-block:: bash
316+
317+
salt '*' file.grep /etc/passwd nobody
318+
salt '*' file.grep /etc/sysconfig/network-scripts/ifcfg-eth0 ipaddr -- -i
319+
salt '*' file.grep /etc/sysconfig/network-scripts/ifcfg-eth0 ipaddr -- -i -B2
320+
salt '*' file.grep "/etc/sysconfig/network-scripts/*" ipaddr -- -i -l
321+
'''
322+
path = os.path.expanduser(path)
323+
324+
split_opts = []
325+
for opt in opts:
326+
try:
327+
opt = salt.utils.shlex_split(opt)
328+
except AttributeError:
329+
opt = salt.utils.shlex_split(str(opt))
330+
split_opts.extend(opt)
331+
332+
cmd = ['grep'] + split_opts + [pattern, path]
333+
try:
334+
ret = __salt__['cmd.run_all'](cmd, python_shell=False, ignore_retcode=True)
335+
except (IOError, OSError) as exc:
336+
raise CommandExecutionError(exc.strerror)
337+
338+
return ret

0 commit comments

Comments
 (0)