test_verify.py 10 KB

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