test_client.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. import logging
  2. import os
  3. import time
  4. import pytest
  5. import salt.config
  6. import salt.netapi
  7. from salt.exceptions import EauthAuthenticationError
  8. from tests.support.case import SSHCase
  9. from tests.support.helpers import (
  10. SKIP_IF_NOT_RUNNING_PYTEST,
  11. SaveRequestsPostHandler,
  12. Webserver,
  13. slowTest,
  14. )
  15. from tests.support.mixins import AdaptedConfigurationTestCaseMixin
  16. from tests.support.mock import patch
  17. from tests.support.runtests import RUNTIME_VARS
  18. from tests.support.unit import TestCase, skipIf
  19. log = logging.getLogger(__name__)
  20. @pytest.mark.usefixtures("salt_master", "salt_sub_minion")
  21. class NetapiClientTest(TestCase):
  22. eauth_creds = {
  23. "username": "saltdev_auto",
  24. "password": "saltdev",
  25. "eauth": "auto",
  26. }
  27. def setUp(self):
  28. """
  29. Set up a NetapiClient instance
  30. """
  31. opts = AdaptedConfigurationTestCaseMixin.get_config("client_config").copy()
  32. self.netapi = salt.netapi.NetapiClient(opts)
  33. def tearDown(self):
  34. del self.netapi
  35. @slowTest
  36. def test_local(self):
  37. low = {"client": "local", "tgt": "*", "fun": "test.ping", "timeout": 300}
  38. low.update(self.eauth_creds)
  39. ret = self.netapi.run(low)
  40. # If --proxy is set, it will cause an extra minion_id to be in the
  41. # response. Since there's not a great way to know if the test
  42. # runner's proxy minion is running, and we're not testing proxy
  43. # minions here anyway, just remove it from the response.
  44. ret.pop("proxytest", None)
  45. self.assertEqual(ret, {"minion": True, "sub_minion": True})
  46. @slowTest
  47. def test_local_batch(self):
  48. low = {"client": "local_batch", "tgt": "*", "fun": "test.ping", "timeout": 300}
  49. low.update(self.eauth_creds)
  50. ret = self.netapi.run(low)
  51. rets = []
  52. for _ret in ret:
  53. rets.append(_ret)
  54. self.assertIn({"sub_minion": True}, rets)
  55. self.assertIn({"minion": True}, rets)
  56. def test_local_async(self):
  57. low = {"client": "local_async", "tgt": "*", "fun": "test.ping"}
  58. low.update(self.eauth_creds)
  59. ret = self.netapi.run(low)
  60. # Remove all the volatile values before doing the compare.
  61. self.assertIn("jid", ret)
  62. ret.pop("jid", None)
  63. ret["minions"] = sorted(ret["minions"])
  64. try:
  65. # If --proxy is set, it will cause an extra minion_id to be in the
  66. # response. Since there's not a great way to know if the test
  67. # runner's proxy minion is running, and we're not testing proxy
  68. # minions here anyway, just remove it from the response.
  69. ret["minions"].remove("proxytest")
  70. except ValueError:
  71. pass
  72. self.assertEqual(ret, {"minions": sorted(["minion", "sub_minion"])})
  73. def test_local_unauthenticated(self):
  74. low = {"client": "local", "tgt": "*", "fun": "test.ping"}
  75. with self.assertRaises(EauthAuthenticationError) as excinfo:
  76. ret = self.netapi.run(low)
  77. @slowTest
  78. def test_wheel(self):
  79. low = {"client": "wheel", "fun": "key.list_all"}
  80. low.update(self.eauth_creds)
  81. ret = self.netapi.run(low)
  82. # Remove all the volatile values before doing the compare.
  83. self.assertIn("tag", ret)
  84. ret.pop("tag")
  85. data = ret.get("data", {})
  86. self.assertIn("jid", data)
  87. data.pop("jid", None)
  88. self.assertIn("tag", data)
  89. data.pop("tag", None)
  90. ret.pop("_stamp", None)
  91. data.pop("_stamp", None)
  92. self.maxDiff = None
  93. self.assertTrue(
  94. {"master.pem", "master.pub"}.issubset(set(ret["data"]["return"]["local"]))
  95. )
  96. @slowTest
  97. def test_wheel_async(self):
  98. # Give this test a little breathing room
  99. time.sleep(3)
  100. low = {"client": "wheel_async", "fun": "key.list_all"}
  101. low.update(self.eauth_creds)
  102. ret = self.netapi.run(low)
  103. self.assertIn("jid", ret)
  104. self.assertIn("tag", ret)
  105. def test_wheel_unauthenticated(self):
  106. low = {"client": "wheel", "tgt": "*", "fun": "test.ping"}
  107. with self.assertRaises(EauthAuthenticationError) as excinfo:
  108. ret = self.netapi.run(low)
  109. @skipIf(True, "This is not testing anything. Skipping for now.")
  110. def test_runner(self):
  111. # TODO: fix race condition in init of event-- right now the event class
  112. # will finish init even if the underlying zmq socket hasn't connected yet
  113. # this is problematic for the runnerclient's master_call method if the
  114. # runner is quick
  115. # low = {'client': 'runner', 'fun': 'cache.grains'}
  116. low = {"client": "runner", "fun": "test.sleep", "arg": [2]}
  117. low.update(self.eauth_creds)
  118. ret = self.netapi.run(low)
  119. @skipIf(True, "This is not testing anything. Skipping for now.")
  120. def test_runner_async(self):
  121. low = {"client": "runner", "fun": "cache.grains"}
  122. low.update(self.eauth_creds)
  123. ret = self.netapi.run(low)
  124. def test_runner_unauthenticated(self):
  125. low = {"client": "runner", "tgt": "*", "fun": "test.ping"}
  126. with self.assertRaises(EauthAuthenticationError) as excinfo:
  127. ret = self.netapi.run(low)
  128. @SKIP_IF_NOT_RUNNING_PYTEST
  129. @pytest.mark.requires_sshd_server
  130. class NetapiSSHClientTest(SSHCase):
  131. eauth_creds = {
  132. "username": "saltdev_auto",
  133. "password": "saltdev",
  134. "eauth": "auto",
  135. }
  136. def setUp(self):
  137. """
  138. Set up a NetapiClient instance
  139. """
  140. opts = AdaptedConfigurationTestCaseMixin.get_config("client_config").copy()
  141. self.netapi = salt.netapi.NetapiClient(opts)
  142. self.priv_file = os.path.join(RUNTIME_VARS.TMP_SSH_CONF_DIR, "client_key")
  143. self.rosters = os.path.join(RUNTIME_VARS.TMP_CONF_DIR)
  144. # Initialize salt-ssh
  145. self.run_function("test.ping")
  146. def tearDown(self):
  147. del self.netapi
  148. @classmethod
  149. def setUpClass(cls):
  150. cls.post_webserver = Webserver(handler=SaveRequestsPostHandler)
  151. cls.post_webserver.start()
  152. cls.post_web_root = cls.post_webserver.web_root
  153. cls.post_web_handler = cls.post_webserver.handler
  154. @classmethod
  155. def tearDownClass(cls):
  156. cls.post_webserver.stop()
  157. del cls.post_webserver
  158. @slowTest
  159. def test_ssh(self):
  160. low = {
  161. "client": "ssh",
  162. "tgt": "localhost",
  163. "fun": "test.ping",
  164. "ignore_host_keys": True,
  165. "roster_file": "roster",
  166. "rosters": [self.rosters],
  167. "ssh_priv": self.priv_file,
  168. }
  169. low.update(self.eauth_creds)
  170. ret = self.netapi.run(low)
  171. self.assertIn("localhost", ret)
  172. self.assertIn("return", ret["localhost"])
  173. self.assertEqual(ret["localhost"]["return"], True)
  174. self.assertEqual(ret["localhost"]["id"], "localhost")
  175. self.assertEqual(ret["localhost"]["fun"], "test.ping")
  176. @slowTest
  177. def test_ssh_unauthenticated(self):
  178. low = {"client": "ssh", "tgt": "localhost", "fun": "test.ping"}
  179. with self.assertRaises(EauthAuthenticationError) as excinfo:
  180. ret = self.netapi.run(low)
  181. @slowTest
  182. def test_ssh_unauthenticated_raw_shell_curl(self):
  183. fun = "-o ProxyCommand curl {}".format(self.post_web_root)
  184. low = {"client": "ssh", "tgt": "localhost", "fun": fun, "raw_shell": True}
  185. ret = None
  186. with self.assertRaises(EauthAuthenticationError) as excinfo:
  187. ret = self.netapi.run(low)
  188. self.assertEqual(self.post_web_handler.received_requests, [])
  189. self.assertEqual(ret, None)
  190. @slowTest
  191. def test_ssh_unauthenticated_raw_shell_touch(self):
  192. badfile = os.path.join(RUNTIME_VARS.TMP, "badfile.txt")
  193. fun = "-o ProxyCommand touch {}".format(badfile)
  194. low = {"client": "ssh", "tgt": "localhost", "fun": fun, "raw_shell": True}
  195. ret = None
  196. with self.assertRaises(EauthAuthenticationError) as excinfo:
  197. ret = self.netapi.run(low)
  198. self.assertEqual(ret, None)
  199. self.assertFalse(os.path.exists("badfile.txt"))
  200. @slowTest
  201. def test_ssh_authenticated_raw_shell_disabled(self):
  202. badfile = os.path.join(RUNTIME_VARS.TMP, "badfile.txt")
  203. fun = "-o ProxyCommand touch {}".format(badfile)
  204. low = {"client": "ssh", "tgt": "localhost", "fun": fun, "raw_shell": True}
  205. low.update(self.eauth_creds)
  206. ret = None
  207. with patch.dict(self.netapi.opts, {"netapi_allow_raw_shell": False}):
  208. with self.assertRaises(EauthAuthenticationError) as excinfo:
  209. ret = self.netapi.run(low)
  210. self.assertEqual(ret, None)
  211. self.assertFalse(os.path.exists("badfile.txt"))