1
0

test_cmdmod.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. # -*- coding: utf-8 -*-
  2. from __future__ import absolute_import, print_function, unicode_literals
  3. import os
  4. import sys
  5. import tempfile
  6. from contextlib import contextmanager
  7. import pytest
  8. import salt.utils.path
  9. import salt.utils.platform
  10. import salt.utils.user
  11. from salt.ext import six
  12. from tests.support.case import ModuleCase
  13. from tests.support.helpers import (
  14. dedent,
  15. destructiveTest,
  16. skip_if_binaries_missing,
  17. skip_if_not_root,
  18. slowTest,
  19. )
  20. from tests.support.runtests import RUNTIME_VARS
  21. from tests.support.unit import skipIf
  22. AVAILABLE_PYTHON_EXECUTABLE = salt.utils.path.which_bin(
  23. ["python", "python2", "python2.6", "python2.7"]
  24. )
  25. @pytest.mark.windows_whitelisted
  26. class CMDModuleTest(ModuleCase):
  27. """
  28. Validate the cmd module
  29. """
  30. def setUp(self):
  31. self.runas_usr = "nobody"
  32. if salt.utils.platform.is_darwin():
  33. self.runas_usr = "macsalttest"
  34. @contextmanager
  35. def _ensure_user_exists(self, name):
  36. if name in self.run_function("user.info", [name]).values():
  37. # User already exists; don't touch
  38. yield
  39. else:
  40. # Need to create user for test
  41. self.run_function("user.add", [name])
  42. try:
  43. yield
  44. finally:
  45. self.run_function("user.delete", [name], remove=True)
  46. @slowTest
  47. def test_run(self):
  48. """
  49. cmd.run
  50. """
  51. shell = os.environ.get("SHELL")
  52. if shell is None:
  53. # Failed to get the SHELL var, don't run
  54. self.skipTest("Unable to get the SHELL environment variable")
  55. self.assertTrue(self.run_function("cmd.run", ["echo $SHELL"]))
  56. self.assertEqual(
  57. self.run_function(
  58. "cmd.run", ["echo $SHELL", "shell={0}".format(shell)], python_shell=True
  59. ).rstrip(),
  60. shell,
  61. )
  62. self.assertEqual(
  63. self.run_function("cmd.run", ["ls / | grep etc"], python_shell=True), "etc"
  64. )
  65. self.assertEqual(
  66. self.run_function(
  67. "cmd.run",
  68. ['echo {{grains.id}} | awk "{print $1}"'],
  69. template="jinja",
  70. python_shell=True,
  71. ),
  72. "minion",
  73. )
  74. self.assertEqual(
  75. self.run_function(
  76. "cmd.run", ["grep f"], stdin="one\ntwo\nthree\nfour\nfive\n"
  77. ),
  78. "four\nfive",
  79. )
  80. self.assertEqual(
  81. self.run_function(
  82. "cmd.run", ['echo "a=b" | sed -e s/=/:/g'], python_shell=True
  83. ),
  84. "a:b",
  85. )
  86. @slowTest
  87. def test_stdout(self):
  88. """
  89. cmd.run_stdout
  90. """
  91. self.assertEqual(
  92. self.run_function("cmd.run_stdout", ['echo "cheese"']).rstrip(),
  93. "cheese" if not salt.utils.platform.is_windows() else '"cheese"',
  94. )
  95. @slowTest
  96. def test_stderr(self):
  97. """
  98. cmd.run_stderr
  99. """
  100. if sys.platform.startswith(("freebsd", "openbsd")):
  101. shell = "/bin/sh"
  102. else:
  103. shell = "/bin/bash"
  104. self.assertEqual(
  105. self.run_function(
  106. "cmd.run_stderr",
  107. ['echo "cheese" 1>&2', "shell={0}".format(shell)],
  108. python_shell=True,
  109. ).rstrip(),
  110. "cheese" if not salt.utils.platform.is_windows() else '"cheese"',
  111. )
  112. @slowTest
  113. def test_run_all(self):
  114. """
  115. cmd.run_all
  116. """
  117. if sys.platform.startswith(("freebsd", "openbsd")):
  118. shell = "/bin/sh"
  119. else:
  120. shell = "/bin/bash"
  121. ret = self.run_function(
  122. "cmd.run_all",
  123. ['echo "cheese" 1>&2', "shell={0}".format(shell)],
  124. python_shell=True,
  125. )
  126. self.assertTrue("pid" in ret)
  127. self.assertTrue("retcode" in ret)
  128. self.assertTrue("stdout" in ret)
  129. self.assertTrue("stderr" in ret)
  130. self.assertTrue(isinstance(ret.get("pid"), int))
  131. self.assertTrue(isinstance(ret.get("retcode"), int))
  132. self.assertTrue(isinstance(ret.get("stdout"), six.string_types))
  133. self.assertTrue(isinstance(ret.get("stderr"), six.string_types))
  134. self.assertEqual(
  135. ret.get("stderr").rstrip(),
  136. "cheese" if not salt.utils.platform.is_windows() else '"cheese"',
  137. )
  138. @slowTest
  139. def test_retcode(self):
  140. """
  141. cmd.retcode
  142. """
  143. self.assertEqual(
  144. self.run_function("cmd.retcode", ["exit 0"], python_shell=True), 0
  145. )
  146. self.assertEqual(
  147. self.run_function("cmd.retcode", ["exit 1"], python_shell=True), 1
  148. )
  149. @slowTest
  150. def test_run_all_with_success_retcodes(self):
  151. """
  152. cmd.run with success_retcodes
  153. """
  154. ret = self.run_function(
  155. "cmd.run_all", ["exit 42"], success_retcodes=[42], python_shell=True
  156. )
  157. self.assertTrue("retcode" in ret)
  158. self.assertEqual(ret.get("retcode"), 0)
  159. @slowTest
  160. def test_retcode_with_success_retcodes(self):
  161. """
  162. cmd.run with success_retcodes
  163. """
  164. ret = self.run_function(
  165. "cmd.retcode", ["exit 42"], success_retcodes=[42], python_shell=True
  166. )
  167. self.assertEqual(ret, 0)
  168. @slowTest
  169. def test_blacklist_glob(self):
  170. """
  171. cmd_blacklist_glob
  172. """
  173. self.assertEqual(
  174. self.run_function("cmd.run", ["bad_command --foo"]).rstrip(),
  175. 'ERROR: The shell command "bad_command --foo" is not permitted',
  176. )
  177. @slowTest
  178. def test_script(self):
  179. """
  180. cmd.script
  181. """
  182. args = "saltines crackers biscuits=yes"
  183. script = "salt://script.py"
  184. ret = self.run_function("cmd.script", [script, args])
  185. self.assertEqual(ret["stdout"], args)
  186. @slowTest
  187. def test_script_retcode(self):
  188. """
  189. cmd.script_retcode
  190. """
  191. script = "salt://script.py"
  192. ret = self.run_function("cmd.script_retcode", [script])
  193. self.assertEqual(ret, 0)
  194. @slowTest
  195. def test_script_cwd(self):
  196. """
  197. cmd.script with cwd
  198. """
  199. tmp_cwd = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  200. args = "saltines crackers biscuits=yes"
  201. script = "salt://script.py"
  202. ret = self.run_function("cmd.script", [script, args], cwd=tmp_cwd)
  203. self.assertEqual(ret["stdout"], args)
  204. @slowTest
  205. def test_script_cwd_with_space(self):
  206. """
  207. cmd.script with cwd
  208. """
  209. tmp_cwd = "{0}{1}test 2".format(
  210. tempfile.mkdtemp(dir=RUNTIME_VARS.TMP), os.path.sep
  211. )
  212. os.mkdir(tmp_cwd)
  213. args = "saltines crackers biscuits=yes"
  214. script = "salt://script.py"
  215. ret = self.run_function("cmd.script", [script, args], cwd=tmp_cwd)
  216. self.assertEqual(ret["stdout"], args)
  217. @destructiveTest
  218. def test_tty(self):
  219. """
  220. cmd.tty
  221. """
  222. for tty in ("tty0", "pts3"):
  223. if os.path.exists(os.path.join("/dev", tty)):
  224. ret = self.run_function("cmd.tty", [tty, "apply salt liberally"])
  225. self.assertTrue("Success" in ret)
  226. @skip_if_binaries_missing(["which"])
  227. def test_which(self):
  228. """
  229. cmd.which
  230. """
  231. cmd_which = self.run_function("cmd.which", ["cat"])
  232. self.assertIsInstance(cmd_which, str)
  233. cmd_run = self.run_function("cmd.run", ["which cat"])
  234. self.assertIsInstance(cmd_run, str)
  235. self.assertEqual(cmd_which.rstrip(), cmd_run.rstrip())
  236. @skip_if_binaries_missing(["which"])
  237. def test_which_bin(self):
  238. """
  239. cmd.which_bin
  240. """
  241. cmds = ["pip3", "pip2", "pip", "pip-python"]
  242. ret = self.run_function("cmd.which_bin", [cmds])
  243. self.assertTrue(os.path.split(ret)[1] in cmds)
  244. @slowTest
  245. def test_has_exec(self):
  246. """
  247. cmd.has_exec
  248. """
  249. self.assertTrue(
  250. self.run_function("cmd.has_exec", [AVAILABLE_PYTHON_EXECUTABLE])
  251. )
  252. self.assertFalse(
  253. self.run_function("cmd.has_exec", ["alllfsdfnwieulrrh9123857ygf"])
  254. )
  255. @slowTest
  256. def test_exec_code(self):
  257. """
  258. cmd.exec_code
  259. """
  260. code = dedent(
  261. """
  262. import sys
  263. sys.stdout.write('cheese')
  264. """
  265. )
  266. self.assertEqual(
  267. self.run_function(
  268. "cmd.exec_code", [AVAILABLE_PYTHON_EXECUTABLE, code]
  269. ).rstrip(),
  270. "cheese",
  271. )
  272. @slowTest
  273. def test_exec_code_with_single_arg(self):
  274. """
  275. cmd.exec_code
  276. """
  277. code = dedent(
  278. """
  279. import sys
  280. sys.stdout.write(sys.argv[1])
  281. """
  282. )
  283. arg = "cheese"
  284. self.assertEqual(
  285. self.run_function(
  286. "cmd.exec_code", [AVAILABLE_PYTHON_EXECUTABLE, code], args=arg
  287. ).rstrip(),
  288. arg,
  289. )
  290. @slowTest
  291. def test_exec_code_with_multiple_args(self):
  292. """
  293. cmd.exec_code
  294. """
  295. code = dedent(
  296. """
  297. import sys
  298. sys.stdout.write(sys.argv[1])
  299. """
  300. )
  301. arg = "cheese"
  302. self.assertEqual(
  303. self.run_function(
  304. "cmd.exec_code", [AVAILABLE_PYTHON_EXECUTABLE, code], args=[arg, "test"]
  305. ).rstrip(),
  306. arg,
  307. )
  308. @slowTest
  309. def test_quotes(self):
  310. """
  311. cmd.run with quoted command
  312. """
  313. cmd = """echo 'SELECT * FROM foo WHERE bar="baz"' """
  314. expected_result = 'SELECT * FROM foo WHERE bar="baz"'
  315. if salt.utils.platform.is_windows():
  316. expected_result = "'SELECT * FROM foo WHERE bar=\"baz\"'"
  317. result = self.run_function("cmd.run_stdout", [cmd]).strip()
  318. self.assertEqual(result, expected_result)
  319. @skip_if_not_root
  320. @skipIf(salt.utils.platform.is_windows(), "skip windows, requires password")
  321. def test_quotes_runas(self):
  322. """
  323. cmd.run with quoted command
  324. """
  325. cmd = """echo 'SELECT * FROM foo WHERE bar="baz"' """
  326. expected_result = 'SELECT * FROM foo WHERE bar="baz"'
  327. result = self.run_function(
  328. "cmd.run_all", [cmd], runas=RUNTIME_VARS.RUNNING_TESTS_USER
  329. )
  330. errmsg = "The command returned: {}".format(result)
  331. self.assertEqual(result["retcode"], 0, errmsg)
  332. self.assertEqual(result["stdout"], expected_result, errmsg)
  333. @destructiveTest
  334. @skip_if_not_root
  335. @skipIf(salt.utils.platform.is_windows(), "skip windows, uses unix commands")
  336. @slowTest
  337. def test_avoid_injecting_shell_code_as_root(self):
  338. """
  339. cmd.run should execute the whole command as the "runas" user, not
  340. running substitutions as root.
  341. """
  342. cmd = "echo $(id -u)"
  343. root_id = self.run_function("cmd.run_stdout", [cmd])
  344. runas_root_id = self.run_function(
  345. "cmd.run_stdout", [cmd], runas=RUNTIME_VARS.RUNNING_TESTS_USER
  346. )
  347. with self._ensure_user_exists(self.runas_usr):
  348. user_id = self.run_function("cmd.run_stdout", [cmd], runas=self.runas_usr)
  349. self.assertNotEqual(user_id, root_id)
  350. self.assertNotEqual(user_id, runas_root_id)
  351. self.assertEqual(root_id, runas_root_id)
  352. @destructiveTest
  353. @skip_if_not_root
  354. @skipIf(salt.utils.platform.is_windows(), "skip windows, uses unix commands")
  355. @slowTest
  356. def test_cwd_runas(self):
  357. """
  358. cmd.run should be able to change working directory correctly, whether
  359. or not runas is in use.
  360. """
  361. cmd = "pwd"
  362. tmp_cwd = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  363. os.chmod(tmp_cwd, 0o711)
  364. cwd_normal = self.run_function("cmd.run_stdout", [cmd], cwd=tmp_cwd).rstrip(
  365. "\n"
  366. )
  367. self.assertEqual(tmp_cwd, cwd_normal)
  368. with self._ensure_user_exists(self.runas_usr):
  369. cwd_runas = self.run_function(
  370. "cmd.run_stdout", [cmd], cwd=tmp_cwd, runas=self.runas_usr
  371. ).rstrip("\n")
  372. self.assertEqual(tmp_cwd, cwd_runas)
  373. @destructiveTest
  374. @skip_if_not_root
  375. @skipIf(not salt.utils.platform.is_darwin(), "applicable to MacOS only")
  376. @slowTest
  377. def test_runas_env(self):
  378. """
  379. cmd.run should be able to change working directory correctly, whether
  380. or not runas is in use.
  381. """
  382. with self._ensure_user_exists(self.runas_usr):
  383. user_path = self.run_function(
  384. "cmd.run_stdout", ['printf %s "$PATH"'], runas=self.runas_usr
  385. )
  386. # XXX: Not sure of a better way. Environment starts out with
  387. # /bin:/usr/bin and should be populated by path helper and the bash
  388. # profile.
  389. self.assertNotEqual("/bin:/usr/bin", user_path)
  390. @destructiveTest
  391. @skip_if_not_root
  392. @skipIf(not salt.utils.platform.is_darwin(), "applicable to MacOS only")
  393. @slowTest
  394. def test_runas_complex_command_bad_cwd(self):
  395. """
  396. cmd.run should not accidentally run parts of a complex command when
  397. given a cwd which cannot be used by the user the command is run as.
  398. Due to the need to use `su -l` to login to another user on MacOS, we
  399. cannot cd into directories that the target user themselves does not
  400. have execute permission for. To an extent, this test is testing that
  401. buggy behaviour, but its purpose is to ensure that the greater bug of
  402. running commands after failing to cd does not occur.
  403. """
  404. tmp_cwd = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  405. os.chmod(tmp_cwd, 0o700)
  406. with self._ensure_user_exists(self.runas_usr):
  407. cmd_result = self.run_function(
  408. "cmd.run_all",
  409. ['pwd; pwd; : $(echo "You have failed the test" >&2)'],
  410. cwd=tmp_cwd,
  411. runas=self.runas_usr,
  412. )
  413. self.assertEqual("", cmd_result["stdout"])
  414. self.assertNotIn("You have failed the test", cmd_result["stderr"])
  415. self.assertNotEqual(0, cmd_result["retcode"])
  416. @skipIf(salt.utils.platform.is_windows(), "minion is windows")
  417. @skip_if_not_root
  418. @destructiveTest
  419. @slowTest
  420. def test_runas(self):
  421. """
  422. Ensure that the env is the runas user's
  423. """
  424. with self._ensure_user_exists(self.runas_usr):
  425. out = self.run_function(
  426. "cmd.run", ["env"], runas=self.runas_usr
  427. ).splitlines()
  428. self.assertIn("USER={0}".format(self.runas_usr), out)
  429. @skipIf(not salt.utils.path.which_bin("sleep"), "sleep cmd not installed")
  430. def test_timeout(self):
  431. """
  432. cmd.run trigger timeout
  433. """
  434. out = self.run_function(
  435. "cmd.run", ["sleep 2 && echo hello"], f_timeout=1, python_shell=True
  436. )
  437. self.assertTrue("Timed out" in out)
  438. @skipIf(not salt.utils.path.which_bin("sleep"), "sleep cmd not installed")
  439. def test_timeout_success(self):
  440. """
  441. cmd.run sufficient timeout to succeed
  442. """
  443. out = self.run_function(
  444. "cmd.run", ["sleep 1 && echo hello"], f_timeout=2, python_shell=True
  445. )
  446. self.assertEqual(out, "hello")
  447. @slowTest
  448. def test_hide_output(self):
  449. """
  450. Test the hide_output argument
  451. """
  452. ls_command = (
  453. ["ls", "/"] if not salt.utils.platform.is_windows() else ["dir", "c:\\"]
  454. )
  455. error_command = ["thiscommanddoesnotexist"]
  456. # cmd.run
  457. out = self.run_function("cmd.run", ls_command, hide_output=True)
  458. self.assertEqual(out, "")
  459. # cmd.shell
  460. out = self.run_function("cmd.shell", ls_command, hide_output=True)
  461. self.assertEqual(out, "")
  462. # cmd.run_stdout
  463. out = self.run_function("cmd.run_stdout", ls_command, hide_output=True)
  464. self.assertEqual(out, "")
  465. # cmd.run_stderr
  466. out = self.run_function("cmd.shell", error_command, hide_output=True)
  467. self.assertEqual(out, "")
  468. # cmd.run_all (command should have produced stdout)
  469. out = self.run_function("cmd.run_all", ls_command, hide_output=True)
  470. self.assertEqual(out["stdout"], "")
  471. self.assertEqual(out["stderr"], "")
  472. # cmd.run_all (command should have produced stderr)
  473. out = self.run_function("cmd.run_all", error_command, hide_output=True)
  474. self.assertEqual(out["stdout"], "")
  475. self.assertEqual(out["stderr"], "")
  476. @slowTest
  477. def test_cmd_run_whoami(self):
  478. """
  479. test return of whoami
  480. """
  481. if not salt.utils.platform.is_windows():
  482. user = RUNTIME_VARS.RUNTIME_CONFIGS["master"]["user"]
  483. else:
  484. user = salt.utils.user.get_specific_user()
  485. if user.startswith("sudo_"):
  486. user = user.replace("sudo_", "")
  487. cmd = self.run_function("cmd.run", ["whoami"])
  488. self.assertEqual(user.lower(), cmd.lower())
  489. @skipIf(not salt.utils.platform.is_windows(), "minion is not windows")
  490. @slowTest
  491. def test_windows_env_handling(self):
  492. """
  493. Ensure that nt.environ is used properly with cmd.run*
  494. """
  495. out = self.run_function(
  496. "cmd.run", ["set"], env={"abc": "123", "ABC": "456"}
  497. ).splitlines()
  498. self.assertIn("abc=123", out)
  499. self.assertIn("ABC=456", out)
  500. @skipIf(not salt.utils.platform.is_windows(), "minion is not windows")
  501. def test_windows_cmd_powershell_list(self):
  502. """
  503. Ensure that cmd.run_all supports running shell='cmd' with cmd passed
  504. as a list
  505. """
  506. out = self.run_function(
  507. "cmd.run_all", cmd=["echo", "salt"], python_shell=False, shell="powershell"
  508. )
  509. self.assertEqual(out["stdout"], "salt")
  510. @skipIf(not salt.utils.platform.is_windows(), "minion is not windows")
  511. def test_windows_cmd_powershell_string(self):
  512. """
  513. Ensure that cmd.run_all supports running shell='cmd' with cmd passed
  514. as a string
  515. """
  516. out = self.run_function(
  517. "cmd.run_all", cmd="echo salt", python_shell=False, shell="powershell"
  518. )
  519. self.assertEqual(out["stdout"], "salt")
  520. @slowTest
  521. @skipIf(not salt.utils.platform.is_windows(), "minion is not windows")
  522. def test_windows_powershell_script_args(self):
  523. """
  524. Ensure that powershell processes inline script in args
  525. """
  526. val = "i like cheese"
  527. args = '-SecureString (ConvertTo-SecureString -String "{0}" -AsPlainText -Force) -ErrorAction Stop'.format(
  528. val
  529. )
  530. script = "salt://issue-56195/test.ps1"
  531. ret = self.run_function("cmd.script", [script], args=args, shell="powershell")
  532. self.assertEqual(ret["stdout"], val)