2012-12-06 22:18:41 +00:00
|
|
|
#!/usr/bin/python
|
|
|
|
import contextlib
|
|
|
|
import logging
|
|
|
|
import paramiko
|
|
|
|
import re
|
|
|
|
|
|
|
|
from cStringIO import StringIO
|
|
|
|
from teuthology import contextutil
|
|
|
|
from ..orchestra import run
|
2012-12-13 22:42:09 +00:00
|
|
|
from ..orchestra.connection import create_key
|
2012-12-06 22:18:41 +00:00
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
# generatees a public and private key
|
|
|
|
def generate_keys():
|
|
|
|
key = paramiko.RSAKey.generate(2048)
|
|
|
|
privateString = StringIO()
|
|
|
|
key.write_private_key(privateString)
|
|
|
|
return key.get_base64(), privateString.getvalue()
|
|
|
|
|
|
|
|
# deletes the keys and removes ~/.ssh/authorized_keys entries we added
|
|
|
|
def cleanup_keys(ctx, public_key):
|
|
|
|
client = paramiko.SSHClient()
|
|
|
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
|
|
|
|
|
|
for host in ctx.cluster.remotes.iterkeys():
|
|
|
|
username, hostname = str(host).split('@')
|
|
|
|
log.info('cleaning up keys on {host}'.format(host=hostname, user=username))
|
|
|
|
|
2012-12-13 22:42:09 +00:00
|
|
|
# try to extract a public key for the host from the ctx.config entries
|
|
|
|
host_key_found = False
|
|
|
|
for t, host_key in ctx.config['targets'].iteritems():
|
|
|
|
|
|
|
|
if str(t) == str(host):
|
|
|
|
keytype, key = host_key.split(' ',1)
|
|
|
|
client.get_host_keys().add(
|
|
|
|
hostname=hostname,
|
|
|
|
keytype=keytype,
|
|
|
|
key=create_key(keytype,key)
|
|
|
|
)
|
|
|
|
host_key_found = True
|
|
|
|
log.info('ssh key found in ctx')
|
|
|
|
|
|
|
|
# if we did not find a key, load the system keys
|
|
|
|
if False == host_key_found:
|
|
|
|
client.load_system_host_keys()
|
|
|
|
log.info('no key found in ctx, using system host keys')
|
|
|
|
|
2012-12-06 22:18:41 +00:00
|
|
|
client.connect(hostname, username=username)
|
|
|
|
client.exec_command('rm ~/.ssh/id_rsa')
|
|
|
|
client.exec_command('rm ~/.ssh/id_rsa.pub')
|
|
|
|
|
|
|
|
# get the absolute path for authorized_keys
|
|
|
|
stdin, stdout, stderr = client.exec_command('ls ~/.ssh/authorized_keys')
|
|
|
|
auth_keys_file = stdout.readlines()[0].rstrip()
|
|
|
|
|
|
|
|
mySftp = client.open_sftp()
|
|
|
|
|
|
|
|
# write to a different authorized_keys file in case something
|
|
|
|
# fails 1/2 way through (don't want to break ssh on the vm)
|
|
|
|
old_auth_keys_file = mySftp.open(auth_keys_file)
|
|
|
|
new_auth_keys_file = mySftp.open(auth_keys_file + '.new', 'w')
|
|
|
|
|
|
|
|
for line in old_auth_keys_file.readlines():
|
|
|
|
match = re.search(re.escape(public_key), line)
|
|
|
|
|
|
|
|
if match:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
new_auth_keys_file.write(line)
|
|
|
|
|
|
|
|
# close the files
|
|
|
|
old_auth_keys_file.close()
|
|
|
|
new_auth_keys_file.close()
|
|
|
|
|
|
|
|
# now try to do an atomic-ish rename. If we botch this, it's bad news
|
|
|
|
stdin, stdout, stderr = client.exec_command('mv ~/.ssh/authorized_keys.new ~/.ssh/authorized_keys')
|
|
|
|
|
|
|
|
mySftp.close()
|
|
|
|
client.close()
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
|
|
|
def tweak_ssh_config(ctx, config):
|
|
|
|
run.wait(
|
|
|
|
ctx.cluster.run(
|
|
|
|
args=[
|
|
|
|
'echo',
|
|
|
|
'StrictHostKeyChecking no\n',
|
|
|
|
run.Raw('>'),
|
|
|
|
run.Raw('/home/ubuntu/.ssh/config'),
|
|
|
|
],
|
|
|
|
wait=False,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
try:
|
|
|
|
yield
|
|
|
|
|
|
|
|
finally:
|
|
|
|
run.wait(
|
|
|
|
ctx.cluster.run(
|
|
|
|
args=['rm',run.Raw('/home/ubuntu/.ssh/config')],
|
|
|
|
wait=False
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
|
|
|
def push_keys_to_host(ctx, config, public_key, private_key):
|
|
|
|
|
|
|
|
client = paramiko.SSHClient()
|
|
|
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
|
|
|
|
|
|
for host in ctx.cluster.remotes.iterkeys():
|
|
|
|
log.info('host: {host}'.format(host=host))
|
|
|
|
username, hostname = str(host).split('@')
|
2012-12-13 22:42:09 +00:00
|
|
|
|
|
|
|
# try to extract a public key for the host from the ctx.config entries
|
|
|
|
host_key_found = False
|
|
|
|
for t, host_key in ctx.config['targets'].iteritems():
|
|
|
|
|
|
|
|
if str(t) == str(host):
|
|
|
|
keytype, key = host_key.split(' ',1)
|
|
|
|
client.get_host_keys().add(
|
|
|
|
hostname=hostname,
|
|
|
|
keytype=keytype,
|
|
|
|
key=create_key(keytype,key)
|
|
|
|
)
|
|
|
|
host_key_found = True
|
|
|
|
log.info('ssh key found in ctx')
|
|
|
|
|
|
|
|
# if we did not find a key, load the system keys
|
|
|
|
if False == host_key_found:
|
|
|
|
client.load_system_host_keys()
|
|
|
|
log.info('no key found in ctx, using system host keys')
|
|
|
|
|
2012-12-06 22:18:41 +00:00
|
|
|
log.info('pushing keys to {host} for {user}'.format(host=hostname, user=username))
|
|
|
|
|
|
|
|
client.connect(hostname, username=username)
|
|
|
|
client.exec_command('echo "{priv_key}" > ~/.ssh/id_rsa'.format(priv_key=private_key))
|
|
|
|
# the default file permissions cause ssh to balk
|
|
|
|
client.exec_command('chmod 500 ~/.ssh/id_rsa')
|
|
|
|
client.exec_command('echo "ssh-rsa {pub_key} {user_host}" > ~/.ssh/id_rsa.pub'.format(pub_key=public_key,user_host=host))
|
|
|
|
|
|
|
|
# for this host, add all hosts to the ~/.ssh/authorized_keys file
|
|
|
|
for inner_host in ctx.cluster.remotes.iterkeys():
|
|
|
|
client.exec_command('echo "ssh-rsa {pub_key} {user_host}" >> ~/.ssh/authorized_keys'.format(pub_key=public_key,user_host=str(inner_host)))
|
|
|
|
|
|
|
|
|
|
|
|
client.close()
|
|
|
|
|
|
|
|
try:
|
|
|
|
yield
|
|
|
|
|
|
|
|
finally:
|
|
|
|
# cleanup the keys
|
|
|
|
log.info("Cleaning up SSH keys")
|
|
|
|
cleanup_keys(ctx, public_key)
|
|
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
|
|
|
def task(ctx, config):
|
|
|
|
"""
|
|
|
|
Creates a set of RSA keys, distributes the same key pair
|
|
|
|
to all hosts listed in ctx.cluster, and adds all hosts
|
|
|
|
to all others authorized_keys list.
|
|
|
|
|
|
|
|
During cleanup it will delete .ssh/id_rsa, .ssh/id_rsa.pub
|
|
|
|
and remove the entries in .ssh/authorized_keys while leaving
|
|
|
|
pre-existing entries in place.
|
|
|
|
"""
|
|
|
|
|
|
|
|
if config is None:
|
|
|
|
config = {}
|
|
|
|
assert isinstance(config, dict), \
|
|
|
|
"task hadoop only supports a dictionary for configuration"
|
|
|
|
|
|
|
|
# this does not need to do cleanup and does not depend on
|
|
|
|
# ctx, so I'm keeping it outside of the nested calls
|
|
|
|
public_key_string, private_key_string = generate_keys()
|
|
|
|
|
|
|
|
with contextutil.nested(
|
|
|
|
lambda: push_keys_to_host(ctx, config, public_key_string, private_key_string),
|
|
|
|
lambda: tweak_ssh_config(ctx, config),
|
|
|
|
|
|
|
|
):
|
|
|
|
yield
|
|
|
|
|