1
0

test_verify.py 12 KB

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