Merge lp:~hopem/charm-helpers/hardeningfixes into lp:charm-helpers

Proposed by Edward Hope-Morley
Status: Merged
Merged at revision: 553
Proposed branch: lp:~hopem/charm-helpers/hardeningfixes
Merge into: lp:charm-helpers
Diff against target: 736 lines (+192/-101)
16 files modified
charmhelpers/contrib/hardening/apache/checks/config.py (+10/-8)
charmhelpers/contrib/hardening/audits/apache.py (+7/-4)
charmhelpers/contrib/hardening/audits/file.py (+4/-3)
charmhelpers/contrib/hardening/defaults/mysql.yaml (+4/-4)
charmhelpers/contrib/hardening/defaults/mysql.yaml.schema (+3/-3)
charmhelpers/contrib/hardening/harden.py (+3/-1)
charmhelpers/contrib/hardening/host/checks/sysctl.py (+4/-1)
charmhelpers/contrib/hardening/host/templates/99-juju-hardening.conf (+1/-1)
charmhelpers/contrib/hardening/mysql/checks/config.py (+4/-1)
charmhelpers/contrib/hardening/mysql/templates/hardening.cnf (+1/-1)
charmhelpers/contrib/hardening/utils.py (+2/-1)
tests/contrib/hardening/audits/test_apache_audits.py (+3/-1)
tests/contrib/hardening/audits/test_file_audits.py (+94/-42)
tests/contrib/hardening/mysql/checks/test_config.py (+1/-1)
tests/contrib/hardening/test_templating.py (+47/-27)
tests/contrib/hardening/test_utils.py (+4/-2)
To merge this branch: bzr merge lp:~hopem/charm-helpers/hardeningfixes
Reviewer Review Type Date Requested Status
Liam Young (community) Approve
Review via email: mp+289624@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Liam Young (gnuoy) wrote :

