test_paramiko_vendor.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. # test_paramiko_vendor.py
  2. #
  3. # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
  4. # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  5. # General Public License as public by the Free Software Foundation; version 2.0
  6. # or (at your option) any later version. You can redistribute it and/or
  7. # modify it under the terms of either of these two licenses.
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. #
  15. # You should have received a copy of the licenses; if not, see
  16. # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
  17. # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
  18. # License, Version 2.0.
  19. #
  20. """Tests for paramiko_vendor."""
  21. import socket
  22. import threading
  23. from io import StringIO
  24. from typing import Optional
  25. from unittest import skipIf
  26. from .. import TestCase
  27. try:
  28. import paramiko
  29. except ImportError:
  30. has_paramiko = False
  31. else:
  32. has_paramiko = True
  33. from dulwich.contrib.paramiko_vendor import ParamikoSSHVendor
  34. class Server(paramiko.ServerInterface):
  35. """http://docs.paramiko.org/en/2.4/api/server.html."""
  36. def __init__(self, commands, *args, **kwargs) -> None:
  37. super().__init__(*args, **kwargs)
  38. self.commands = commands
  39. def check_channel_exec_request(self, channel, command) -> bool:
  40. self.commands.append(command)
  41. return True
  42. def check_auth_password(self, username, password):
  43. if username == USER and password == PASSWORD:
  44. return paramiko.AUTH_SUCCESSFUL
  45. return paramiko.AUTH_FAILED
  46. def check_auth_publickey(self, username, key):
  47. pubkey = paramiko.RSAKey.from_private_key(StringIO(CLIENT_KEY))
  48. if username == USER and key == pubkey:
  49. return paramiko.AUTH_SUCCESSFUL
  50. return paramiko.AUTH_FAILED
  51. def check_channel_request(self, kind, chanid):
  52. if kind == "session":
  53. return paramiko.OPEN_SUCCEEDED
  54. return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
  55. def get_allowed_auths(self, username) -> str:
  56. return "password,publickey"
  57. USER = "testuser"
  58. PASSWORD = "test"
  59. SERVER_KEY = """\
  60. -----BEGIN RSA PRIVATE KEY-----
  61. MIIEpAIBAAKCAQEAy/L1sSYAzxsMprtNXW4u/1jGXXkQmQ2xtmKVlR+RlIL3a1BH
  62. bzTpPlZyjltAAwzIP8XRh0iJFKz5y3zSQChhX47ZGN0NvQsVct8R+YwsUonwfAJ+
  63. JN0KBKKvC8fPHlzqBr3gX+ZxqsFH934tQ6wdQPH5eQWtdM8L826lMsH1737uyTGk
  64. +mCSDjL3c6EzY83g7qhkJU2R4qbi6ne01FaWADzG8sOzXnHT+xpxtk8TTT8yCVUY
  65. MmBNsSoA/ka3iWz70ghB+6Xb0WpFJZXWq1oYovviPAfZGZSrxBZMxsWMye70SdLl
  66. TqsBEt0+miIcm9s0fvjWvQuhaHX6mZs5VO4r5QIDAQABAoIBAGYqeYWaYgFdrYLA
  67. hUrubUCg+g3NHdFuGL4iuIgRXl4lFUh+2KoOuWDu8Uf60iA1AQNhV0sLvQ/Mbv3O
  68. s4xMLisuZfaclctDiCUZNenqnDFkxEF7BjH1QJV94W5nU4wEQ3/JEmM4D2zYkfKb
  69. FJW33JeyH6TOgUvohDYYEU1R+J9V8qA243p+ui1uVtNI6Pb0TXJnG5y9Ny4vkSWH
  70. Fi0QoMPR1r9xJ4SEearGzA/crb4SmmDTKhGSoMsT3d5ATieLmwcS66xWz8w4oFGJ
  71. yzDq24s4Fp9ccNjMf/xR8XRiekJv835gjEqwF9IXyvgOaq6XJ1iCqGPFDKa25nui
  72. JnEstOkCgYEA/ZXk7aIanvdeJlTqpX578sJfCnrXLydzE8emk1b7+5mrzGxQ4/pM
  73. PBQs2f8glT3t0O0mRX9NoRqnwrid88/b+cY4NCOICFZeasX336/gYQxyVeRLJS6Z
  74. hnGEQqry8qS7PdKAyeHMNmZFrUh4EiHiObymEfQS+mkRUObn0cGBTw8CgYEAzeQU
  75. D2baec1DawjppKaRynAvWjp+9ry1lZx9unryKVRwjRjkEpw+b3/+hdaF1IvsVSce
  76. cNj+6W2guZ2tyHuPhZ64/4SJVyE2hKDSKD4xTb2nVjsMeN0bLD2UWXC9mwbx8nWa
  77. 2tmtUZ7a/okQb2cSdosJinRewLNqXIsBXamT1csCgYEA0cXb2RCOQQ6U3dTFPx4A
  78. 3vMXuA2iUKmrsqMoEx6T2LBow/Sefdkik1iFOdipVYwjXP+w9zC2QR1Rxez/DR/X
  79. 8ymceNUjxPHdrSoTQQG29dFcC92MpDeGXQcuyA+uZjcLhbrLOzYEvsOfxBb87NMG
  80. 14hNQPDNekTMREafYo9WrtUCgYAREK54+FVzcwf7fymedA/xb4r9N4v+d3W1iNsC
  81. 8d3Qfyc1CrMct8aVB07ZWQaOr2pPRIbJY7L9NhD0UZVt4I/sy1MaGqonhqE2LP4+
  82. R6legDG2e/50ph7yc8gwAaA1kUXMiuLi8Nfkw/3yyvmJwklNegi4aRzRbA2Mzhi2
  83. 4q9WMQKBgQCb0JNyxHG4pvLWCF/j0Sm1FfvrpnqSv5678n1j4GX7Ka/TubOK1Y4K
  84. U+Oib7dKa/zQMWehVFNTayrsq6bKVZ6q7zG+IHiRLw4wjeAxREFH6WUjDrn9vl2l
  85. D48DKbBuBwuVOJWyq3qbfgJXojscgNQklrsPdXVhDwOF0dYxP89HnA==
  86. -----END RSA PRIVATE KEY-----"""
  87. CLIENT_KEY = """\
  88. -----BEGIN RSA PRIVATE KEY-----
  89. MIIEpAIBAAKCAQEAxvREKSElPOm/0z/nPO+j5rk2tjdgGcGc7We1QZ6TRXYLu7nN
  90. GeEFIL4p8N1i6dmB+Eydt7xqCU79MWD6Yy4prFe1+/K1wCDUxIbFMxqQcX5zjJzd
  91. i8j8PbcaUlVhP/OkjtkSxrXaGDO1BzfdV4iEBtTV/2l3zmLKJlt3jnOHLczP24CB
  92. DTQKp3rKshbRefzot9Y+wnaK692RsYgsyo9YEP0GyWKG9topCHk13r46J6vGLeuj
  93. ryUKqmbLJkzbJbIcEqwTDo5iHaCVqaMr5Hrb8BdMucSseqZQJsXSd+9tdRcIblUQ
  94. 38kZjmFMm4SFbruJcpZCNM2wNSZPIRX+3eiwNwIDAQABAoIBAHSacOBSJsr+jIi5
  95. KUOTh9IPtzswVUiDKwARCjB9Sf8p4lKR4N1L/n9kNJyQhApeikgGT2GCMftmqgoo
  96. tlculQoHFgemBlOmak0MV8NNzF5YKEy/GzF0CDH7gJfEpoyetVFrdA+2QS5yD6U9
  97. XqKQxiBi2VEqdScmyyeT8AwzNYTnPeH/DOEcnbdRjqiy/CD79F49CQ1lX1Fuqm0K
  98. I7BivBH1xo/rVnUP4F+IzocDqoga+Pjdj0LTXIgJlHQDSbhsQqWujWQDDuKb+MAw
  99. sNK4Zf8ErV3j1PyA7f/M5LLq6zgstkW4qikDHo4SpZX8kFOO8tjqb7kujj7XqeaB
  100. CxqrOTECgYEA73uWkrohcmDJ4KqbuL3tbExSCOUiaIV+sT1eGPNi7GCmXD4eW5Z4
  101. 75v2IHymW83lORSu/DrQ6sKr1nkuRpqr2iBzRmQpl/H+wahIhBXlnJ25uUjDsuPO
  102. 1Pq2LcmyD+jTxVnmbSe/q7O09gZQw3I6H4+BMHmpbf8tC97lqimzpJ0CgYEA1K0W
  103. ZL70Xtn9quyHvbtae/BW07NZnxvUg4UaVIAL9Zu34JyplJzyzbIjrmlDbv6aRogH
  104. /KtuG9tfbf55K/jjqNORiuRtzt1hUN1ye4dyW7tHx2/7lXdlqtyK40rQl8P0kqf8
  105. zaS6BqjnobgSdSpg32rWoL/pcBHPdJCJEgQ8zeMCgYEA0/PK8TOhNIzrP1dgGSKn
  106. hkkJ9etuB5nW5mEM7gJDFDf6JPupfJ/xiwe6z0fjKK9S57EhqgUYMB55XYnE5iIw
  107. ZQ6BV9SAZ4V7VsRs4dJLdNC3tn/rDGHJBgCaym2PlbsX6rvFT+h1IC8dwv0V79Ui
  108. Ehq9WTzkMoE8yhvNokvkPZUCgYEAgBAFxv5xGdh79ftdtXLmhnDvZ6S8l6Fjcxqo
  109. Ay/jg66Tp43OU226iv/0mmZKM8Dd1xC8dnon4GBVc19jSYYiWBulrRPlx0Xo/o+K
  110. CzZBN1lrXH1i6dqufpc0jq8TMf/N+q1q/c1uMupsKCY1/xVYpc+ok71b7J7c49zQ
  111. nOeuUW8CgYA9Infooy65FTgbzca0c9kbCUBmcAPQ2ItH3JcPKWPQTDuV62HcT00o
  112. fZdIV47Nez1W5Clk191RMy8TXuqI54kocciUWpThc6j44hz49oUueb8U4bLcEHzA
  113. WxtWBWHwxfSmqgTXilEA3ALJp0kNolLnEttnhENwJpZHlqtes0ZA4w==
  114. -----END RSA PRIVATE KEY-----"""
  115. @skipIf(not has_paramiko, "paramiko is not installed")
  116. class ParamikoSSHVendorTests(TestCase):
  117. def setUp(self) -> None:
  118. import paramiko.transport
  119. # re-enable server functionality for tests
  120. if hasattr(paramiko.transport, "SERVER_DISABLED_BY_GENTOO"):
  121. paramiko.transport.SERVER_DISABLED_BY_GENTOO = False
  122. self.commands = []
  123. socket.setdefaulttimeout(10)
  124. self.addCleanup(socket.setdefaulttimeout, None)
  125. self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  126. self.socket.bind(("127.0.0.1", 0))
  127. self.socket.listen(5)
  128. self.addCleanup(self.socket.close)
  129. self.port = self.socket.getsockname()[1]
  130. self.thread = threading.Thread(target=self._run)
  131. self.thread.start()
  132. def tearDown(self) -> None:
  133. self.thread.join()
  134. def _run(self) -> Optional[bool]:
  135. try:
  136. conn, addr = self.socket.accept()
  137. except OSError:
  138. return False
  139. self.transport = paramiko.Transport(conn)
  140. self.addCleanup(self.transport.close)
  141. host_key = paramiko.RSAKey.from_private_key(StringIO(SERVER_KEY))
  142. self.transport.add_server_key(host_key)
  143. server = Server(self.commands)
  144. self.transport.start_server(server=server)
  145. def test_run_command_password(self) -> None:
  146. vendor = ParamikoSSHVendor(
  147. allow_agent=False,
  148. look_for_keys=False,
  149. )
  150. vendor.run_command(
  151. "127.0.0.1",
  152. "test_run_command_password",
  153. username=USER,
  154. port=self.port,
  155. password=PASSWORD,
  156. )
  157. self.assertIn(b"test_run_command_password", self.commands)
  158. def test_run_command_with_privkey(self) -> None:
  159. key = paramiko.RSAKey.from_private_key(StringIO(CLIENT_KEY))
  160. vendor = ParamikoSSHVendor(
  161. allow_agent=False,
  162. look_for_keys=False,
  163. )
  164. vendor.run_command(
  165. "127.0.0.1",
  166. "test_run_command_with_privkey",
  167. username=USER,
  168. port=self.port,
  169. pkey=key,
  170. )
  171. self.assertIn(b"test_run_command_with_privkey", self.commands)
  172. def test_run_command_data_transfer(self) -> None:
  173. vendor = ParamikoSSHVendor(
  174. allow_agent=False,
  175. look_for_keys=False,
  176. )
  177. con = vendor.run_command(
  178. "127.0.0.1",
  179. "test_run_command_data_transfer",
  180. username=USER,
  181. port=self.port,
  182. password=PASSWORD,
  183. )
  184. self.assertIn(b"test_run_command_data_transfer", self.commands)
  185. channel = self.transport.accept(5)
  186. channel.send(b"stdout\n")
  187. channel.send_stderr(b"stderr\n")
  188. channel.close()
  189. # Fixme: it's return false
  190. # self.assertTrue(con.can_read())
  191. self.assertEqual(b"stdout\n", con.read(4096))
  192. # Fixme: it's return empty string
  193. # self.assertEqual(b'stderr\n', con.read_stderr(4096))