mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 08:58:59 +00:00
commit
9c4292fb4e
105
salt/modules/openscap.py
Normal file
105
salt/modules/openscap.py
Normal file
@ -0,0 +1,105 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
import tempfile
|
||||
import shlex
|
||||
import shutil
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from salt.client import Caller
|
||||
|
||||
|
||||
ArgumentParser = object
|
||||
|
||||
try:
|
||||
import argparse # pylint: disable=minimum-python-version
|
||||
ArgumentParser = argparse.ArgumentParser
|
||||
HAS_ARGPARSE = True
|
||||
except ImportError: # python 2.6
|
||||
HAS_ARGPARSE = False
|
||||
|
||||
|
||||
_XCCDF_MAP = {
|
||||
'eval': {
|
||||
'parser_arguments': [
|
||||
(('--profile',), {'required': True}),
|
||||
],
|
||||
'cmd_pattern': (
|
||||
"oscap xccdf eval "
|
||||
"--oval-results --results results.xml --report report.html "
|
||||
"--profile {0} {1}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def __virtual__():
|
||||
return HAS_ARGPARSE, 'argparse module is required.'
|
||||
|
||||
|
||||
class _ArgumentParser(ArgumentParser):
|
||||
|
||||
def __init__(self, action=None, *args, **kwargs):
|
||||
super(_ArgumentParser, self).__init__(*args, prog='oscap', **kwargs)
|
||||
self.add_argument('action', choices=['eval'])
|
||||
add_arg = None
|
||||
for params, kwparams in _XCCDF_MAP['eval']['parser_arguments']:
|
||||
self.add_argument(*params, **kwparams)
|
||||
|
||||
def error(self, message, *args, **kwargs):
|
||||
raise Exception(message)
|
||||
|
||||
|
||||
_OSCAP_EXIT_CODES_MAP = {
|
||||
0: True, # all rules pass
|
||||
1: False, # there is an error during evaluation
|
||||
2: True # there is at least one rule with either fail or unknown result
|
||||
}
|
||||
|
||||
|
||||
def xccdf(params):
|
||||
'''
|
||||
Run ``oscap xccdf`` commands on minions.
|
||||
It uses cp.push_dir to upload the generated files to the salt master
|
||||
in the master's minion files cachedir
|
||||
(defaults to ``/var/cache/salt/master/minions/minion-id/files``)
|
||||
|
||||
It needs ``file_recv`` set to ``True`` in the master configuration file.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' openscap.xccdf "eval --profile Default /usr/share/openscap/scap-yast2sec-xccdf.xml"
|
||||
'''
|
||||
params = shlex.split(params)
|
||||
policy = params[-1]
|
||||
|
||||
success = True
|
||||
error = None
|
||||
upload_dir = None
|
||||
action = None
|
||||
|
||||
try:
|
||||
parser = _ArgumentParser()
|
||||
action = parser.parse_known_args(params)[0].action
|
||||
args, argv = _ArgumentParser(action=action).parse_known_args(args=params)
|
||||
except Exception as err:
|
||||
success = False
|
||||
error = str(err)
|
||||
|
||||
if success:
|
||||
cmd = _XCCDF_MAP[action]['cmd_pattern'].format(args.profile, policy)
|
||||
tempdir = tempfile.mkdtemp()
|
||||
proc = Popen(
|
||||
shlex.split(cmd), stdout=PIPE, stderr=PIPE, cwd=tempdir)
|
||||
(stdoutdata, stderrdata) = proc.communicate()
|
||||
success = _OSCAP_EXIT_CODES_MAP[proc.returncode]
|
||||
if success:
|
||||
caller = Caller()
|
||||
caller.cmd('cp.push_dir', tempdir)
|
||||
shutil.rmtree(tempdir, ignore_errors=True)
|
||||
upload_dir = tempdir
|
||||
else:
|
||||
error = stderrdata
|
||||
|
||||
return dict(success=success, upload_dir=upload_dir, error=error)
|
207
tests/unit/modules/openscap_test.py
Normal file
207
tests/unit/modules/openscap_test.py
Normal file
@ -0,0 +1,207 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
|
||||
from subprocess import PIPE
|
||||
|
||||
from salt.modules import openscap
|
||||
|
||||
from salttesting import skipIf, TestCase
|
||||
from salttesting.mock import (
|
||||
Mock,
|
||||
MagicMock,
|
||||
patch,
|
||||
NO_MOCK,
|
||||
NO_MOCK_REASON
|
||||
)
|
||||
|
||||
|
||||
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
||||
class OpenscapTestCase(TestCase):
|
||||
|
||||
random_temp_dir = '/tmp/unique-name'
|
||||
policy_file = '/usr/share/openscap/policy-file-xccdf.xml'
|
||||
|
||||
def setUp(self):
|
||||
patchers = [
|
||||
patch('salt.modules.openscap.Caller', MagicMock()),
|
||||
patch('salt.modules.openscap.shutil.rmtree', Mock()),
|
||||
patch(
|
||||
'salt.modules.openscap.tempfile.mkdtemp',
|
||||
Mock(return_value=self.random_temp_dir)
|
||||
),
|
||||
]
|
||||
for patcher in patchers:
|
||||
self.apply_patch(patcher)
|
||||
|
||||
def apply_patch(self, patcher):
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
@patch(
|
||||
'salt.modules.openscap.Popen',
|
||||
MagicMock(
|
||||
return_value=Mock(
|
||||
**{'returncode': 0, 'communicate.return_value': ('', '')}
|
||||
)
|
||||
)
|
||||
)
|
||||
def test_openscap_xccdf_eval_success(self):
|
||||
response = openscap.xccdf(
|
||||
'eval --profile Default {0}'.format(self.policy_file))
|
||||
|
||||
self.assertEqual(openscap.tempfile.mkdtemp.call_count, 1)
|
||||
expected_cmd = [
|
||||
'oscap',
|
||||
'xccdf',
|
||||
'eval',
|
||||
'--oval-results',
|
||||
'--results', 'results.xml',
|
||||
'--report', 'report.html',
|
||||
'--profile', 'Default',
|
||||
self.policy_file
|
||||
]
|
||||
openscap.Popen.assert_called_once_with(
|
||||
expected_cmd,
|
||||
cwd=openscap.tempfile.mkdtemp.return_value,
|
||||
stderr=PIPE,
|
||||
stdout=PIPE)
|
||||
openscap.Caller().cmd.assert_called_once_with(
|
||||
'cp.push_dir', self.random_temp_dir)
|
||||
self.assertEqual(openscap.shutil.rmtree.call_count, 1)
|
||||
self.assertEqual(
|
||||
response,
|
||||
{
|
||||
'upload_dir': self.random_temp_dir,
|
||||
'error': None, 'success': True
|
||||
}
|
||||
)
|
||||
|
||||
@patch(
|
||||
'salt.modules.openscap.Popen',
|
||||
MagicMock(
|
||||
return_value=Mock(
|
||||
**{'returncode': 2, 'communicate.return_value': ('', '')}
|
||||
)
|
||||
)
|
||||
)
|
||||
def test_openscap_xccdf_eval_success_with_failing_rules(self):
|
||||
response = openscap.xccdf(
|
||||
'eval --profile Default {0}'.format(self.policy_file))
|
||||
|
||||
self.assertEqual(openscap.tempfile.mkdtemp.call_count, 1)
|
||||
expected_cmd = [
|
||||
'oscap',
|
||||
'xccdf',
|
||||
'eval',
|
||||
'--oval-results',
|
||||
'--results', 'results.xml',
|
||||
'--report', 'report.html',
|
||||
'--profile', 'Default',
|
||||
self.policy_file
|
||||
]
|
||||
openscap.Popen.assert_called_once_with(
|
||||
expected_cmd,
|
||||
cwd=openscap.tempfile.mkdtemp.return_value,
|
||||
stderr=PIPE,
|
||||
stdout=PIPE)
|
||||
openscap.Caller().cmd.assert_called_once_with(
|
||||
'cp.push_dir', self.random_temp_dir)
|
||||
self.assertEqual(openscap.shutil.rmtree.call_count, 1)
|
||||
self.assertEqual(
|
||||
response,
|
||||
{
|
||||
'upload_dir': self.random_temp_dir,
|
||||
'error': None,
|
||||
'success': True
|
||||
}
|
||||
)
|
||||
|
||||
def test_openscap_xccdf_eval_fail_no_profile(self):
|
||||
response = openscap.xccdf(
|
||||
'eval --param Default /unknown/param')
|
||||
self.assertEqual(
|
||||
response,
|
||||
{
|
||||
'error': 'argument --profile is required',
|
||||
'upload_dir': None,
|
||||
'success': False
|
||||
}
|
||||
)
|
||||
|
||||
@patch(
|
||||
'salt.modules.openscap.Popen',
|
||||
MagicMock(
|
||||
return_value=Mock(
|
||||
**{'returncode': 2, 'communicate.return_value': ('', '')}
|
||||
)
|
||||
)
|
||||
)
|
||||
def test_openscap_xccdf_eval_success_ignore_unknown_params(self):
|
||||
response = openscap.xccdf(
|
||||
'eval --profile Default --param Default /policy/file')
|
||||
self.assertEqual(
|
||||
response,
|
||||
{
|
||||
'upload_dir': self.random_temp_dir,
|
||||
'error': None,
|
||||
'success': True
|
||||
}
|
||||
)
|
||||
expected_cmd = [
|
||||
'oscap',
|
||||
'xccdf',
|
||||
'eval',
|
||||
'--oval-results',
|
||||
'--results', 'results.xml',
|
||||
'--report', 'report.html',
|
||||
'--profile', 'Default',
|
||||
'/policy/file'
|
||||
]
|
||||
openscap.Popen.assert_called_once_with(
|
||||
expected_cmd,
|
||||
cwd=openscap.tempfile.mkdtemp.return_value,
|
||||
stderr=PIPE,
|
||||
stdout=PIPE)
|
||||
|
||||
@patch(
|
||||
'salt.modules.openscap.Popen',
|
||||
MagicMock(
|
||||
return_value=Mock(**{
|
||||
'returncode': 1,
|
||||
'communicate.return_value': ('', 'evaluation error')
|
||||
})
|
||||
)
|
||||
)
|
||||
def test_openscap_xccdf_eval_evaluation_error(self):
|
||||
response = openscap.xccdf(
|
||||
'eval --profile Default {0}'.format(self.policy_file))
|
||||
|
||||
self.assertEqual(
|
||||
response,
|
||||
{
|
||||
'upload_dir': None,
|
||||
'error': 'evaluation error',
|
||||
'success': False
|
||||
}
|
||||
)
|
||||
|
||||
@patch(
|
||||
'salt.modules.openscap.Popen',
|
||||
MagicMock(
|
||||
return_value=Mock(**{
|
||||
'returncode': 1,
|
||||
'communicate.return_value': ('', 'evaluation error')
|
||||
})
|
||||
)
|
||||
)
|
||||
def test_openscap_xccdf_eval_fail_not_implemented_action(self):
|
||||
response = openscap.xccdf('info {0}'.format(self.policy_file))
|
||||
|
||||
self.assertEqual(
|
||||
response,
|
||||
{
|
||||
'upload_dir': None,
|
||||
'error': "argument action: invalid choice: 'info' (choose from 'eval')",
|
||||
'success': False
|
||||
}
|
||||
)
|
Loading…
Reference in New Issue
Block a user