2
1

sshd.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import os
  2. import shutil
  3. import subprocess
  4. from unittest import SkipTest
  5. # subprocess does not kill the child daemon when a test case fails by raising
  6. # an exception. So use pexpect instead.
  7. import pexpect
  8. import infra
  9. SSHD_PORT_INITIAL = 2222
  10. SSHD_PORT_LAST = SSHD_PORT_INITIAL + 99
  11. SSHD_PATH = "/usr/sbin/sshd"
  12. SSHD_HOST_DIR = "host"
  13. # SSHD_KEY_DIR is where the /etc/ssh/ssh_host_*_key files go
  14. SSHD_KEY_DIR = os.path.join(SSHD_HOST_DIR, "etc/ssh")
  15. SSHD_KEY = os.path.join(SSHD_KEY_DIR, "ssh_host_ed25519_key")
  16. # SSH_CLIENT_KEY_DIR is where the client id_rsa key and authorized_keys files go
  17. SSH_CLIENT_KEY_DIR = os.path.join(SSHD_HOST_DIR, "home/br-user/ssh")
  18. SSH_CLIENT_KEY = os.path.join(SSH_CLIENT_KEY_DIR, "id_rsa")
  19. SSH_AUTH_KEYS_FILE = os.path.join(SSH_CLIENT_KEY_DIR, "authorized_keys")
  20. class OpenSSHDaemon():
  21. def __init__(self, builddir, logtofile):
  22. """
  23. Start an OpenSSH SSH Daemon
  24. In order to support test cases in parallel, select the port the
  25. server will listen to in runtime. Since there is no reliable way
  26. to allocate the port prior to starting the server (another
  27. process in the host machine can use the port between it is
  28. selected from a list and it is really allocated to the server)
  29. try to start the server in a port and in the case it is already
  30. in use, try the next one in the allowed range.
  31. """
  32. self.daemon = None
  33. self.port = None
  34. self.logfile = infra.open_log_file(builddir, "sshd", logtofile)
  35. server_keyfile = os.path.join(builddir, SSHD_KEY)
  36. auth_keys_file = os.path.join(builddir, SSH_AUTH_KEYS_FILE)
  37. daemon_cmd = [SSHD_PATH,
  38. "-D", # or use -ddd to debug
  39. "-e",
  40. "-h", server_keyfile,
  41. "-f", "/dev/null",
  42. "-o", "ListenAddress=localhost",
  43. "-o", "PidFile=none",
  44. "-o", "AuthenticationMethods=publickey",
  45. "-o", "StrictModes=no",
  46. "-o", "Subsystem=sftp internal-sftp",
  47. "-o", "AuthorizedKeysFile={}".format(auth_keys_file)]
  48. for port in range(SSHD_PORT_INITIAL, SSHD_PORT_LAST + 1):
  49. cmd = daemon_cmd + ["-p", "{}".format(port)]
  50. self.logfile.write(
  51. "> starting sshd with '{}'\n".format(" ".join(cmd)))
  52. try:
  53. self.daemon = pexpect.spawn(cmd[0], cmd[1:], logfile=self.logfile,
  54. encoding='utf-8')
  55. except pexpect.exceptions.ExceptionPexpect as e:
  56. self.logfile.write("> {} - skipping\n".format(e))
  57. raise SkipTest(str(e))
  58. ret = self.daemon.expect([
  59. # Success
  60. "Server listening on .* port {}.".format(port),
  61. # Failure
  62. "Cannot bind any address."])
  63. if ret == 0:
  64. self.port = port
  65. return
  66. raise SystemError("Could not find a free port to run sshd")
  67. def stop(self):
  68. if self.daemon is None:
  69. return
  70. self.daemon.terminate(force=True)
  71. def generate_keys_server(builddir, logfile):
  72. """Generate keys required to run an OpenSSH Daemon."""
  73. keyfile = os.path.join(builddir, SSHD_KEY)
  74. if os.path.exists(keyfile):
  75. logfile.write("> SSH server key already exists '{}'".format(keyfile))
  76. return
  77. hostdir = os.path.join(builddir, SSHD_HOST_DIR)
  78. keydir = os.path.join(builddir, SSHD_KEY_DIR)
  79. os.makedirs(hostdir, exist_ok=True)
  80. os.makedirs(keydir, exist_ok=True)
  81. cmd = ["ssh-keygen", "-A", "-f", hostdir]
  82. logfile.write(
  83. "> generating SSH server keys with '{}'\n".format(" ".join(cmd)))
  84. # When ssh-keygen fails to create an SSH server key it doesn't return a
  85. # useful error code. So use check for an error message in the output
  86. # instead.
  87. try:
  88. out = subprocess.check_output(cmd, encoding='utf-8')
  89. except FileNotFoundError:
  90. logfile.write("> ssh-keygen not found - skipping\n")
  91. raise SkipTest("ssh-keygen not found")
  92. logfile.write(out)
  93. if "Could not save your public key" in out:
  94. raise SystemError("Could not generate SSH server keys")
  95. def generate_keys_client(builddir, logfile):
  96. """Generate keys required to log into an OpenSSH Daemon via SCP or SFTP."""
  97. keyfile = os.path.join(builddir, SSH_CLIENT_KEY)
  98. if os.path.exists(keyfile):
  99. logfile.write("> SSH client key already exists '{}'".format(keyfile))
  100. return
  101. keydir = os.path.join(builddir, SSH_CLIENT_KEY_DIR)
  102. os.makedirs(keydir, exist_ok=True)
  103. cmd = ["ssh-keygen",
  104. "-f", keyfile,
  105. "-b", "2048",
  106. "-t", "rsa",
  107. "-N", "",
  108. "-q"]
  109. logfile.write(
  110. "> generating SSH client keys with '{}'\n".format(" ".join(cmd)))
  111. try:
  112. subprocess.check_call(cmd, stdout=logfile, stderr=logfile)
  113. except FileNotFoundError:
  114. logfile.write("> ssh-keygen not found - skipping\n")
  115. raise SkipTest("ssh-keygen not found")
  116. # Allow key-based login for this user (so that we can fetch from localhost)
  117. pubkeyfile = os.path.join(keydir, "{}.pub".format(keyfile))
  118. authfile = os.path.join(keydir, "authorized_keys")
  119. shutil.copy(pubkeyfile, authfile)
  120. def generate_keys(builddir, logtofile):
  121. logfile = infra.open_log_file(builddir, "ssh-keygen", logtofile)
  122. generate_keys_server(builddir, logfile)
  123. generate_keys_client(builddir, logfile)