lgtm

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/contrib/hardening/apache/checks/config.py'
2--- charmhelpers/contrib/hardening/apache/checks/config.py 2016-03-18 05:54:42 +0000
3+++ charmhelpers/contrib/hardening/apache/checks/config.py 2016-03-21 10:39:40 +0000
4@@ -19,15 +19,17 @@
5 import subprocess
6
7
8-from charmhelpers.core.hookenv import log
9-from charmhelpers.core.hookenv import INFO
10-
11-from charmhelpers.contrib.hardening.audits.file import FilePermissionAudit
12-from charmhelpers.contrib.hardening.audits.file import DirectoryPermissionAudit
13-from charmhelpers.contrib.hardening.audits.file import NoReadWriteForOther
14-from charmhelpers.contrib.hardening.audits.file import TemplatedFile
15+from charmhelpers.core.hookenv import (
16+ log,
17+ INFO,
18+)
19+from charmhelpers.contrib.hardening.audits.file import (
20+ FilePermissionAudit,
21+ DirectoryPermissionAudit,
22+ NoReadWriteForOther,
23+ TemplatedFile,
24+)
25 from charmhelpers.contrib.hardening.audits.apache import DisabledModuleAudit
26-
27 from charmhelpers.contrib.hardening.apache import TEMPLATES_DIR
28 from charmhelpers.contrib.hardening import utils
29
30
31=== modified file 'charmhelpers/contrib/hardening/audits/apache.py'
32--- charmhelpers/contrib/hardening/audits/apache.py 2016-03-18 06:49:19 +0000
33+++ charmhelpers/contrib/hardening/audits/apache.py 2016-03-21 10:39:40 +0000
34@@ -19,9 +19,11 @@
35
36 from six import string_types
37
38-from charmhelpers.core.hookenv import log
39-from charmhelpers.core.hookenv import INFO
40-from charmhelpers.core.hookenv import ERROR
41+from charmhelpers.core.hookenv import (
42+ log,
43+ INFO,
44+ ERROR,
45+)
46
47 from charmhelpers.contrib.hardening.audits import BaseAudit
48
49@@ -50,7 +52,8 @@
50 non_compliant_modules = []
51 for module in self.modules:
52 if module in loaded_modules:
53- log('Module %s is enabled and should not be.', level=INFO)
54+ log("Module '%s' is enabled but should not be." %
55+ (module), level=INFO)
56 non_compliant_modules.append(module)
57
58 if len(non_compliant_modules) == 0:
59
60=== modified file 'charmhelpers/contrib/hardening/audits/file.py'
61--- charmhelpers/contrib/hardening/audits/file.py 2016-03-18 05:54:42 +0000
62+++ charmhelpers/contrib/hardening/audits/file.py 2016-03-21 10:39:40 +0000
63@@ -509,9 +509,10 @@
64
65 def is_compliant(self, path):
66 """
67- Given a set of content matching cases, check that all cases match
68- as expected with the contents of the file. Cases can be expected to
69- pass of fail.
70+ Given a set of content matching cases i.e. tuple(regex, bool) where
71+ bool value denotes whether or not regex is expected to match, check that
72+ all cases match as expected with the contents of the file. Cases can be
73+ expected to pass of fail.
74
75 :param path: Path of file to check.
76 :returns: Boolean value representing whether or not all cases are
77
78=== modified file 'charmhelpers/contrib/hardening/defaults/mysql.yaml'
79--- charmhelpers/contrib/hardening/defaults/mysql.yaml 2016-03-14 13:57:17 +0000
80+++ charmhelpers/contrib/hardening/defaults/mysql.yaml 2016-03-21 10:39:40 +0000
81@@ -4,6 +4,10 @@
82 # name 'mysql' as the root key followed by any of the following with new
83 # values.
84
85+hardening:
86+ mysql-conf: /etc/mysql/my.cnf
87+ hardening-conf: /etc/mysql/conf.d/hardening.cnf
88+
89 security:
90 # @see http://www.symantec.com/connect/articles/securing-mysql-step-step
91 # @see http://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_chroot
92@@ -32,7 +36,3 @@
93
94 # @see https://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_secure-file-priv
95 secure-file-priv: /tmp
96-
97-hardening:
98- mysql-conf: /etc/mysql/my.cnf
99- hardening-conf: /etc/mysql/conf.d/hardening.cnf
100
101=== modified file 'charmhelpers/contrib/hardening/defaults/mysql.yaml.schema'
102--- charmhelpers/contrib/hardening/defaults/mysql.yaml.schema 2016-03-14 13:57:17 +0000
103+++ charmhelpers/contrib/hardening/defaults/mysql.yaml.schema 2016-03-21 10:39:40 +0000
104@@ -1,5 +1,8 @@
105 # NOTE: this schema must contain all valid keys from it's associated defaults
106 # file. It is used to validate user-provided overrides.
107+hardening:
108+ mysql-conf:
109+ hardening-conf:
110 security:
111 chroot:
112 safe-user-create:
113@@ -10,6 +13,3 @@
114 allow-suspicious-udfs:
115 automatic-sp-privileges:
116 secure-file-priv:
117-hardening:
118- mysql-conf:
119- hardening-conf:
120\ No newline at end of file
121
122=== modified file 'charmhelpers/contrib/hardening/harden.py'
123--- charmhelpers/contrib/hardening/harden.py 2016-03-14 14:18:58 +0000
124+++ charmhelpers/contrib/hardening/harden.py 2016-03-21 10:39:40 +0000
125@@ -14,6 +14,8 @@
126 # You should have received a copy of the GNU Lesser General Public License
127 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
128
129+import six
130+
131 from collections import OrderedDict
132
133 from charmhelpers.core.hookenv import (
134@@ -60,7 +62,7 @@
135 if enabled:
136 modules_to_run = []
137 # modules will always be performed in the following order
138- for module, func in RUN_CATALOG.iteritems():
139+ for module, func in six.iteritems(RUN_CATALOG):
140 if module in enabled:
141 enabled.remove(module)
142 modules_to_run.append(func)
143
144=== modified file 'charmhelpers/contrib/hardening/host/checks/sysctl.py'
145--- charmhelpers/contrib/hardening/host/checks/sysctl.py 2016-03-14 20:24:18 +0000
146+++ charmhelpers/contrib/hardening/host/checks/sysctl.py 2016-03-21 10:39:40 +0000
147@@ -17,6 +17,7 @@
148 import os
149 import platform
150 import re
151+import six
152 import subprocess
153
154 from charmhelpers.core.hookenv import (
155@@ -184,7 +185,9 @@
156
157 ctxt['sysctl'][key] = d[2] or None
158
159- return ctxt
160+ # Translate for python3
161+ return {'sysctl_settings':
162+ [(k, v) for k, v in six.iteritems(ctxt['sysctl'])]}
163
164
165 class SysctlConf(TemplatedFile):
166
167=== modified file 'charmhelpers/contrib/hardening/host/templates/99-juju-hardening.conf'
168--- charmhelpers/contrib/hardening/host/templates/99-juju-hardening.conf 2016-03-14 20:24:18 +0000
169+++ charmhelpers/contrib/hardening/host/templates/99-juju-hardening.conf 2016-03-21 10:39:40 +0000
170@@ -2,6 +2,6 @@
171 # WARNING: This configuration file is maintained by Juju. Local changes may
172 # be overwritten.
173 ###############################################################################
174-{% for key, value in sysctl.iteritems() -%}
175+{% for key, value in sysctl_settings -%}
176 {{ key }}={{ value }}
177 {% endfor -%}
178
179=== modified file 'charmhelpers/contrib/hardening/mysql/checks/config.py'
180--- charmhelpers/contrib/hardening/mysql/checks/config.py 2016-03-16 01:44:06 +0000
181+++ charmhelpers/contrib/hardening/mysql/checks/config.py 2016-03-21 10:39:40 +0000
182@@ -14,6 +14,7 @@
183 # You should have received a copy of the GNU Lesser General Public License
184 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
185
186+import six
187 import subprocess
188
189 from charmhelpers.core.hookenv import (
190@@ -83,4 +84,6 @@
191 """
192 def __call__(self):
193 settings = utils.get_settings('mysql')
194- return {'mysql_settings': settings['security']}
195+ # Translate for python3
196+ return {'mysql_settings':
197+ [(k, v) for k, v in six.iteritems(settings['security'])]}
198
199=== modified file 'charmhelpers/contrib/hardening/mysql/templates/hardening.cnf'
200--- charmhelpers/contrib/hardening/mysql/templates/hardening.cnf 2016-03-16 01:44:06 +0000
201+++ charmhelpers/contrib/hardening/mysql/templates/hardening.cnf 2016-03-21 10:39:40 +0000
202@@ -3,7 +3,7 @@
203 # be overwritten.
204 ###############################################################################
205 [mysqld]
206-{% for setting, value in mysql_settings.iteritems() -%}
207+{% for setting, value in mysql_settings -%}
208 {% if value == 'True' -%}
209 {{ setting }}
210 {% elif value != 'None' and value != None -%}
211
212=== modified file 'charmhelpers/contrib/hardening/utils.py'
213--- charmhelpers/contrib/hardening/utils.py 2016-03-14 14:18:58 +0000
214+++ charmhelpers/contrib/hardening/utils.py 2016-03-21 10:39:40 +0000
215@@ -18,6 +18,7 @@
216 import grp
217 import os
218 import pwd
219+import six
220 import yaml
221
222 from charmhelpers.core.hookenv import (
223@@ -92,7 +93,7 @@
224 :returns: dictionary of modules config with user overrides applied.
225 """
226 if overrides:
227- for k, v in overrides.iteritems():
228+ for k, v in six.iteritems(overrides):
229 if k in schema:
230 if schema[k] is None:
231 settings[k] = v
232
233=== added file 'tests/contrib/hardening/__init__.py'
234=== modified file 'tests/contrib/hardening/audits/test_apache_audits.py'
235--- tests/contrib/hardening/audits/test_apache_audits.py 2016-03-18 05:54:42 +0000
236+++ tests/contrib/hardening/audits/test_apache_audits.py 2016-03-21 10:39:40 +0000
237@@ -49,15 +49,16 @@
238 self.assertFalse(mock_loaded_modules.called)
239
240 @patch.object(apache.DisabledModuleAudit, '_get_loaded_modules')
241+ @patch.object(apache, 'log', lambda *args, **kwargs: None)
242 def test_ensure_compliance_loaded_modules_raises_ex(self,
243 mock_loaded_modules):
244 mock_loaded_modules.side_effect = CalledProcessError(1, 'test', 'err')
245 audit = apache.DisabledModuleAudit('foo')
246 audit.ensure_compliance()
247- self.assertTrue(self.log.called)
248
249 @patch.object(apache.DisabledModuleAudit, '_get_loaded_modules')
250 @patch.object(apache.DisabledModuleAudit, '_disable_module')
251+ @patch.object(apache, 'log', lambda *args, **kwargs: None)
252 def test_disabled_modules_not_loaded(self, mock_disable_module,
253 mock_loaded_modules):
254 mock_loaded_modules.return_value = ['foo']
255@@ -68,6 +69,7 @@
256 @patch.object(apache.DisabledModuleAudit, '_get_loaded_modules')
257 @patch.object(apache.DisabledModuleAudit, '_disable_module')
258 @patch.object(apache.DisabledModuleAudit, '_restart_apache')
259+ @patch.object(apache, 'log', lambda *args, **kwargs: None)
260 def test_disabled_modules_loaded(self, mock_restart_apache,
261 mock_disable_module, mock_loaded_modules):
262 mock_loaded_modules.return_value = ['foo', 'bar']
263
264=== modified file 'tests/contrib/hardening/audits/test_file_audits.py'
265--- tests/contrib/hardening/audits/test_file_audits.py 2016-03-14 13:49:57 +0000
266+++ tests/contrib/hardening/audits/test_file_audits.py 2016-03-21 10:39:40 +0000
267@@ -38,7 +38,7 @@
268 def _patch_obj(self, obj, method):
269 _m = patch.object(obj, method)
270 mock = _m.start()
271- self.addCleanup(mock.stop)
272+ self.addCleanup(_m.stop)
273 setattr(self, method, mock)
274
275 def test_ensure_compliance(self, mock_exists):
276@@ -79,9 +79,9 @@
277 self._patch_obj(file.grp, 'getgrnam')
278 self._patch_obj(file.pwd, 'getpwnam')
279 self._patch_obj(file.FilePermissionAudit, '_get_stat')
280- self.getpwnam.return_value = EasyMock({'pw_name': 'ubuntu',
281+ self.getpwnam.return_value = EasyMock({'pw_name': 'testuser',
282 'pw_uid': 1000})
283- self.getgrnam.return_value = EasyMock({'gr_name': 'ubuntu',
284+ self.getgrnam.return_value = EasyMock({'gr_name': 'testgroup',
285 'gr_gid': 1000})
286 self._get_stat.return_value = EasyMock({'st_mode': 0o644,
287 'st_uid': 1000,
288@@ -90,47 +90,51 @@
289 def _patch_obj(self, obj, method):
290 _m = patch.object(obj, method)
291 mock = _m.start()
292- self.addCleanup(mock.stop)
293+ self.addCleanup(_m.stop)
294 setattr(self, method, mock)
295
296 def test_is_compliant(self):
297 check = file.FilePermissionAudit(paths=['/foo/bar'],
298- user='ubuntu',
299- group='ubuntu', mode=0o644)
300+ user='testuser',
301+ group='testgroup', mode=0o644)
302 compliant = check.is_compliant('/foo/bar')
303 self.assertTrue(compliant)
304
305+ @patch.object(file, 'log', lambda *args, **kwargs: None)
306 def test_not_compliant_wrong_group(self):
307- self.getgrnam.return_value = EasyMock({'gr_name': 'admin',
308+ self.getgrnam.return_value = EasyMock({'gr_name': 'testgroup',
309 'gr_gid': 222})
310- check = file.FilePermissionAudit(paths=['/foo/bar'], user='ubuntu',
311- group='ubuntu', mode=0o644)
312+ check = file.FilePermissionAudit(paths=['/foo/bar'], user='testuser',
313+ group='testgroup', mode=0o644)
314 compliant = check.is_compliant('/foo/bar')
315 self.assertFalse(compliant)
316
317+ @patch.object(file, 'log', lambda *args, **kwargs: None)
318 def test_not_compliant_wrong_user(self):
319 self.getpwnam.return_value = EasyMock({'pw_name': 'fred',
320 'pw_uid': 123})
321- check = file.FilePermissionAudit(paths=['/foo/bar'], user='ubuntu',
322- group='ubuntu', mode=0o644)
323+ check = file.FilePermissionAudit(paths=['/foo/bar'], user='testuser',
324+ group='testgroup', mode=0o644)
325 compliant = check.is_compliant('/foo/bar')
326 self.assertFalse(compliant)
327
328+ @patch.object(file, 'log', lambda *args, **kwargs: None)
329 def test_not_compliant_wrong_permissions(self):
330 self._get_stat.return_value = EasyMock({'st_mode': 0o777,
331 'st_uid': 1000,
332 'st_gid': 1000})
333- check = file.FilePermissionAudit(paths=['/foo/bar'], user='ubuntu',
334- group='ubuntu', mode=0o644)
335+ check = file.FilePermissionAudit(paths=['/foo/bar'], user='testuser',
336+ group='testgroup', mode=0o644)
337 compliant = check.is_compliant('/foo/bar')
338 self.assertFalse(compliant)
339
340 @patch('charmhelpers.contrib.hardening.utils.ensure_permissions')
341+ @patch.object(file, 'log', lambda *args, **kwargs: None)
342 def test_comply(self, _ensure_permissions):
343- check = file.FilePermissionAudit(paths=['/foo/bar'], user='ubuntu',
344- group='ubuntu', mode=0o644)
345+ check = file.FilePermissionAudit(paths=['/foo/bar'], user='testuser',
346+ group='testgroup', mode=0o644)
347 check.comply('/foo/bar')
348- c = call('/foo/bar', 'ubuntu', 'ubuntu', 0o644)
349+ c = call('/foo/bar', 'testuser', 'testgroup', 0o644)
350 _ensure_permissions.assert_has_calls(c)
351
352
353@@ -139,19 +143,24 @@
354 super(DirectoryPermissionAuditTestCase, self).setUp()
355
356 @patch('charmhelpers.contrib.hardening.audits.file.os.path.isdir')
357+ @patch.object(file, 'log', lambda *args, **kwargs: None)
358 def test_is_compliant_not_directory(self, mock_isdir):
359 mock_isdir.return_value = False
360- check = file.DirectoryPermissionAudit(paths=['/foo/bar'], user='ubuntu',
361- group='ubuntu', mode=0o0700)
362+ check = file.DirectoryPermissionAudit(paths=['/foo/bar'],
363+ user='testuser',
364+ group='testgroup', mode=0o0700)
365 self.assertRaises(ValueError, check.is_compliant, '/foo/bar')
366
367 @patch.object(file.FilePermissionAudit, 'is_compliant')
368+ @patch.object(file, 'log', lambda *args, **kwargs: None)
369 def test_is_compliant_file_not_compliant(self, mock_is_compliant):
370 mock_is_compliant.return_value = False
371 tmpdir = tempfile.mkdtemp()
372 try:
373- check = file.DirectoryPermissionAudit(paths=[tmpdir], user='ubuntu',
374- group='ubuntu', mode=0o0700)
375+ check = file.DirectoryPermissionAudit(paths=[tmpdir],
376+ user='testuser',
377+ group='testgroup',
378+ mode=0o0700)
379 compliant = check.is_compliant(tmpdir)
380 self.assertFalse(compliant)
381 finally:
382@@ -163,6 +172,7 @@
383 super(NoSUIDGUIDAuditTestCase, self).setUp()
384
385 @patch.object(file.NoSUIDSGIDAudit, '_get_stat')
386+ @patch.object(file, 'log', lambda *args, **kwargs: None)
387 def test_is_compliant(self, mock_get_stat):
388 mock_get_stat.return_value = EasyMock({'st_mode': 0o0644,
389 'st_uid': 0,
390@@ -172,6 +182,7 @@
391 self.assertTrue(compliant)
392
393 @patch.object(file.NoSUIDSGIDAudit, '_get_stat')
394+ @patch.object(file, 'log', lambda *args, **kwargs: None)
395 def test_is_noncompliant(self, mock_get_stat):
396 mock_get_stat.return_value = EasyMock({'st_mode': 0o6644,
397 'st_uid': 0,
398@@ -180,23 +191,26 @@
399 compliant = audit.is_compliant('/foo/bar')
400 self.assertFalse(compliant)
401
402- @patch('charmhelpers.contrib.hardening.audits.file.log')
403- @patch('charmhelpers.contrib.hardening.audits.file.check_output')
404+ @patch.object(file, 'log')
405+ @patch.object(file, 'check_output')
406 def test_comply(self, mock_check_output, mock_log):
407 audit = file.NoSUIDSGIDAudit('/foo/bar')
408 audit.comply('/foo/bar')
409 mock_check_output.assert_has_calls(call(['chmod', '-s', '/foo/bar']))
410+ self.assertTrue(mock_log.called)
411
412
413 class TemplatedFileTestCase(TestCase):
414 def setUp(self):
415 super(TemplatedFileTestCase, self).setUp()
416- self.kv = patch.object(unitdata, 'kv').start()
417+ self.kv = patch.object(unitdata, 'kv')
418+ self.kv.start()
419 self.addCleanup(self.kv.stop)
420
421 @patch.object(file.TemplatedFile, 'templates_match')
422 @patch.object(file.TemplatedFile, 'contents_match')
423 @patch.object(file.TemplatedFile, 'permissions_match')
424+ @patch.object(file, 'log', lambda *args, **kwargs: None)
425 def test_is_not_compliant(self, contents_match_, permissions_match_,
426 templates_match_):
427 contents_match_.return_value = False
428@@ -210,6 +224,7 @@
429 @patch.object(file.TemplatedFile, 'templates_match')
430 @patch.object(file.TemplatedFile, 'contents_match')
431 @patch.object(file.TemplatedFile, 'permissions_match')
432+ @patch.object(file, 'log', lambda *args, **kwargs: None)
433 def test_is_compliant(self, contents_match_, permissions_match_,
434 templates_match_):
435 contents_match_.return_value = True
436@@ -223,6 +238,7 @@
437 @patch.object(file.TemplatedFile, 'templates_match')
438 @patch.object(file.TemplatedFile, 'contents_match')
439 @patch.object(file.TemplatedFile, 'permissions_match')
440+ @patch.object(file, 'log', lambda *args, **kwargs: None)
441 def test_template_changes(self, contents_match_, permissions_match_,
442 templates_match_):
443 contents_match_.return_value = True
444@@ -235,6 +251,7 @@
445
446 @patch.object(file, 'render_and_write')
447 @patch.object(file.utils, 'ensure_permissions')
448+ @patch.object(file, 'log', lambda *args, **kwargs: None)
449 def test_comply(self, mock_ensure_permissions, mock_render_and_write):
450 class Context(object):
451 def __call__(self):
452@@ -263,22 +280,57 @@
453
454 class FileContentAuditTestCase(TestCase):
455
456- def test_audit_contents_pass(self):
457- conditions = {'pass': [r'^KexAlgorithms\s+diffie-hellman-group-exchange-sha256$'],
458- 'fail': [r'^KexAlgorithms\s+diffie-hellman-group-exchange-sha256.+$']}
459- with tempfile.NamedTemporaryFile() as ftmp:
460- with open(ftmp.name, 'w') as fd:
461- fd.write(CONTENTS_PASS)
462-
463- audit = file.FileContentAudit(ftmp.name, conditions)
464- self.assertTrue(audit.is_compliant(ftmp.name))
465-
466- def test_audit_contents_fail(self):
467- conditions = {'pass': [r'^KexAlgorithms\s+diffie-hellman-group-exchange-sha256$'],
468- 'fail': [r'^KexAlgorithms\s+diffie-hellman-group-exchange-sha256.+$']}
469- with tempfile.NamedTemporaryFile() as ftmp:
470- with open(ftmp.name, 'w') as fd:
471- fd.write(CONTENTS_FAIL)
472-
473- audit = file.FileContentAudit(ftmp.name, conditions)
474- self.assertFalse(audit.is_compliant(ftmp.name))
475+ @patch.object(file, 'log')
476+ def test_audit_contents_pass(self, mock_log):
477+ conditions = {'pass':
478+ [r'^KexAlgorithms\s+diffie-hellman-group-exchange-'
479+ 'sha256$'],
480+ 'fail': [r'^KexAlgorithms\s+diffie-hellman-group-'
481+ 'exchange-sha256.+$']}
482+ with tempfile.NamedTemporaryFile() as ftmp:
483+ filename = ftmp.name
484+ with open(filename, 'w') as fd:
485+ fd.write(CONTENTS_FAIL)
486+
487+ audit = file.FileContentAudit(filename, conditions)
488+ self.assertFalse(audit.is_compliant(filename))
489+
490+ calls = [call("Auditing contents of file '%s'" % filename,
491+ level='DEBUG'),
492+ call("Pattern '^KexAlgorithms\\s+diffie-hellman-group-"
493+ "exchange-sha256$' was expected to pass but instead it "
494+ "failed", level='WARNING'),
495+ call("Pattern '^KexAlgorithms\\s+diffie-hellman-group-"
496+ "exchange-sha256.+$' was expected to fail but instead "
497+ "it passed", level='WARNING'),
498+ call('Checked 2 cases and 0 passed', level='DEBUG')]
499+ mock_log.assert_has_calls(calls)
500+
501+ @patch.object(file, 'log')
502+ def test_audit_contents_fail(self, mock_log):
503+ conditions = {'pass':
504+ [r'^KexAlgorithms\s+diffie-hellman-group-exchange-'
505+ 'sha256$'],
506+ 'fail':
507+ [r'^KexAlgorithms\s+diffie-hellman-group-exchange-'
508+ 'sha256.+$']}
509+ with tempfile.NamedTemporaryFile() as ftmp:
510+ filename = ftmp.name
511+ with open(filename, 'w') as fd:
512+ fd.write(CONTENTS_FAIL)
513+
514+ audit = file.FileContentAudit(filename, conditions)
515+ self.assertFalse(audit.is_compliant(filename))
516+
517+ calls = [call("Auditing contents of file '%s'" % filename,
518+ level='DEBUG'),
519+ call("Pattern '^KexAlgorithms\\s+diffie-hellman-group-"
520+ "exchange-sha256$' was expected to pass but instead "
521+ "it failed",
522+ level='WARNING'),
523+ call("Pattern '^KexAlgorithms\\s+diffie-hellman-group-"
524+ "exchange-sha256.+$' was expected to fail but instead "
525+ "it passed",
526+ level='WARNING'),
527+ call('Checked 2 cases and 0 passed', level='DEBUG')]
528+ mock_log.assert_has_calls(calls)
529
530=== modified file 'tests/contrib/hardening/mysql/checks/test_config.py'
531--- tests/contrib/hardening/mysql/checks/test_config.py 2016-03-14 13:49:57 +0000
532+++ tests/contrib/hardening/mysql/checks/test_config.py 2016-03-21 10:39:40 +0000
533@@ -32,4 +32,4 @@
534 })
535 def test_get_audits(self):
536 audits = config.get_audits()
537- self.assertEqual(3, len(audits))
538+ self.assertEqual(4, len(audits))
539
540=== modified file 'tests/contrib/hardening/test_templating.py'
541--- tests/contrib/hardening/test_templating.py 2016-03-14 20:24:18 +0000
542+++ tests/contrib/hardening/test_templating.py 2016-03-21 10:39:40 +0000
543@@ -16,8 +16,9 @@
544
545 import tempfile
546 import os
547+import six
548
549-from mock import patch, MagicMock
550+from mock import call, patch
551 from unittest import TestCase
552
553 os.environ['JUJU_CHARM_DIR'] = '/tmp'
554@@ -85,22 +86,29 @@
555 lambda: {'DISTRIB_CODENAME': 'precise'})
556 @patch.object(utils, 'ensure_permissions')
557 @patch.object(templating, 'write')
558+ @patch('charmhelpers.contrib.hardening.audits.file.log')
559 @patch.object(templating, 'log', lambda *args, **kwargs: None)
560 @patch.object(utils, 'log', lambda *args, **kwargs: None)
561 @patch.object(ssh_config_check, 'log', lambda *args, **kwargs: None)
562- def test_ssh_config_render_and_check_lt_trusty(self, mock_write,
563+ def test_ssh_config_render_and_check_lt_trusty(self, mock_log, mock_write,
564 mock_ensure_permissions):
565 audits = ssh_config_check.get_audits()
566 contentcheckers = self.get_contentcheckers(audits)
567 renderers = self.get_renderers(audits)
568+ configs = {}
569
570 def write(path, data):
571- with tempfile.NamedTemporaryFile(delete=False) as FTMP:
572+ with tempfile.NamedTemporaryFile(delete=False) as ftmp:
573+ if os.path.basename(path) == "ssh_config":
574+ configs['ssh'] = ftmp.name
575+ elif os.path.basename(path) == "sshd_config":
576+ configs['sshd'] = ftmp.name
577+
578 if path in self.pathindex:
579 raise Exception("File already rendered '%s'" % path)
580
581- self.pathindex[path] = FTMP.name
582- with open(FTMP.name, 'w') as fd:
583+ self.pathindex[path] = ftmp.name
584+ with open(ftmp.name, 'wb') as fd:
585 fd.write(data)
586
587 mock_write.side_effect = write
588@@ -112,26 +120,35 @@
589 self.assertEqual('/etc/ssh/sshd_config', args_list[1][0][0])
590 self.assertEqual(mock_write.call_count, 2)
591
592+ calls = [call("Auditing contents of file '%s'" % configs['ssh'],
593+ level='DEBUG'),
594+ call('Checked 10 cases and 10 passed', level='DEBUG'),
595+ call("Auditing contents of file '%s'" % configs['sshd'],
596+ level='DEBUG'),
597+ call('Checked 10 cases and 10 passed', level='DEBUG')]
598+ mock_log.assert_has_calls(calls)
599+
600 @patch.object(ssh_config_check, 'lsb_release',
601 lambda: {'DISTRIB_CODENAME': 'trusty'})
602 @patch.object(utils, 'ensure_permissions')
603 @patch.object(templating, 'write')
604+ @patch('charmhelpers.contrib.hardening.audits.file.log')
605 @patch.object(templating, 'log', lambda *args, **kwargs: None)
606 @patch.object(utils, 'log', lambda *args, **kwargs: None)
607 @patch.object(ssh_config_check, 'log', lambda *args, **kwargs: None)
608- def test_ssh_config_render_and_check_gte_trusty(self, mock_write,
609+ def test_ssh_config_render_and_check_gte_trusty(self, mock_log, mock_write,
610 mock_ensure_permissions):
611 audits = ssh_config_check.get_audits()
612 contentcheckers = self.get_contentcheckers(audits)
613 renderers = self.get_renderers(audits)
614
615 def write(path, data):
616- with tempfile.NamedTemporaryFile(delete=False) as FTMP:
617+ with tempfile.NamedTemporaryFile(delete=False) as ftmp:
618 if path in self.pathindex:
619 raise Exception("File already rendered '%s'" % path)
620
621- self.pathindex[path] = FTMP.name
622- with open(FTMP.name, 'w') as fd:
623+ self.pathindex[path] = ftmp.name
624+ with open(ftmp.name, 'wb') as fd:
625 fd.write(data)
626
627 mock_write.side_effect = write
628@@ -143,6 +160,9 @@
629 self.assertEqual('/etc/ssh/sshd_config', args_list[1][0][0])
630 self.assertEqual(mock_write.call_count, 2)
631
632+ mock_log.assert_has_calls([call('Checked 9 cases and 9 passed',
633+ level='DEBUG')])
634+
635 @patch.object(utils, 'ensure_permissions')
636 @patch.object(templating, 'write')
637 @patch.object(sysctl, 'log', lambda *args, **kwargs: None)
638@@ -157,9 +177,9 @@
639 if path in self.pathindex:
640 raise Exception("File already rendered '%s'" % path)
641
642- with tempfile.NamedTemporaryFile(delete=False) as FTMP:
643- self.pathindex[path] = FTMP.name
644- with open(FTMP.name, 'w') as fd:
645+ with tempfile.NamedTemporaryFile(delete=False) as ftmp:
646+ self.pathindex[path] = ftmp.name
647+ with open(ftmp.name, 'wb') as fd:
648 fd.write(data)
649
650 mock_write.side_effect = write
651@@ -185,9 +205,9 @@
652 if path in self.pathindex:
653 raise Exception("File already rendered '%s'" % path)
654
655- with tempfile.NamedTemporaryFile(delete=False) as FTMP:
656- self.pathindex[path] = FTMP.name
657- with open(FTMP.name, 'w') as fd:
658+ with tempfile.NamedTemporaryFile(delete=False) as ftmp:
659+ self.pathindex[path] = ftmp.name
660+ with open(ftmp.name, 'wb') as fd:
661 fd.write(data)
662
663 mock_write.side_effect = write
664@@ -209,17 +229,17 @@
665 @patch('charmhelpers.contrib.hardening.audits.file.os.path.exists',
666 lambda *a, **kwa: True)
667 @patch.object(apache_config_check, 'subprocess')
668- @patch.object(apache_config_check, 're')
669 @patch.object(utils, 'ensure_permissions')
670 @patch.object(templating, 'write')
671 @patch.object(templating, 'log', lambda *args, **kwargs: None)
672 @patch.object(utils, 'log', lambda *args, **kwargs: None)
673 def test_apache_conf_and_check(self, mock_write, mock_ensure_permissions,
674- mock_re, mock_subprocess):
675- mock_group = MagicMock()
676- mock_group.side_effect = "foo"
677- mock_re.search.return_value = mock_group
678+ mock_subprocess):
679 mock_subprocess.call.return_value = 0
680+ apache_version = """Server version: Apache/2.4.7 (Ubuntu)
681+ Server built: Jan 14 2016 17:45:23
682+ """
683+ mock_subprocess.check_output.return_value = apache_version
684 audits = apache_config_check.get_audits()
685 contentcheckers = self.get_contentcheckers(audits)
686 renderers = self.get_renderers(audits)
687@@ -228,9 +248,9 @@
688 if path in self.pathindex:
689 raise Exception("File already rendered '%s'" % path)
690
691- with tempfile.NamedTemporaryFile(delete=False) as FTMP:
692- self.pathindex[path] = FTMP.name
693- with open(FTMP.name, 'w') as fd:
694+ with tempfile.NamedTemporaryFile(delete=False) as ftmp:
695+ self.pathindex[path] = ftmp.name
696+ with open(ftmp.name, 'wb') as fd:
697 fd.write(data)
698
699 mock_write.side_effect = write
700@@ -266,9 +286,9 @@
701 if path in self.pathindex:
702 raise Exception("File already rendered '%s'" % path)
703
704- with tempfile.NamedTemporaryFile(delete=False) as FTMP:
705- self.pathindex[path] = FTMP.name
706- with open(FTMP.name, 'w') as fd:
707+ with tempfile.NamedTemporaryFile(delete=False) as ftmp:
708+ self.pathindex[path] = ftmp.name
709+ with open(ftmp.name, 'wb') as fd:
710 fd.write(data)
711
712 mock_write.side_effect = write
713@@ -282,7 +302,7 @@
714
715 def tearDown(self):
716 # Cleanup
717- for path in self.pathindex.itervalues():
718+ for path in six.itervalues(self.pathindex):
719 os.remove(path)
720
721 super(TemplatingTestCase, self).tearDown()
722
723=== modified file 'tests/contrib/hardening/test_utils.py'
724--- tests/contrib/hardening/test_utils.py 2016-03-14 13:49:57 +0000
725+++ tests/contrib/hardening/test_utils.py 2016-03-21 10:39:40 +0000
726@@ -58,6 +58,8 @@
727 mock_get_user_provided_overrides.return_value = {}
728 self.assertEqual(utils.__SETTINGS__, {})
729 self.assertTrue('sysctl' in utils.get_settings('os'))
730- self.assertEqual(list(six.iterkeys(utils.__SETTINGS__)), ['os'])
731+ self.assertEqual(sorted(list(six.iterkeys(utils.__SETTINGS__))),
732+ ['os'])
733 self.assertTrue('server' in utils.get_settings('ssh'))
734- self.assertEqual(list(six.iterkeys(utils.__SETTINGS__)), ['os', 'ssh'])
735+ self.assertEqual(sorted(list(six.iterkeys(utils.__SETTINGS__))),
736+ ['os', 'ssh'])

Subscribers

People subscribed via source and target branches