def create_ssh_key_pair(name): """Create an SSH private/public key pair in ~/.ssh/ If an SSH key pair exists with "name" then the private key path is returned *without* creating anything new. Args: name (str): Filename of private key file Returns: str: Private ssh key path Raises: UserException: If ssh-keygen command fails """ log = logger.getlogger() ssh_dir = os.path.join(Path.home(), ".ssh") private_key_path = os.path.join(ssh_dir, name) if not os.path.isdir(ssh_dir): os.mkdir(ssh_dir, mode=0o700) if os.path.isfile(private_key_path): log.info(f'SSH key \'{private_key_path}\' already exists, continuing') else: print(bold(f'Creating SSH key \'{private_key_path}\'')) cmd = ('ssh-keygen -t rsa -b 4096 ' '-C "Generated by Power-Up Software Installer" ' f'-f {private_key_path} -N ""') resp, err, rc = sub_proc_exec(cmd, shell=True) if str(rc) != "0": msg = 'ssh-keygen failed:\n{}'.format(resp) log.debug(msg) raise UserException(msg) return private_key_path
def sync(self): self.log.info(f'Syncing {self.repo_name}') self.log.info( 'This can take many minutes or hours for large repositories\n') cmd = (f'reposync -a {self.arch} -r {self.repo_id} -p' f'{os.path.dirname(self.repo_dir)} -l -m') rc = sub_proc_display(cmd) if rc != 0: self.log.error(bold(f'\nFailed {self.repo_name} repo sync. {rc}')) raise UserException else: self.log.info(f'{self.repo_name} sync finished successfully')
def copy_ssh_key_pair_to_hosts(private_key_path, software_hosts_file_path, global_pass=None): """Copy an SSH public key into software hosts authorized_keys files TODO: detailed description Args: private_key_path (str) : Filename of private key file software_hosts_file_path (str): Path to software inventory file global_pass (str, optional): Global client default SSH password Returns: bool: True iff rc of all commands are "0" """ hosts_list = _validate_inventory_count(software_hosts_file_path, 0) all_zero_returns = True hostvars = get_ansible_hostvars(software_hosts_file_path) for host in hosts_list: print(bold(f'Copy SSH Public Key to {host}')) cmd = f'ssh-copy-id -i {private_key_path} ' if "ansible_port" in hostvars[host]: cmd += f'-p {hostvars[host]["ansible_port"]} ' if "ansible_ssh_common_args" in hostvars[host]: cmd += f'{hostvars[host]["ansible_ssh_common_args"]} ' cmd += f'{hostvars[host]["ansible_user"]}@{host}' if 'ansible_ssh_pass' not in hostvars[host]: cmd = f'SSHPASS=\'{global_pass}\' sshpass -e ' + cmd resp, err, rc = sub_proc_exec(cmd, shell=True) if rc != 0: all_zero_returns = False print(err) return all_zero_returns
def get_ansible_inventory(): log = logger.getlogger() inventory_choice = None dynamic_inventory_path = get_dynamic_inventory_path() software_hosts_file_path = ( os.path.join(get_playbooks_path(), 'software_hosts')) heading1("Software hosts inventory setup\n") dynamic_inventory = None # If dynamic inventory contains clients prompt user to use it if (dynamic_inventory is not None and len(set(_get_hosts_list(dynamic_inventory)) - set(['deployer', 'localhost'])) > 0): print("Ansible Dynamic Inventory found:") print("--------------------------------") print(_get_groups_hosts_string(dynamic_inventory)) print("--------------------------------") validate_software_inventory(dynamic_inventory) if click.confirm('Do you want to use this inventory?'): print("Using Ansible Dynamic Inventory") inventory_choice = dynamic_inventory_path else: print("NOT using Ansible Dynamic Inventory") # If dynamic inventory has no hosts or user declines to use it if inventory_choice is None: while True: # Check if software inventory file exists if os.path.isfile(software_hosts_file_path): print("Software inventory file found at '{}':" .format(software_hosts_file_path)) # If no software inventory file exists create one using template else: rlinput("Press enter to create client node inventory") _create_new_software_inventory(software_hosts_file_path) # If still no software inventory file exists prompt user to # exit (else start over to create one). if not os.path.isfile(software_hosts_file_path): print("No inventory file found at '{}'" .format(software_hosts_file_path)) if click.confirm('Do you want to exit the program?'): sys.exit(1) else: continue # Menu items can modified to show validation results continue_msg = 'Continue with current inventory' edit_msg = 'Edit inventory file' exit_msg = 'Exit program' ssh_config_msg = 'Configure Client Nodes for SSH Key Access' menu_items = [] # Validate software inventory inv_count = len(_validate_inventory_count(software_hosts_file_path, 0)) print(f'Validating software inventory ({inv_count} nodes)...') if validate_software_inventory(software_hosts_file_path): print(bold("Validation passed!")) else: print(bold("Unable to complete validation")) continue_msg = ("Continue with inventory as-is - " "WARNING: Validation incomplete") menu_items.append(ssh_config_msg) # Prompt user menu_items += [continue_msg, edit_msg, exit_msg] choice, item = get_selection(menu_items) print(f'Choice: {choice} Item: {item}') if item == ssh_config_msg: configure_ssh_keys(software_hosts_file_path) elif item == continue_msg: print("Using '{}' as inventory" .format(software_hosts_file_path)) inventory_choice = software_hosts_file_path break elif item == edit_msg: click.edit(filename=software_hosts_file_path) elif item == exit_msg: sys.exit(1) if inventory_choice is None: log.error("Software inventory file is required to continue!") sys.exit(1) log.debug("User software inventory choice: {}".format(inventory_choice)) return inventory_choice
def copy_ssh_key_pair_to_user_dir(private_key_path): """Copy an SSH private/public key pair into the user's ~/.ssh dir This function is useful when a key pair is created as root user (e.g. using 'sudo') but should also be available to the user for direct 'ssh' calls. If the private key is already in the user's ~/.ssh directory nothing is done. Args: private_key_path (str) : Filename of private key file Returns: str: Path to user copy of private key """ public_key_path = private_key_path + '.pub' user_name, user_home_dir = get_user_and_home() user_ssh_dir = os.path.join(user_home_dir, ".ssh") if user_ssh_dir not in private_key_path: user_private_key_path = os.path.join( user_ssh_dir, os.path.basename(private_key_path)) user_public_key_path = user_private_key_path + '.pub' user_uid = pwd.getpwnam(user_name).pw_uid user_gid = grp.getgrnam(user_name).gr_gid if not os.path.isdir(user_ssh_dir): os.mkdir(user_ssh_dir, mode=0o700) os.chown(user_ssh_dir, user_uid, user_gid) # Never overwrite an existing private key file! already_copied = False while os.path.isfile(user_private_key_path): # If key pair already exists no need to do anything if (filecmp.cmp(private_key_path, user_private_key_path) and filecmp.cmp(public_key_path, user_public_key_path)): already_copied = True break else: user_private_key_path += "_powerup" user_public_key_path = user_private_key_path + '.pub' if already_copied: print(f'\'{private_key_path}\' already copied to ' f'\'{user_private_key_path}\'') else: print(bold(f'Copying \'{private_key_path}\' to ' f'\'{user_private_key_path}\' for unprivileged use')) copyfile(private_key_path, user_private_key_path) copyfile(public_key_path, user_public_key_path) os.chown(user_private_key_path, user_uid, user_gid) os.chmod(user_private_key_path, 0o600) os.chown(user_public_key_path, user_uid, user_gid) os.chmod(user_public_key_path, 0o644) else: user_private_key_path = private_key_path return user_private_key_path
def configure_ssh_keys(software_hosts_file_path): """Configure SSH keys for Ansible software hosts Scan for SSH key pairs in home directory, and if called using 'sudo' also in "login" user's home directory. Allow user to create a new SSH key pair if 'default_ssh_key_name' doesn't already exist. If multiple choices are available user will be prompted to choose. Selected key pair is copied into "login" user's home '.ssh' directory if necessary. Selected key pair is then copied to all hosts listed in 'software_hosts' file via 'ssh-copy-id', and finally assigned to the 'ansible_ssh_private_key_file' var in the 'software_hosts' '[all:vars]' section. Args: software_hosts_file_path (str): Path to software inventory file """ log = logger.getlogger() default_ssh_key_name = "powerup" ssh_key_options = get_existing_ssh_key_pairs(no_root_keys=True) user_name, user_home_dir = get_user_and_home() if os.path.join(user_home_dir, ".ssh", default_ssh_key_name) not in ssh_key_options: ssh_key_options.insert(0, 'Create New "powerup" Key Pair') if len(ssh_key_options) == 1: item = ssh_key_options[0] elif len(ssh_key_options) > 1: print(bold("\nSelect an SSH key to use:")) choice, item = get_selection(ssh_key_options) if item == 'Create New "powerup" Key Pair': ssh_key = create_ssh_key_pair(default_ssh_key_name) else: ssh_key = item ssh_key = copy_ssh_key_pair_to_user_dir(ssh_key) add_software_hosts_global_var( software_hosts_file_path, "ansible_ssh_common_args='-o StrictHostKeyChecking=no'") hostvars = get_ansible_hostvars(software_hosts_file_path) run = True while run: global_user = None global_pass = None header_printed = False header_msg = bold('\nGlobal client SSH login credentials required') for host in _validate_inventory_count(software_hosts_file_path, 0): if global_user is None and 'ansible_user' not in hostvars[host]: print(header_msg) header_printed = True global_user = rlinput('username: '******'ansible_user={global_user}') if (global_pass is None and 'ansible_ssh_pass' not in hostvars[host]): if not header_printed: print(header_msg) global_pass = getpass('password: '******'Retry', 'Continue', 'Exit']) if choice == "1": pass elif choice == "2": run = False elif choice == "3": log.debug('User chooses to exit.') sys.exit('Exiting') else: print() log.info("SSH key successfully copied to all hosts\n") run = False add_software_hosts_global_var(software_hosts_file_path, f'ansible_ssh_private_key_file={ssh_key}')