/teuthology/task/ssh_keys.py

https://github.com/hjwsm1989/teuthology · Python · 180 lines · 109 code · 28 blank · 43 comment · 21 complexity · cd242a46574a163e542103c0011bc2d3 MD5 · raw file

  1. #!/usr/bin/python
  2. """
  3. Ssh-key key handlers and associated routines
  4. """
  5. import contextlib
  6. import logging
  7. import paramiko
  8. import re
  9. from cStringIO import StringIO
  10. from teuthology import contextutil
  11. import teuthology.misc as misc
  12. from ..orchestra import run
  13. log = logging.getLogger(__name__)
  14. ssh_keys_user = 'ssh-keys-user'
  15. def generate_keys():
  16. """
  17. Generatees a public and private key
  18. """
  19. key = paramiko.RSAKey.generate(2048)
  20. privateString = StringIO()
  21. key.write_private_key(privateString)
  22. return key.get_base64(), privateString.getvalue()
  23. def particular_ssh_key_test(line_to_test, ssh_key):
  24. """
  25. Check the validity of the ssh_key
  26. """
  27. match = re.match('[\w-]+ {key} \S+@\S+'.format(key=re.escape(ssh_key)), line_to_test)
  28. if match:
  29. return False
  30. else:
  31. return True
  32. def ssh_keys_user_line_test(line_to_test, username ):
  33. """
  34. Check the validity of the username
  35. """
  36. match = re.match('[\w-]+ \S+ {username}@\S+'.format(username=username), line_to_test)
  37. if match:
  38. return False
  39. else:
  40. return True
  41. def cleanup_added_key(ctx):
  42. """
  43. Delete the keys and removes ~/.ssh/authorized_keys2 entries we added
  44. """
  45. log.info('cleaning up keys added for testing')
  46. for remote in ctx.cluster.remotes:
  47. username, hostname = str(remote).split('@')
  48. if "" == username or "" == hostname:
  49. continue
  50. else:
  51. log.info(' cleaning up keys for user {user} on {host}'.format(host=hostname, user=username))
  52. misc.delete_file(remote, '/home/{user}/.ssh/id_rsa'.format(user=username))
  53. misc.delete_file(remote, '/home/{user}/.ssh/id_rsa.pub'.format(user=username))
  54. misc.delete_file(remote, '/home/{user}/.ssh/authorized_keys2'.format(user=username))
  55. @contextlib.contextmanager
  56. def tweak_ssh_config(ctx, config):
  57. """
  58. Turn off StrictHostKeyChecking
  59. """
  60. run.wait(
  61. ctx.cluster.run(
  62. args=[
  63. 'echo',
  64. 'StrictHostKeyChecking no\n',
  65. run.Raw('>'),
  66. run.Raw('/home/ubuntu/.ssh/config'),
  67. run.Raw('&&'),
  68. 'echo',
  69. 'UserKnownHostsFile ',
  70. run.Raw('/dev/null'),
  71. run.Raw('>>'),
  72. run.Raw('/home/ubuntu/.ssh/config'),
  73. run.Raw('&&'),
  74. run.Raw('chmod 600 /home/ubuntu/.ssh/config'),
  75. ],
  76. wait=False,
  77. )
  78. )
  79. try:
  80. yield
  81. finally:
  82. run.wait(
  83. ctx.cluster.run(
  84. args=['rm',run.Raw('/home/ubuntu/.ssh/config')],
  85. wait=False
  86. ),
  87. )
  88. @contextlib.contextmanager
  89. def push_keys_to_host(ctx, config, public_key, private_key):
  90. """
  91. Push keys to all hosts
  92. """
  93. log.info('generated public key {pub_key}'.format(pub_key=public_key))
  94. # add an entry for all hosts in ctx to auth_keys_data
  95. auth_keys_data = ''
  96. for inner_host in ctx.cluster.remotes.iterkeys():
  97. inner_username, inner_hostname = str(inner_host).split('@')
  98. # create a 'user@hostname' string using our fake hostname
  99. fake_hostname = '{user}@{host}'.format(user=ssh_keys_user, host=str(inner_hostname))
  100. auth_keys_data += '\nssh-rsa {pub_key} {user_host}\n'.format(pub_key=public_key, user_host=fake_hostname)
  101. # for each host in ctx, add keys for all other hosts
  102. for remote in ctx.cluster.remotes:
  103. username, hostname = str(remote).split('@')
  104. if "" == username or "" == hostname:
  105. continue
  106. else:
  107. log.info('pushing keys to {host} for {user}'.format(host=hostname, user=username))
  108. # adding a private key
  109. priv_key_file = '/home/{user}/.ssh/id_rsa'.format(user=username)
  110. priv_key_data = '{priv_key}'.format(priv_key=private_key)
  111. misc.delete_file(remote, priv_key_file, force=True)
  112. # Hadoop requires that .ssh/id_rsa have permissions of '500'
  113. misc.create_file(remote, priv_key_file, priv_key_data, str(500))
  114. # then a private key
  115. pub_key_file = '/home/{user}/.ssh/id_rsa.pub'.format(user=username)
  116. pub_key_data = 'ssh-rsa {pub_key} {user_host}'.format(pub_key=public_key, user_host=str(remote))
  117. misc.delete_file(remote, pub_key_file, force=True)
  118. misc.create_file(remote, pub_key_file, pub_key_data)
  119. # adding appropriate entries to the authorized_keys2 file for this host
  120. auth_keys_file = '/home/{user}/.ssh/authorized_keys2'.format(user=username)
  121. # now add the list of keys for hosts in ctx to ~/.ssh/authorized_keys2
  122. misc.create_file(remote, auth_keys_file, auth_keys_data, str(600))
  123. try:
  124. yield
  125. finally:
  126. # cleanup the keys
  127. log.info("Cleaning up SSH keys")
  128. cleanup_added_key(ctx)
  129. @contextlib.contextmanager
  130. def task(ctx, config):
  131. """
  132. Creates a set of RSA keys, distributes the same key pair
  133. to all hosts listed in ctx.cluster, and adds all hosts
  134. to all others authorized_keys list.
  135. During cleanup it will delete .ssh/id_rsa, .ssh/id_rsa.pub
  136. and remove the entries in .ssh/authorized_keys while leaving
  137. pre-existing entries in place.
  138. """
  139. if config is None:
  140. config = {}
  141. assert isinstance(config, dict), \
  142. "task hadoop only supports a dictionary for configuration"
  143. # this does not need to do cleanup and does not depend on
  144. # ctx, so I'm keeping it outside of the nested calls
  145. public_key_string, private_key_string = generate_keys()
  146. with contextutil.nested(
  147. lambda: tweak_ssh_config(ctx, config),
  148. lambda: push_keys_to_host(ctx, config, public_key_string, private_key_string),
  149. #lambda: tweak_ssh_config(ctx, config),
  150. ):
  151. yield