1
0

test_ssh.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. # -*- coding: utf-8 -*-
  2. '''
  3. :codeauthor: Pedro Algarvio (pedro@algarvio.me)
  4. tests.unit.config.schemas.test_ssh
  5. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  6. '''
  7. # Import python libs
  8. from __future__ import absolute_import, print_function, unicode_literals
  9. # Import Salt Testing Libs
  10. from tests.support.unit import TestCase, skipIf
  11. # Import Salt Libs
  12. from salt.config.schemas import ssh as ssh_schemas
  13. from salt.config.schemas.minion import MinionConfiguration
  14. import salt.utils.stringutils
  15. from salt.utils.versions import LooseVersion as _LooseVersion
  16. # Import 3rd-party libs
  17. try:
  18. import jsonschema
  19. import jsonschema.exceptions
  20. HAS_JSONSCHEMA = True
  21. JSONSCHEMA_VERSION = _LooseVersion(jsonschema.__version__)
  22. except ImportError:
  23. HAS_JSONSCHEMA = False
  24. JSONSCHEMA_VERSION = _LooseVersion('0')
  25. class RosterEntryConfigTest(TestCase):
  26. def test_config(self):
  27. config = ssh_schemas.RosterEntryConfig()
  28. expected = {
  29. '$schema': 'http://json-schema.org/draft-04/schema#',
  30. 'title': 'Roster Entry',
  31. 'description': 'Salt SSH roster entry definition',
  32. 'type': 'object',
  33. 'properties': {
  34. 'host': {
  35. 'title': 'Host',
  36. 'description': 'The IP address or DNS name of the remote host',
  37. 'type': 'string',
  38. 'pattern': r'^((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|([A-Za-z0-9][A-Za-z0-9\.\-]{1,255}))$',
  39. 'minLength': 1
  40. },
  41. 'port': {
  42. 'description': 'The target system\'s ssh port number',
  43. 'title': 'Port',
  44. 'default': 22,
  45. 'maximum': 65535,
  46. 'minimum': 0,
  47. 'type': 'integer'
  48. },
  49. 'user': {
  50. 'default': 'root',
  51. 'type': 'string',
  52. 'description': 'The user to log in as. Defaults to root',
  53. 'title': 'User',
  54. 'minLength': 1
  55. },
  56. 'passwd': {
  57. 'title': 'Password',
  58. 'type': 'string',
  59. 'description': 'The password to log in with',
  60. 'format': 'secret',
  61. 'minLength': 1
  62. },
  63. 'priv': {
  64. 'type': 'string',
  65. 'description': 'File path to ssh private key, defaults to salt-ssh.rsa',
  66. 'title': 'Private Key',
  67. 'minLength': 1
  68. },
  69. 'priv_passwd': {
  70. 'type': 'string',
  71. 'description': 'Passphrase for private key file',
  72. 'title': 'Private Key passphrase',
  73. 'format': 'secret',
  74. 'minLength': 1,
  75. },
  76. 'sudo': {
  77. 'default': False,
  78. 'type': 'boolean',
  79. 'description': 'run command via sudo. Defaults to False',
  80. 'title': 'Sudo'
  81. },
  82. 'timeout': {
  83. 'type': 'integer',
  84. 'description': 'Number of seconds to wait for response when establishing an SSH connection',
  85. 'title': 'Timeout'
  86. },
  87. 'thin_dir': {
  88. 'type': 'string',
  89. 'description': 'The target system\'s storage directory for Salt components. Defaults to /tmp/salt-<hash>.',
  90. 'title': 'Thin Directory'
  91. },
  92. # The actuall representation of the minion options would make this HUGE!
  93. 'minion_opts': ssh_schemas.DictItem(title='Minion Options',
  94. description='Dictionary of minion options',
  95. properties=MinionConfiguration()).serialize(),
  96. },
  97. 'anyOf': [
  98. {
  99. 'required': [
  100. 'passwd'
  101. ]
  102. },
  103. {
  104. 'required': [
  105. 'priv'
  106. ]
  107. }
  108. ],
  109. 'required': [
  110. 'host',
  111. 'user',
  112. ],
  113. 'x-ordering': [
  114. 'host',
  115. 'port',
  116. 'user',
  117. 'passwd',
  118. 'priv',
  119. 'priv_passwd',
  120. 'sudo',
  121. 'timeout',
  122. 'thin_dir',
  123. 'minion_opts'
  124. ],
  125. 'additionalProperties': False
  126. }
  127. try:
  128. self.assertDictContainsSubset(expected['properties'], config.serialize()['properties'])
  129. self.assertDictContainsSubset(expected, config.serialize())
  130. except AssertionError:
  131. import salt.utils.json
  132. print(salt.utils.json.dumps(config.serialize(), indent=4))
  133. raise
  134. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  135. def test_config_validate(self):
  136. try:
  137. jsonschema.validate(
  138. {
  139. 'host': 'localhost',
  140. 'user': 'root',
  141. 'passwd': 'foo'
  142. },
  143. ssh_schemas.RosterEntryConfig.serialize(),
  144. format_checker=jsonschema.FormatChecker()
  145. )
  146. except jsonschema.exceptions.ValidationError as exc:
  147. self.fail('ValidationError raised: {0}'.format(exc))
  148. try:
  149. jsonschema.validate(
  150. {
  151. 'host': '127.0.0.1',
  152. 'user': 'root',
  153. 'passwd': 'foo'
  154. },
  155. ssh_schemas.RosterEntryConfig.serialize(),
  156. format_checker=jsonschema.FormatChecker()
  157. )
  158. except jsonschema.exceptions.ValidationError as exc:
  159. self.fail('ValidationError raised: {0}'.format(exc))
  160. try:
  161. jsonschema.validate(
  162. {
  163. 'host': '127.1.0.1',
  164. 'user': 'root',
  165. 'priv': 'foo',
  166. 'passwd': 'foo'
  167. },
  168. ssh_schemas.RosterEntryConfig.serialize(),
  169. format_checker=jsonschema.FormatChecker()
  170. )
  171. except jsonschema.exceptions.ValidationError as exc:
  172. self.fail('ValidationError raised: {0}'.format(exc))
  173. try:
  174. jsonschema.validate(
  175. {
  176. 'host': '127.1.0.1',
  177. 'user': 'root',
  178. 'passwd': 'foo',
  179. 'sudo': False
  180. },
  181. ssh_schemas.RosterEntryConfig.serialize(),
  182. format_checker=jsonschema.FormatChecker()
  183. )
  184. except jsonschema.exceptions.ValidationError as exc:
  185. self.fail('ValidationError raised: {0}'.format(exc))
  186. try:
  187. jsonschema.validate(
  188. {
  189. 'host': '127.1.0.1',
  190. 'user': 'root',
  191. 'priv': 'foo',
  192. 'passwd': 'foo',
  193. 'thin_dir': '/foo/bar'
  194. },
  195. ssh_schemas.RosterEntryConfig.serialize(),
  196. format_checker=jsonschema.FormatChecker()
  197. )
  198. except jsonschema.exceptions.ValidationError as exc:
  199. self.fail('ValidationError raised: {0}'.format(exc))
  200. try:
  201. jsonschema.validate(
  202. {
  203. 'host': '127.1.0.1',
  204. 'user': 'root',
  205. 'passwd': 'foo',
  206. 'minion_opts': {
  207. 'interface': '0.0.0.0'
  208. }
  209. },
  210. ssh_schemas.RosterEntryConfig.serialize(),
  211. format_checker=jsonschema.FormatChecker()
  212. )
  213. except jsonschema.exceptions.ValidationError as exc:
  214. self.fail('ValidationError raised: {0}'.format(exc))
  215. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  216. jsonschema.validate(
  217. {
  218. 'host': '127.1.0.1',
  219. 'user': '',
  220. 'passwd': 'foo',
  221. },
  222. ssh_schemas.RosterEntryConfig.serialize(),
  223. format_checker=jsonschema.FormatChecker()
  224. )
  225. self.assertIn('is too short', excinfo.exception.message)
  226. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  227. jsonschema.validate(
  228. {
  229. 'host': '127.1.0.1',
  230. 'user': 'root',
  231. 'passwd': 'foo',
  232. 'minion_opts': {
  233. 'interface': 0
  234. }
  235. },
  236. ssh_schemas.RosterEntryConfig.serialize(),
  237. format_checker=jsonschema.FormatChecker()
  238. )
  239. self.assertIn('is not of type', excinfo.exception.message)
  240. class RosterItemTest(TestCase):
  241. def test_roster_config(self):
  242. try:
  243. self.assertDictContainsSubset(
  244. {
  245. "$schema": "http://json-schema.org/draft-04/schema#",
  246. "title": "Roster Configuration",
  247. "description": "Roster entries definition",
  248. "type": "object",
  249. "patternProperties": {
  250. r"^([^:]+)$": ssh_schemas.RosterEntryConfig.serialize()
  251. },
  252. "additionalProperties": False
  253. },
  254. ssh_schemas.RosterItem.serialize()
  255. )
  256. except AssertionError:
  257. import salt.utils.json
  258. print(salt.utils.json.dumps(ssh_schemas.RosterItem.serialize(), indent=4))
  259. raise
  260. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  261. def test_roster_config_validate(self):
  262. try:
  263. jsonschema.validate(
  264. {'target-1':
  265. {
  266. 'host': 'localhost',
  267. 'user': 'root',
  268. 'passwd': 'foo'
  269. }
  270. },
  271. ssh_schemas.RosterItem.serialize(),
  272. format_checker=jsonschema.FormatChecker()
  273. )
  274. except jsonschema.exceptions.ValidationError as exc:
  275. self.fail('ValidationError raised: {0}'.format(exc))
  276. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  277. jsonschema.validate(
  278. {salt.utils.stringutils.to_str('target-1:1'):
  279. {
  280. 'host': 'localhost',
  281. 'user': 'root',
  282. 'passwd': 'foo'
  283. }
  284. },
  285. ssh_schemas.RosterItem.serialize(),
  286. format_checker=jsonschema.FormatChecker()
  287. )
  288. if JSONSCHEMA_VERSION < _LooseVersion('2.6.0'):
  289. self.assertIn(
  290. 'Additional properties are not allowed (\'target-1:1\' was unexpected)',
  291. excinfo.exception.message
  292. )
  293. else:
  294. self.assertIn(
  295. '\'target-1:1\' does not match any of the regexes',
  296. excinfo.exception.message
  297. )