[PATCH] [Kimchi] Issue #1063: Upon migrating guest to remote server, password less ssh is permanent

From: Archana Singh <archus@linux.vnet.ibm.com> Changes to ensure that password less login is removed if its setup by kimchi. It also provide option for user to specify if user want to keep the password less login setup by kimchi, if not specified password less login setup by kimchi will be removed. However if password less login is not setup by kimchi, code does not remove the password less login. Signed-off-by: Archana Singh <archus@linux.vnet.ibm.com> --- model/vms.py | 180 ++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 129 insertions(+), 51 deletions(-) diff --git a/model/vms.py b/model/vms.py index bff7ed2..28aab76 100644 --- a/model/vms.py +++ b/model/vms.py @@ -1747,58 +1747,59 @@ class VMModel(object): if password is None: raise OperationFailed("KCHVM0056E", {'host': remote_host, 'user': user}) - else: - self._set_password_less_login(remote_host, user, password) + return False + return True - def _set_password_less_login(self, remote_host, user, passwd): - home_dir = '/root' if user is 'root' else '/home/%s' % user + def _read_id_rsa_pub_file(self, id_rsa_pub_file): + data = None + with open(id_rsa_pub_file, "r") as id_file: + data = id_file.read() + return data - id_rsa_file = "%s/.ssh/id_rsa" % home_dir - id_rsa_pub_file = id_rsa_file + '.pub' - ssh_port = 22 - ssh_client = None + def _create_root_ssh_key_if_required(self, user, id_rsa_pub_file, + id_rsa_file): + if os.path.isfile(id_rsa_pub_file): + return - def read_id_rsa_pub_file(): - data = None - with open(id_rsa_pub_file, "r") as id_file: - data = id_file.read() - return data + with open("/dev/zero") as zero_input: + cmd = ['ssh-keygen', '-q', '-N', '', '-f', id_rsa_file] + proc = subprocess.Popen( + cmd, + stdin=zero_input, + stdout=open(os.devnull, 'wb') + ) + out, err = proc.communicate() - def create_root_ssh_key_if_required(): - if os.path.isfile(id_rsa_pub_file): - return + if not os.path.isfile(id_rsa_pub_file): + raise OperationFailed("KCHVM0070E") - with open("/dev/zero") as zero_input: - cmd = ['ssh-keygen', '-q', '-N', '', '-f', id_rsa_file] - proc = subprocess.Popen( - cmd, - stdin=zero_input, - stdout=open(os.devnull, 'wb') + if user != 'root': + id_rsa_content = self._read_id_rsa_pub_file(id_rsa_pub_file) + updated_content = id_rsa_content.replace( + ' root@', ' %s@' % user ) - out, err = proc.communicate() + with open(id_rsa_pub_file, 'w+') as f: + f.write(updated_content) - if not os.path.isfile(id_rsa_pub_file): - raise OperationFailed("KCHVM0070E") + user_uid = pwd.getpwnam(user).pw_uid + user_gid = pwd.getpwnam(user).pw_gid + os.chown(id_rsa_pub_file, user_uid, user_gid) + os.chown(id_rsa_file, user_uid, user_gid) - if user is not 'root': - id_rsa_content = read_id_rsa_pub_file() - updated_content = id_rsa_content.replace( - ' root@', ' %s@' % user - ) - with open(id_rsa_pub_file, 'w+') as f: - f.write(updated_content) + def _get_ssh_client(self, remote_host, user, passwd, ssh_port): + ssh_client = paramiko.SSHClient() + ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh_client.connect(remote_host, ssh_port, username=user, + password=passwd, timeout=4) + return ssh_client - user_uid = pwd.getpwnam(user).pw_uid - user_gid = pwd.getpwnam(user).pw_gid - os.chown(id_rsa_pub_file, user_uid, user_gid) - os.chown(id_rsa_file, user_uid, user_gid) + def _set_password_less_login(self, remote_host, user, passwd): + home_dir = '/root' if user == 'root' else '/home/%s' % user - def get_ssh_client(remote_host, user, passwd): - ssh_client = paramiko.SSHClient() - ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - ssh_client.connect(remote_host, ssh_port, username=user, - password=passwd, timeout=4) - return ssh_client + id_rsa_file = "%s/.ssh/id_rsa" % home_dir + id_rsa_pub_file = id_rsa_file + '.pub' + ssh_port = 22 + ssh_client = None def append_id_rsa_to_remote_authorized_keys(ssh_client, id_rsa_data): sftp_client = ssh_client.open_sftp() @@ -1824,9 +1825,11 @@ class VMModel(object): sftp_client.close() try: - create_root_ssh_key_if_required() - id_rsa_data = read_id_rsa_pub_file() - ssh_client = get_ssh_client(remote_host, user, passwd) + self._create_root_ssh_key_if_required(user, id_rsa_pub_file, + id_rsa_file) + id_rsa_data = self._read_id_rsa_pub_file(id_rsa_pub_file) + ssh_client = self._get_ssh_client(remote_host, user, passwd, + ssh_port) append_id_rsa_to_remote_authorized_keys( ssh_client, id_rsa_data @@ -1865,17 +1868,21 @@ class VMModel(object): def migration_pre_check(self, remote_host, user, password): self._check_if_host_not_localhost(remote_host) - self._check_if_password_less_login_enabled( + default_passless_status = self._check_if_password_less_login_enabled( remote_host, user, password ) - self._check_remote_libvirt_conn(remote_host, user) + if not default_passless_status: + self._set_password_less_login(remote_host, user, password) + self._check_if_migrating_same_arch_hypervisor(remote_host, user) if platform.machine() in ['ppc64', 'ppc64le']: self._check_ppc64_subcores_per_core(remote_host, user) + return default_passless_status + def _check_if_path_exists_in_remote_host(self, path, remote_host, user): username_host = "%s@%s" % (user, remote_host) cmd = ['ssh', '-oStrictHostKeyChecking=no', username_host, @@ -1957,15 +1964,22 @@ class VMModel(object): user ) - def migrate(self, name, remote_host, user=None, password=None): + def migrate(self, name, remote_host, user=None, password=None, + remove_passless_login=True): name = name.decode('utf-8') remote_host = remote_host.decode('utf-8') if user is None: user = 'root' + user = user.encode('utf-8') + + default_passless_status = self.migration_pre_check(remote_host, user, + password) - self.migration_pre_check(remote_host, user, password) - dest_conn = self._get_remote_libvirt_conn(remote_host, user) + if default_passless_status: + remove_passless_login = False + + dest_conn = self._get_remote_libvirt_conn(remote_host) non_shared = self._check_if_nonshared_migration( name, @@ -1977,18 +1991,80 @@ class VMModel(object): 'dest_conn': dest_conn, 'non_shared': non_shared, 'remote_host': remote_host, - 'user': user} + 'user': user, + 'password': password, + 'remove_passless_login': remove_passless_login} task_id = AsyncTask('/plugins/kimchi/vms/%s/migrate' % name, self._migrate_task, params).id return self.task.lookup(task_id) + def _remove_password_less_login(self, remote_host, user, passwd): + home_dir = '/root' if user == 'root' else '/home/%s' % user + + id_rsa_file = "%s/.ssh/id_rsa" % home_dir + id_rsa_pub_file = id_rsa_file + '.pub' + ssh_port = 22 + ssh_client = None + + def remove_id_rsa_to_remote_authorized_keys(ssh_client, id_rsa_data): + sftp_client = ssh_client.open_sftp() + ssh_dir = '%s/.ssh' % home_dir + + try: + sftp_client.chdir(ssh_dir) + except IOError: + raise OperationFailed( + "KCHVM0089E", + {'host': remote_host, 'user': user, 'sshdir': ssh_dir} + ) + file_handler = sftp_client.file( + '%s/.ssh/authorized_keys' % home_dir, + mode='r', + bufsize=1 + ) + lines = file_handler.readlines() + try: + lines.remove(id_rsa_data) + except ValueError: + pass + file_handler = sftp_client.file( + '%s/.ssh/authorized_keys' % home_dir, + mode='w', + bufsize=1 + ) + file_handler.writelines(lines) + file_handler.flush() + file_handler.close() + sftp_client.close() + + try: + self._create_root_ssh_key_if_required(user, id_rsa_pub_file, + id_rsa_file) + id_rsa_data = self._read_id_rsa_pub_file(id_rsa_pub_file) + ssh_client = self._get_ssh_client(remote_host, user, passwd, + ssh_port) + remove_id_rsa_to_remote_authorized_keys( + ssh_client, + id_rsa_data + ) + except Exception, e: + raise OperationFailed( + "KCHVM0068E", + {'host': remote_host, 'user': user, 'error': e} + ) + finally: + if ssh_client: + ssh_client.close() + def _migrate_task(self, cb, params): name = params['name'].decode('utf-8') dest_conn = params['dest_conn'] non_shared = params['non_shared'] remote_host = params['remote_host'] user = params['user'] + password = params['password'] + remove_passless_login = params['remove_passless_login'] cb('starting a migration') @@ -2022,6 +2098,8 @@ class VMModel(object): raise OperationFailed('KCHVM0058E', {'err': e.message, 'name': name}) finally: + if remove_passless_login: + self._remove_password_less_login(remote_host, user, password) dest_conn.close() cb('Migrate finished', True) -- 2.7.4

Tested-by: Daniel Barboza <danielhb@linux.vnet.ibm.com> It worked perfectly. Good job! My only recommendation before pushing it upstream: please add the new 'remove_passless_login' parameter in documentation of the 'migrate' API in docs/API.md. Don't forget to mention that the default behavior if no password less setup is present between the hosts is to undo any setup made by Kimchi. Daniel On 11/15/2016 02:22 PM, archus@linux.vnet.ibm.com wrote:
From: Archana Singh <archus@linux.vnet.ibm.com>
Changes to ensure that password less login is removed if its setup by kimchi. It also provide option for user to specify if user want to keep the password less login setup by kimchi, if not specified password less login setup by kimchi will be removed. However if password less login is not setup by kimchi, code does not remove the password less login.
Signed-off-by: Archana Singh <archus@linux.vnet.ibm.com> --- model/vms.py | 180 ++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 129 insertions(+), 51 deletions(-)
diff --git a/model/vms.py b/model/vms.py index bff7ed2..28aab76 100644 --- a/model/vms.py +++ b/model/vms.py @@ -1747,58 +1747,59 @@ class VMModel(object): if password is None: raise OperationFailed("KCHVM0056E", {'host': remote_host, 'user': user}) - else: - self._set_password_less_login(remote_host, user, password) + return False + return True
- def _set_password_less_login(self, remote_host, user, passwd): - home_dir = '/root' if user is 'root' else '/home/%s' % user + def _read_id_rsa_pub_file(self, id_rsa_pub_file): + data = None + with open(id_rsa_pub_file, "r") as id_file: + data = id_file.read() + return data
- id_rsa_file = "%s/.ssh/id_rsa" % home_dir - id_rsa_pub_file = id_rsa_file + '.pub' - ssh_port = 22 - ssh_client = None + def _create_root_ssh_key_if_required(self, user, id_rsa_pub_file, + id_rsa_file): + if os.path.isfile(id_rsa_pub_file): + return
- def read_id_rsa_pub_file(): - data = None - with open(id_rsa_pub_file, "r") as id_file: - data = id_file.read() - return data + with open("/dev/zero") as zero_input: + cmd = ['ssh-keygen', '-q', '-N', '', '-f', id_rsa_file] + proc = subprocess.Popen( + cmd, + stdin=zero_input, + stdout=open(os.devnull, 'wb') + ) + out, err = proc.communicate()
- def create_root_ssh_key_if_required(): - if os.path.isfile(id_rsa_pub_file): - return + if not os.path.isfile(id_rsa_pub_file): + raise OperationFailed("KCHVM0070E")
- with open("/dev/zero") as zero_input: - cmd = ['ssh-keygen', '-q', '-N', '', '-f', id_rsa_file] - proc = subprocess.Popen( - cmd, - stdin=zero_input, - stdout=open(os.devnull, 'wb') + if user != 'root': + id_rsa_content = self._read_id_rsa_pub_file(id_rsa_pub_file) + updated_content = id_rsa_content.replace( + ' root@', ' %s@' % user ) - out, err = proc.communicate() + with open(id_rsa_pub_file, 'w+') as f: + f.write(updated_content)
- if not os.path.isfile(id_rsa_pub_file): - raise OperationFailed("KCHVM0070E") + user_uid = pwd.getpwnam(user).pw_uid + user_gid = pwd.getpwnam(user).pw_gid + os.chown(id_rsa_pub_file, user_uid, user_gid) + os.chown(id_rsa_file, user_uid, user_gid)
- if user is not 'root': - id_rsa_content = read_id_rsa_pub_file() - updated_content = id_rsa_content.replace( - ' root@', ' %s@' % user - ) - with open(id_rsa_pub_file, 'w+') as f: - f.write(updated_content) + def _get_ssh_client(self, remote_host, user, passwd, ssh_port): + ssh_client = paramiko.SSHClient() + ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh_client.connect(remote_host, ssh_port, username=user, + password=passwd, timeout=4) + return ssh_client
- user_uid = pwd.getpwnam(user).pw_uid - user_gid = pwd.getpwnam(user).pw_gid - os.chown(id_rsa_pub_file, user_uid, user_gid) - os.chown(id_rsa_file, user_uid, user_gid) + def _set_password_less_login(self, remote_host, user, passwd): + home_dir = '/root' if user == 'root' else '/home/%s' % user
- def get_ssh_client(remote_host, user, passwd): - ssh_client = paramiko.SSHClient() - ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - ssh_client.connect(remote_host, ssh_port, username=user, - password=passwd, timeout=4) - return ssh_client + id_rsa_file = "%s/.ssh/id_rsa" % home_dir + id_rsa_pub_file = id_rsa_file + '.pub' + ssh_port = 22 + ssh_client = None
def append_id_rsa_to_remote_authorized_keys(ssh_client, id_rsa_data): sftp_client = ssh_client.open_sftp() @@ -1824,9 +1825,11 @@ class VMModel(object): sftp_client.close()
try: - create_root_ssh_key_if_required() - id_rsa_data = read_id_rsa_pub_file() - ssh_client = get_ssh_client(remote_host, user, passwd) + self._create_root_ssh_key_if_required(user, id_rsa_pub_file, + id_rsa_file) + id_rsa_data = self._read_id_rsa_pub_file(id_rsa_pub_file) + ssh_client = self._get_ssh_client(remote_host, user, passwd, + ssh_port) append_id_rsa_to_remote_authorized_keys( ssh_client, id_rsa_data @@ -1865,17 +1868,21 @@ class VMModel(object):
def migration_pre_check(self, remote_host, user, password): self._check_if_host_not_localhost(remote_host) - self._check_if_password_less_login_enabled( + default_passless_status = self._check_if_password_less_login_enabled( remote_host, user, password ) - self._check_remote_libvirt_conn(remote_host, user) + if not default_passless_status: + self._set_password_less_login(remote_host, user, password) + self._check_if_migrating_same_arch_hypervisor(remote_host, user)
if platform.machine() in ['ppc64', 'ppc64le']: self._check_ppc64_subcores_per_core(remote_host, user)
+ return default_passless_status + def _check_if_path_exists_in_remote_host(self, path, remote_host, user): username_host = "%s@%s" % (user, remote_host) cmd = ['ssh', '-oStrictHostKeyChecking=no', username_host, @@ -1957,15 +1964,22 @@ class VMModel(object): user )
- def migrate(self, name, remote_host, user=None, password=None): + def migrate(self, name, remote_host, user=None, password=None, + remove_passless_login=True): name = name.decode('utf-8') remote_host = remote_host.decode('utf-8')
if user is None: user = 'root' + user = user.encode('utf-8') + + default_passless_status = self.migration_pre_check(remote_host, user, + password)
- self.migration_pre_check(remote_host, user, password) - dest_conn = self._get_remote_libvirt_conn(remote_host, user) + if default_passless_status: + remove_passless_login = False + + dest_conn = self._get_remote_libvirt_conn(remote_host)
non_shared = self._check_if_nonshared_migration( name, @@ -1977,18 +1991,80 @@ class VMModel(object): 'dest_conn': dest_conn, 'non_shared': non_shared, 'remote_host': remote_host, - 'user': user} + 'user': user, + 'password': password, + 'remove_passless_login': remove_passless_login} task_id = AsyncTask('/plugins/kimchi/vms/%s/migrate' % name, self._migrate_task, params).id
return self.task.lookup(task_id)
+ def _remove_password_less_login(self, remote_host, user, passwd): + home_dir = '/root' if user == 'root' else '/home/%s' % user + + id_rsa_file = "%s/.ssh/id_rsa" % home_dir + id_rsa_pub_file = id_rsa_file + '.pub' + ssh_port = 22 + ssh_client = None + + def remove_id_rsa_to_remote_authorized_keys(ssh_client, id_rsa_data): + sftp_client = ssh_client.open_sftp() + ssh_dir = '%s/.ssh' % home_dir + + try: + sftp_client.chdir(ssh_dir) + except IOError: + raise OperationFailed( + "KCHVM0089E", + {'host': remote_host, 'user': user, 'sshdir': ssh_dir} + ) + file_handler = sftp_client.file( + '%s/.ssh/authorized_keys' % home_dir, + mode='r', + bufsize=1 + ) + lines = file_handler.readlines() + try: + lines.remove(id_rsa_data) + except ValueError: + pass + file_handler = sftp_client.file( + '%s/.ssh/authorized_keys' % home_dir, + mode='w', + bufsize=1 + ) + file_handler.writelines(lines) + file_handler.flush() + file_handler.close() + sftp_client.close() + + try: + self._create_root_ssh_key_if_required(user, id_rsa_pub_file, + id_rsa_file) + id_rsa_data = self._read_id_rsa_pub_file(id_rsa_pub_file) + ssh_client = self._get_ssh_client(remote_host, user, passwd, + ssh_port) + remove_id_rsa_to_remote_authorized_keys( + ssh_client, + id_rsa_data + ) + except Exception, e: + raise OperationFailed( + "KCHVM0068E", + {'host': remote_host, 'user': user, 'error': e} + ) + finally: + if ssh_client: + ssh_client.close() + def _migrate_task(self, cb, params): name = params['name'].decode('utf-8') dest_conn = params['dest_conn'] non_shared = params['non_shared'] remote_host = params['remote_host'] user = params['user'] + password = params['password'] + remove_passless_login = params['remove_passless_login']
cb('starting a migration')
@@ -2022,6 +2098,8 @@ class VMModel(object): raise OperationFailed('KCHVM0058E', {'err': e.message, 'name': name}) finally: + if remove_passless_login: + self._remove_password_less_login(remote_host, user, password) dest_conn.close()
cb('Migrate finished', True)
participants (2)
-
archus@linux.vnet.ibm.com
-
Daniel Henrique Barboza