PK ! W`E E red_connector_ssh/__init__.pyfrom red_connector_ssh.version import VERSION __version__ = VERSION PK ! oN N red_connector_ssh/convert_key.pyimport binascii import os import sys from argparse import ArgumentParser import paramiko WARNING = 'WARNING: Do not use private keys, if you do not trust the executor of the experiment. There is no problem' \ ' of using private keys for "faice agent red".' DESCRIPTION = 'Converts a given private key file into an entry for a red connector. This is useful, if you want to ' \ 'enable ssh access in an experiment via a private key.\n{}'.format(WARNING) DEFAULT_OUTPUT_FILE = 'variables.yml' DEFAULT_VARIABLE_KEY = 'privateKey' DEFAULT_PASSPHRASE_KEY = 'passphrase' def main(): parser = ArgumentParser(description=DESCRIPTION) attach_args(parser) args = parser.parse_args() print(WARNING) key_file_path = args.key_file try: key_file = open(key_file_path, 'r') except FileNotFoundError: print('Error: File "{}" not found'.format(key_file_path)) return 1 # validate key_file if not args.no_validation: try: paramiko.RSAKey.from_private_key(key_file, password=args.passphrase) key_file.seek(0) except paramiko.ssh_exception.PasswordRequiredException: print('The given private key file needs a passphrase for validation (see --help). If you want to' ' bypass validation run again with "--no-validation"', file=sys.stderr) return 2 except binascii.Error: print('The given private key file "{}" is not valid.'.format(key_file_path)) return 3 except paramiko.ssh_exception.SSHException as e: print(str(e)) return 4 # read key_file lines = [] for line in key_file: lines.append(line.strip()) # write everything to string key_string = '\\n'.join(lines) # write to output_file with open(os.path.expanduser(args.output_file), 'a') as output_file: output_file.write('{}: \"{}\"\n'.format(args.variable_key, key_string)) if args.passphrase: output_file.write('{}: \"{}\"\n'.format(DEFAULT_PASSPHRASE_KEY, args.passphrase)) print('Success: output file has been written to "{}"'.format(args.output_file)) def attach_args(parser): parser.add_argument( 'key_file', action='store', type=str, metavar='KEYFILE', help='The private key file to convert.' ) parser.add_argument( '--format', action='store', type=str, metavar='FORMAT', choices=['json', 'yaml', 'yml'], default='yaml', help='Specify FORMAT for generated data as one of [json, yaml, yml]. Default is yaml.' ) parser.add_argument( '--passphrase', action='store', type=str, default=None, help='Passphrase to validate the given key file. If no validation is wanted, you can bypass with' '--no-validation.' ) parser.add_argument( '--no-validation', action='store_true', help='Bypass key validation.' ) parser.add_argument( '--output-file', action='store', type=str, default=DEFAULT_OUTPUT_FILE, help='The output file to write. Default is "{}".'.format(DEFAULT_OUTPUT_FILE) ) parser.add_argument( '--variable-key', action='store', type=str, default=DEFAULT_VARIABLE_KEY, help='The variable key in the output file. Default is "{}".'.format(DEFAULT_VARIABLE_KEY) ) if __name__ == '__main__': exit(main()) PK ! | red_connector_ssh/main.pyfrom cc_connector_cli.connector_cli import run_connector from red_connector_ssh.sftp import Sftp from red_connector_ssh.version import VERSION def main(): run_connector(Sftp, version=VERSION) if __name__ == '__main__': main() PK ! tܞ) ) red_connector_ssh/sftp.pyimport os import jsonschema import shutil import stat import tempfile from scp import SCPClient, SCPException from paramiko import SSHClient, AutoAddPolicy, RSAKey DEFAULT_DIRECTORY_MODE = stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH \ | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH sftp_schema = { 'type': 'object', 'properties': { 'host': {'type': 'string'}, 'port': {'type': 'integer'}, 'username': {'type': 'string'}, 'password': {'type': 'string'}, 'privateKey': {'type': 'string'}, 'passphrase': {'type': 'string'}, 'fileDir': {'type': 'string'}, 'fileName': {'type': 'string'} }, 'additionalProperties': False, 'required': ['host', 'username', 'fileDir', 'fileName'] } sftp_directory_schema = { 'type': 'object', 'properties': { 'host': {'type': 'string'}, 'port': {'type': 'integer'}, 'username': {'type': 'string'}, 'password': {'type': 'string'}, 'privateKey': {'type': 'string'}, 'passphrase': {'type': 'string'}, 'dirName': {'type': 'string'}, }, 'additionalProperties': False, 'required': ['host', 'username', 'dirName'] } def _create_temp_file(content): """ Creates a temporary file that resists in memory. :param content: :return: """ tmp_file = tempfile.SpooledTemporaryFile(max_size=1000000, mode='w+') tmp_file.write(content) tmp_file.seek(0) return tmp_file def _create_ssh_client(host, username, port=22, password=None, private_key=None, passphrase=None): """ Creates and returns a connected SSHClient. If a password is supplied the connection is created using this password. If no password is supplied a valid private key must be present. If this private key is encrypted the associated passphrase must be supplied. :param host: The host to connect to :param username: The username which is used to connect to the ssh host :param port: The port number to connect to. Default is 22 :param password: The password to authenticate :param private_key: A valid private RSA key as string :param passphrase: A passphrase to decrypt the private key, if the private key is encrypted :raise Exception: If neither password nor private_key is given :raise SSHException: If the given private_key, username or password is invalid :raise socket.gaierror: If the given host is not known :return: A connected paramiko.SSHClient """ client = SSHClient() client.set_missing_host_key_policy(AutoAddPolicy()) if password is not None: client.connect( host, port=port, username=username, password=password ) elif private_key is not None: key_file = _create_temp_file(private_key) pkey = RSAKey.from_private_key(key_file, password=passphrase) key_file.close() client.connect(host, username=username, pkey=pkey) else: raise Exception('At least password or private_key must be present.') return client class Sftp: @staticmethod def receive(access, internal): host = access['host'] port = access.get('port', 22) username = access['username'] password = access.get('password') file_dir = access['fileDir'] file_name = access['fileName'] private_key = access.get('privateKey') passphrase = access.get('passphrase') with _create_ssh_client(host, username, port, password, private_key, passphrase) as client: with client.open_sftp() as sftp: remote_file_path = os.path.join(file_dir, file_name) sftp.get(remote_file_path, internal['path']) @staticmethod def receive_validate(access): jsonschema.validate(access, sftp_schema) # At least password or privateKey must be present if ('password' not in access) and ('privateKey' not in access): raise Exception('At least "password" or "privateKey" must be present.') @staticmethod def receive_cleanup(internal): """ Removes the given file. :param internal: A dictionary containing a path to the file to remove. """ os.remove(internal['path']) @staticmethod def _ssh_mkdir(sftp, remote_directory): # source http://stackoverflow.com/a/14819803 if remote_directory == '/': sftp.chdir('/') return if remote_directory == '': return try: sftp.chdir(remote_directory) except IOError: dirname, basename = os.path.split(remote_directory.rstrip('/')) Sftp._ssh_mkdir(sftp, dirname) sftp.mkdir(basename) sftp.chdir(basename) @staticmethod def send(access, internal): host = access['host'] port = access.get('port', 22) username = access['username'] password = access.get('password') file_dir = access['fileDir'] file_name = access['fileName'] private_key = access.get('privateKey') passphrase = access.get('passphrase') remote_file_path = os.path.join(file_dir, file_name) with _create_ssh_client(host, username, port, password, private_key, passphrase) as client: with client.open_sftp() as sftp: Sftp._ssh_mkdir(sftp, file_dir) sftp.put( internal['path'], os.path.join(remote_file_path) ) @staticmethod def send_validate(access): try: jsonschema.validate(access, sftp_schema) except jsonschema.ValidationError as e: if e.context: raise Exception(e.context) else: raise Exception(str(e)) @staticmethod def fetch_directory(listing, scp_client, base_directory, remote_directory, path="./"): """ Fetches the directories given in the listing using the given scp_client. The read/write/execute permissions of the remote and local directories may differ. :param listing: A complete listing with complete urls for every containing file. :param scp_client: A SCPClient, that has to be connected to a host. :param base_directory: The path to the base directory, where to create the fetched files and directories. This base directory should already be present on the local filesystem. :param remote_directory: The path to the remote base directory from where to fetch the subfiles and directories. :param path: A path specifying which subdirectory of remove_directory should be fetched and where to place it under base_directory. The files are fetched from os.path.join(remote_directory, path) and placed under os.path.join(base_directory, path) :raise Exception: If the listing specifies a file or directory which is not present on the remote host """ for sub in listing: sub_path = os.path.normpath(os.path.join(path, sub['basename'])) remote_path = os.path.normpath(os.path.join(remote_directory, sub_path)) local_path = os.path.normpath(os.path.join(base_directory, sub_path)) if sub['class'] == 'File': try: scp_client.get(remote_path=remote_path, local_path=local_path) except SCPException as e: raise Exception('The remote file under "{}" could not be transferred.\n{}'. format(remote_path, str(e))) elif sub['class'] == 'Directory': os.mkdir(local_path, DEFAULT_DIRECTORY_MODE) listing = sub.get('listing') if listing: Sftp.fetch_directory(listing, scp_client, base_directory, remote_directory, sub_path) @staticmethod def receive_directory(access, internal, listing): """ Fetches a directory from a ssh server and stores at under the path given by internal['path'] :param access: A dictionary containing access information. Has the following keys - 'host': The host to connect to - 'username': A username that is used to perform authentication - 'password': A password that is used to perform authentication - 'dirName': The name of the directory to fetch :param internal: A dictionary containing information about where to put the directory content :param listing: Listing of subfiles and subdirectories which are contained by the directory given in access. Specified like a listing in the common workflow language :raise Exception: If the remote host is not accessible or the listing specifies a directory or a file, which is not present on the remote machine. """ host = access['host'] port = access.get('port', 22) username = access['username'] password = access.get('password') private_key = access.get('privateKey') passphrase = access.get('passphrase') remote_path = access['dirName'] with _create_ssh_client(host, username, port, password, private_key, passphrase) as ssh_client: local_path = internal['path'] with SCPClient(ssh_client.get_transport()) as scp_client: if listing is None: scp_client.get(remote_path, local_path, recursive=True) else: os.mkdir(local_path, DEFAULT_DIRECTORY_MODE) Sftp.fetch_directory(listing, scp_client, local_path, remote_path) @staticmethod def receive_directory_validate(access): try: jsonschema.validate(access, sftp_directory_schema) except jsonschema.ValidationError as e: if e.context: raise Exception(e.context) else: raise Exception(str(e)) # At least password or privateKey must be present if ('password' not in access) and ('privateKey' not in access): raise Exception('At least "password" or "privateKey" must be present.') @staticmethod def receive_directory_cleanup(internal): """ Removes the given directory. :param internal: A dictionary containing a path to the directory to remove. """ shutil.rmtree(internal['path']) PK ! ar$ red_connector_ssh/version.pyVERSION = '0.5' PK !HљJ x 0 red_connector_ssh-0.5.dist-info/entry_points.txtN+I/N.,()*JM &gE"@<+S]ZTRZE=TE