test_verify.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. # -*- coding: utf-8 -*-
  2. '''
  3. Test the verification routines
  4. '''
  5. # Import Python libs
  6. from __future__ import absolute_import, print_function, unicode_literals
  7. import getpass
  8. import os
  9. import sys
  10. import stat
  11. import shutil
  12. import tempfile
  13. import socket
  14. import pytest
  15. # Import third party libs
  16. if sys.platform.startswith('win'):
  17. import win32file
  18. else:
  19. import resource
  20. # Import Salt Testing libs
  21. from tests.support.runtests import RUNTIME_VARS
  22. from tests.support.unit import skipIf, TestCase
  23. from tests.support.helpers import TstSuiteLoggingHandler
  24. from tests.support.mock import MagicMock, patch
  25. # Import salt libs
  26. import salt.utils.files
  27. import salt.utils.platform
  28. from salt.utils.verify import (
  29. check_user,
  30. verify_env,
  31. verify_socket,
  32. zmq_version,
  33. check_max_open_files,
  34. valid_id,
  35. log,
  36. verify_log,
  37. verify_logs_filter,
  38. verify_log_files,
  39. )
  40. # Import 3rd-party libs
  41. from salt.ext import six
  42. from salt.ext.six.moves import range # pylint: disable=import-error,redefined-builtin
  43. class TestVerify(TestCase):
  44. '''
  45. Verify module tests
  46. '''
  47. def test_valid_id_exception_handler(self):
  48. '''
  49. Ensure we just return False if we pass in invalid or undefined paths.
  50. Refs #8259
  51. '''
  52. opts = {'pki_dir': '/tmp/whatever'}
  53. self.assertFalse(valid_id(opts, None))
  54. def test_valid_id_pathsep(self):
  55. '''
  56. Path separators in id should make it invalid
  57. '''
  58. opts = {'pki_dir': '/tmp/whatever'}
  59. # We have to test both path separators because os.path.normpath will
  60. # convert forward slashes to backslashes on Windows.
  61. for pathsep in ('/', '\\'):
  62. self.assertFalse(valid_id(opts, pathsep.join(('..', 'foobar'))))
  63. def test_zmq_verify(self):
  64. self.assertTrue(zmq_version())
  65. def test_zmq_verify_insufficient(self):
  66. import zmq
  67. with patch.object(zmq, '__version__', '2.1.0'):
  68. self.assertFalse(zmq_version())
  69. def test_user(self):
  70. self.assertTrue(check_user(getpass.getuser()))
  71. def test_no_user(self):
  72. # Catch sys.stderr here since no logging is configured and
  73. # check_user WILL write to sys.stderr
  74. class FakeWriter(object):
  75. def __init__(self):
  76. self.output = ""
  77. def write(self, data):
  78. self.output += data
  79. stderr = sys.stderr
  80. writer = FakeWriter()
  81. sys.stderr = writer
  82. # Now run the test
  83. if sys.platform.startswith('win'):
  84. self.assertTrue(check_user('nouser'))
  85. else:
  86. self.assertFalse(check_user('nouser'))
  87. # Restore sys.stderr
  88. sys.stderr = stderr
  89. if writer.output != 'CRITICAL: User not found: "nouser"\n':
  90. # If there's a different error catch, write it to sys.stderr
  91. sys.stderr.write(writer.output)
  92. @skipIf(salt.utils.platform.is_windows(), 'No verify_env Windows')
  93. def test_verify_env(self):
  94. root_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  95. var_dir = os.path.join(root_dir, 'var', 'log', 'salt')
  96. key_dir = os.path.join(root_dir, 'key_dir')
  97. verify_env([var_dir], getpass.getuser(), root_dir=root_dir)
  98. self.assertTrue(os.path.exists(var_dir))
  99. dir_stat = os.stat(var_dir)
  100. self.assertEqual(dir_stat.st_uid, os.getuid())
  101. self.assertEqual(dir_stat.st_mode & stat.S_IRWXU, stat.S_IRWXU)
  102. self.assertEqual(dir_stat.st_mode & stat.S_IRWXG, 40)
  103. self.assertEqual(dir_stat.st_mode & stat.S_IRWXO, 5)
  104. @pytest.mark.requires_network(only_local_network=True)
  105. def test_verify_socket(self):
  106. self.assertTrue(verify_socket('', 18000, 18001))
  107. if socket.has_ipv6:
  108. # Only run if Python is built with IPv6 support; otherwise
  109. # this will just fail.
  110. try:
  111. self.assertTrue(verify_socket('::', 18000, 18001))
  112. except socket.error as serr:
  113. # Python has IPv6 enabled, but the system cannot create
  114. # IPv6 sockets (otherwise the test would return a bool)
  115. # - skip the test
  116. #
  117. # FIXME - possibly emit a message that the system does
  118. # not support IPv6.
  119. pass
  120. def test_max_open_files(self):
  121. with TstSuiteLoggingHandler() as handler:
  122. logmsg_dbg = (
  123. 'DEBUG:This salt-master instance has accepted {0} minion keys.'
  124. )
  125. logmsg_chk = (
  126. '{0}:The number of accepted minion keys({1}) should be lower '
  127. 'than 1/4 of the max open files soft setting({2}). According '
  128. 'to the system\'s hard limit, there\'s still a margin of {3} '
  129. 'to raise the salt\'s max_open_files setting. Please consider '
  130. 'raising this value.'
  131. )
  132. logmsg_crash = (
  133. '{0}:The number of accepted minion keys({1}) should be lower '
  134. 'than 1/4 of the max open files soft setting({2}). '
  135. 'salt-master will crash pretty soon! According to the '
  136. 'system\'s hard limit, there\'s still a margin of {3} to '
  137. 'raise the salt\'s max_open_files setting. Please consider '
  138. 'raising this value.'
  139. )
  140. if sys.platform.startswith('win'):
  141. logmsg_crash = (
  142. '{0}:The number of accepted minion keys({1}) should be lower '
  143. 'than 1/4 of the max open files soft setting({2}). '
  144. 'salt-master will crash pretty soon! Please consider '
  145. 'raising this value.'
  146. )
  147. if sys.platform.startswith('win'):
  148. # Check the Windows API for more detail on this
  149. # http://msdn.microsoft.com/en-us/library/xt874334(v=vs.71).aspx
  150. # and the python binding http://timgolden.me.uk/pywin32-docs/win32file.html
  151. mof_s = mof_h = win32file._getmaxstdio()
  152. else:
  153. mof_s, mof_h = resource.getrlimit(resource.RLIMIT_NOFILE)
  154. tempdir = tempfile.mkdtemp(prefix='fake-keys')
  155. keys_dir = os.path.join(tempdir, 'minions')
  156. os.makedirs(keys_dir)
  157. mof_test = 256
  158. if sys.platform.startswith('win'):
  159. win32file._setmaxstdio(mof_test)
  160. else:
  161. resource.setrlimit(resource.RLIMIT_NOFILE, (mof_test, mof_h))
  162. try:
  163. prev = 0
  164. for newmax, level in ((24, None), (66, 'INFO'),
  165. (127, 'WARNING'), (196, 'CRITICAL')):
  166. for n in range(prev, newmax):
  167. kpath = os.path.join(keys_dir, six.text_type(n))
  168. with salt.utils.files.fopen(kpath, 'w') as fp_:
  169. fp_.write(str(n)) # future lint: disable=blacklisted-function
  170. opts = {
  171. 'max_open_files': newmax,
  172. 'pki_dir': tempdir
  173. }
  174. check_max_open_files(opts)
  175. if level is None:
  176. # No log message is triggered, only the DEBUG one which
  177. # tells us how many minion keys were accepted.
  178. self.assertEqual(
  179. [logmsg_dbg.format(newmax)], handler.messages
  180. )
  181. else:
  182. self.assertIn(
  183. logmsg_dbg.format(newmax), handler.messages
  184. )
  185. self.assertIn(
  186. logmsg_chk.format(
  187. level,
  188. newmax,
  189. mof_test,
  190. mof_test - newmax if sys.platform.startswith('win') else mof_h - newmax,
  191. ),
  192. handler.messages
  193. )
  194. handler.clear()
  195. prev = newmax
  196. newmax = mof_test
  197. for n in range(prev, newmax):
  198. kpath = os.path.join(keys_dir, six.text_type(n))
  199. with salt.utils.files.fopen(kpath, 'w') as fp_:
  200. fp_.write(str(n)) # future lint: disable=blacklisted-function
  201. opts = {
  202. 'max_open_files': newmax,
  203. 'pki_dir': tempdir
  204. }
  205. check_max_open_files(opts)
  206. self.assertIn(logmsg_dbg.format(newmax), handler.messages)
  207. self.assertIn(
  208. logmsg_crash.format(
  209. 'CRITICAL',
  210. newmax,
  211. mof_test,
  212. mof_test - newmax if sys.platform.startswith('win') else mof_h - newmax,
  213. ),
  214. handler.messages
  215. )
  216. handler.clear()
  217. except IOError as err:
  218. if err.errno == 24:
  219. # Too many open files
  220. self.skipTest('We\'ve hit the max open files setting')
  221. raise
  222. finally:
  223. if sys.platform.startswith('win'):
  224. win32file._setmaxstdio(mof_h)
  225. else:
  226. resource.setrlimit(resource.RLIMIT_NOFILE, (mof_s, mof_h))
  227. shutil.rmtree(tempdir)
  228. def test_verify_log(self):
  229. '''
  230. Test that verify_log works as expected
  231. '''
  232. message = 'Insecure logging configuration detected! Sensitive data may be logged.'
  233. mock_cheese = MagicMock()
  234. with patch.object(log, 'warning', mock_cheese):
  235. verify_log({'log_level': 'cheeseshop'})
  236. mock_cheese.assert_called_once_with(message)
  237. mock_trace = MagicMock()
  238. with patch.object(log, 'warning', mock_trace):
  239. verify_log({'log_level': 'trace'})
  240. mock_trace.assert_called_once_with(message)
  241. mock_none = MagicMock()
  242. with patch.object(log, 'warning', mock_none):
  243. verify_log({})
  244. mock_none.assert_called_once_with(message)
  245. mock_info = MagicMock()
  246. with patch.object(log, 'warning', mock_info):
  247. verify_log({'log_level': 'info'})
  248. self.assertTrue(mock_info.call_count == 0)
  249. class TestVerifyLog(TestCase):
  250. def setUp(self):
  251. self.tmpdir = tempfile.mkdtemp()
  252. def tearDown(self):
  253. shutil.rmtree(self.tmpdir)
  254. def test_verify_logs_filter(self):
  255. filtered = verify_logs_filter(
  256. ['udp://foo', 'tcp://bar', '/tmp/foo', 'file://tmp/bar']
  257. )
  258. assert filtered == ['/tmp/foo'], filtered
  259. @skipIf(salt.utils.platform.is_windows(), 'Not applicable on Windows')
  260. def test_verify_log_files_udp_scheme(self):
  261. verify_log_files(['udp://foo'], getpass.getuser())
  262. self.assertFalse(os.path.isdir(os.path.join(os.getcwd(), 'udp:')))
  263. @skipIf(salt.utils.platform.is_windows(), 'Not applicable on Windows')
  264. def test_verify_log_files_tcp_scheme(self):
  265. verify_log_files(['udp://foo'], getpass.getuser())
  266. self.assertFalse(os.path.isdir(os.path.join(os.getcwd(), 'tcp:')))
  267. @skipIf(salt.utils.platform.is_windows(), 'Not applicable on Windows')
  268. def test_verify_log_files_file_scheme(self):
  269. verify_log_files(['file://{}'], getpass.getuser())
  270. self.assertFalse(os.path.isdir(os.path.join(os.getcwd(), 'file:')))
  271. @skipIf(salt.utils.platform.is_windows(), 'Not applicable on Windows')
  272. def test_verify_log_files(self):
  273. path = os.path.join(self.tmpdir, 'foo', 'bar.log')
  274. self.assertFalse(os.path.exists(path))
  275. verify_log_files([path], getpass.getuser())
  276. self.assertTrue(os.path.exists(path))