Пример #1
0
    def _remote_init_items(self, comm_meth):
        """Return list of items to install based on communication method.

        Return (list):
            Each item is (source_path, dest_path) where:
            - source_path is the path to the source file to install.
            - dest_path is relative path under suite run directory
              at target remote.
        """
        items = []
        if comm_meth in ['ssh', 'zmq']:
            # Contact file
            items.append((get_contact_file(self.suite),
                          os.path.join(SuiteFiles.Service.DIRNAME,
                                       SuiteFiles.Service.CONTACT)))

        if comm_meth in ['zmq']:

            suite_srv_dir = get_suite_srv_dir(self.suite)
            server_pub_keyinfo = KeyInfo(KeyType.PUBLIC,
                                         KeyOwner.SERVER,
                                         suite_srv_dir=suite_srv_dir)
            client_pri_keyinfo = KeyInfo(KeyType.PRIVATE,
                                         KeyOwner.CLIENT,
                                         suite_srv_dir=suite_srv_dir)
            dest_path_srvr_public_key = os.path.join(
                SuiteFiles.Service.DIRNAME, server_pub_keyinfo.file_name)
            items.append(
                (server_pub_keyinfo.full_key_path, dest_path_srvr_public_key))
            dest_path_cli_pri_key = os.path.join(SuiteFiles.Service.DIRNAME,
                                                 client_pri_keyinfo.file_name)
            items.append(
                (client_pri_keyinfo.full_key_path, dest_path_cli_pri_key))
        return items
Пример #2
0
    def _socket_bind(self, min_port, max_port, srv_prv_key_loc=None):
        """Bind socket.

        Will use a port range provided to select random ports.

        """
        if srv_prv_key_loc is None:
            # Create new KeyInfo object for the server private key
            suite_srv_dir = get_suite_srv_dir(self.suite)
            srv_prv_key_info = KeyInfo(KeyType.PRIVATE,
                                       KeyOwner.SERVER,
                                       suite_srv_dir=suite_srv_dir)
        else:
            srv_prv_key_info = KeyInfo(KeyType.PRIVATE,
                                       KeyOwner.SERVER,
                                       full_key_path=srv_prv_key_loc)

        # create socket
        self.socket = self.context.socket(self.pattern)
        self._socket_options()

        try:
            server_public_key, server_private_key = zmq.auth.load_certificate(
                srv_prv_key_info.full_key_path)
        except (ValueError):
            raise SuiteServiceFileError(f"Failed to find server's public "
                                        f"key in "
                                        f"{srv_prv_key_info.full_key_path}.")
        except (OSError):
            raise SuiteServiceFileError(f"IO error opening server's private "
                                        f"key from "
                                        f"{srv_prv_key_info.full_key_path}.")

        if server_private_key is None:  # this can't be caught by exception
            raise SuiteServiceFileError(f"Failed to find server's private "
                                        f"key in "
                                        f"{srv_prv_key_info.full_key_path}.")

        self.socket.curve_publickey = server_public_key
        self.socket.curve_secretkey = server_private_key
        self.socket.curve_server = True

        try:
            if min_port == max_port:
                self.port = min_port
                self.socket.bind(f'tcp://*:{min_port}')
            else:
                self.port = self.socket.bind_to_random_port(
                    'tcp://*', min_port, max_port)
        except (zmq.error.ZMQError, zmq.error.ZMQBindError) as exc:
            raise CylcError(f'could not start Cylc ZMQ server: {exc}')

        if self.barrier is not None:
            self.barrier.wait()
Пример #3
0
    def _socket_connect(self, host, port, srv_public_key_loc=None):
        """Connect socket to stub."""
        suite_srv_dir = get_suite_srv_dir(self.suite)
        if srv_public_key_loc is None:
            # Create new KeyInfo object for the server public key
            srv_pub_key_info = KeyInfo(KeyType.PUBLIC,
                                       KeyOwner.SERVER,
                                       suite_srv_dir=suite_srv_dir)

        else:
            srv_pub_key_info = KeyInfo(KeyType.PUBLIC,
                                       KeyOwner.SERVER,
                                       full_key_path=srv_public_key_loc)

        self.host = host
        self.port = port
        self.socket = self.context.socket(self.pattern)
        self._socket_options()

        client_priv_key_info = KeyInfo(KeyType.PRIVATE,
                                       KeyOwner.CLIENT,
                                       suite_srv_dir=suite_srv_dir)
        error_msg = "Failed to find user's private key, so cannot connect."
        try:
            client_public_key, client_priv_key = zmq.auth.load_certificate(
                client_priv_key_info.full_key_path)
        except (OSError, ValueError):
            raise ClientError(error_msg)
        if client_priv_key is None:  # this can't be caught by exception
            raise ClientError(error_msg)
        self.socket.curve_publickey = client_public_key
        self.socket.curve_secretkey = client_priv_key

        # A client can only connect to the server if it knows its public key,
        # so we grab this from the location it was created on the filesystem:
        try:
            # 'load_certificate' will try to load both public & private keys
            # from a provided file but will return None, not throw an error,
            # for the latter item if not there (as for all public key files)
            # so it is OK to use; there is no method to load only the
            # public key.
            server_public_key = zmq.auth.load_certificate(
                srv_pub_key_info.full_key_path)[0]
            self.socket.curve_serverkey = server_public_key
        except (OSError, ValueError):  # ValueError raised w/ no public key
            raise ClientError(
                "Failed to load the suite's public key, so cannot connect.")

        self.socket.connect(f'tcp://{host}:{port}')
Пример #4
0
def remove_keys_on_client(srvd, install_target, full_clean=False):
    """Removes client authentication keys"""
    keys = {
        "client_private_key": KeyInfo(
            KeyType.PRIVATE,
            KeyOwner.CLIENT,
            suite_srv_dir=srvd),
        "client_public_key": KeyInfo(
            KeyType.PUBLIC,
            KeyOwner.CLIENT,
            suite_srv_dir=srvd,
            install_target=install_target,
            server_held=False),
    }
    # WARNING, DESTRUCTIVE. Removes old keys if they already exist.
    if full_clean:
        keys.update({"server_public_key": KeyInfo(
            KeyType.PUBLIC, KeyOwner.SERVER, suite_srv_dir=srvd)})
    for k in keys.values():
        if os.path.exists(k.full_key_path):
            os.remove(k.full_key_path)
Пример #5
0
def key_housekeeping(reg, platform=None, create=True):
    """Clean any existing authentication keys and create new ones.
        If create is set to false, keys will only be cleaned from
        server."""
    suite_srv_dir = get_suite_srv_dir(reg)
    keys = {
        "client_public_key":
        KeyInfo(KeyType.PUBLIC,
                KeyOwner.CLIENT,
                suite_srv_dir=suite_srv_dir,
                install_target=platform),
        "client_private_key":
        KeyInfo(KeyType.PRIVATE, KeyOwner.CLIENT, suite_srv_dir=suite_srv_dir),
        "server_public_key":
        KeyInfo(KeyType.PUBLIC, KeyOwner.SERVER, suite_srv_dir=suite_srv_dir),
        "server_private_key":
        KeyInfo(KeyType.PRIVATE, KeyOwner.SERVER, suite_srv_dir=suite_srv_dir)
    }
    remove_keys_on_server(keys)
    if create:
        create_server_keys(keys, suite_srv_dir)
Пример #6
0
def remote_init(install_target, rund, indirect_comm=None):
    """cylc remote-init

    Arguments:
        install_target (str): target to be initialised
        rund (str): suite run directory
        *indirect_comm (str): use indirect communication via e.g. 'ssh'
    """
    rund = os.path.expandvars(rund)
    srvd = os.path.join(rund, SuiteFiles.Service.DIRNAME)
    os.makedirs(srvd, exist_ok=True)
    client_pub_keyinfo = KeyInfo(
        KeyType.PUBLIC,
        KeyOwner.CLIENT,
        suite_srv_dir=srvd, install_target=install_target, server_held=False)

    pattern = re.compile(r"^client_\S*key$")
    for filepath in os.listdir(srvd):
        if pattern.match(filepath) and f"{install_target}" not in filepath:
            # client key for a different install target exists
            print(REMOTE_INIT_FAILED)
    try:
        remove_keys_on_client(srvd, install_target)
        create_client_keys(srvd, install_target)
    except Exception:
        # Catching all exceptions as need to fail remote init if any problems
        # with key generation.
        print(REMOTE_INIT_FAILED)
        return
    oldcwd = os.getcwd()
    os.chdir(rund)
    # Extract job.sh from library, for use in job scripts.
    extract_resources(SuiteFiles.Service.DIRNAME, ['etc/job.sh'])
    try:
        tarhandle = tarfile.open(fileobj=sys.stdin.buffer, mode='r|')
        tarhandle.extractall()
        tarhandle.close()
    finally:
        os.chdir(oldcwd)
    if indirect_comm:
        fname = os.path.join(srvd, SuiteFiles.Service.CONTACT)
        with open(fname, 'a') as handle:
            handle.write('%s=%s\n' % (
                ContactFileFields.COMMS_PROTOCOL_2, indirect_comm))
    print("KEYSTART", end='')
    with open(client_pub_keyinfo.full_key_path) as keyfile:
        print(keyfile.read(), end='KEYEND')
    print(REMOTE_INIT_DONE)
    return
Пример #7
0
def setup_keys(suite_name):
    suite_srv_dir = get_suite_srv_dir(suite_name)
    server_keys = {
        "client_public_key": KeyInfo(
            KeyType.PUBLIC,
            KeyOwner.CLIENT,
            suite_srv_dir=suite_srv_dir),
        "client_private_key": KeyInfo(
            KeyType.PRIVATE,
            KeyOwner.CLIENT,
            suite_srv_dir=suite_srv_dir),
        "server_public_key": KeyInfo(
            KeyType.PUBLIC,
            KeyOwner.SERVER,
            suite_srv_dir=suite_srv_dir),
        "server_private_key": KeyInfo(
            KeyType.PRIVATE,
            KeyOwner.SERVER,
            suite_srv_dir=suite_srv_dir)
    }
    remove_keys_on_server(server_keys)
    remove_keys_on_client(suite_srv_dir, None, full_clean=True)
    create_server_keys(server_keys, suite_srv_dir)
    create_client_keys(suite_srv_dir, None)
Пример #8
0
    def _remote_init_callback(self, proc_ctx, platform, tmphandle, curve_auth,
                              client_pub_key_dir):
        """Callback when "cylc remote-init" exits.

        Write public key for install target into client public key
        directory.
        Set remote_init__map status to REMOTE_INIT_DONE on success which
        in turn will trigger file installation to start.
        Set remote_init_map status to REMOTE_INIT_FAILED on error.

        """
        try:
            tmphandle.close()
        except OSError:  # E.g. ignore bad unlink, etc
            pass
        install_target = platform['install target']
        if proc_ctx.ret_code == 0:
            if "KEYSTART" in proc_ctx.out:
                regex_result = re.search('KEYSTART((.|\n|\r)*)KEYEND',
                                         proc_ctx.out)
                key = regex_result.group(1)
                suite_srv_dir = get_suite_srv_dir(self.suite)
                public_key = KeyInfo(KeyType.PUBLIC,
                                     KeyOwner.CLIENT,
                                     suite_srv_dir=suite_srv_dir,
                                     install_target=install_target)
                old_umask = os.umask(0o177)
                with open(public_key.full_key_path, 'w',
                          encoding='utf8') as text_file:
                    text_file.write(key)
                os.umask(old_umask)
                # configure_curve must be called every time certificates are
                # added or removed, in order to update the Authenticator's
                # state.
                curve_auth.configure_curve(domain='*',
                                           location=(client_pub_key_dir))
                self.remote_init_map[install_target] = REMOTE_INIT_DONE
                self.ready = True
                return
        # Bad status
        LOG.error(
            TaskRemoteMgmtError(TaskRemoteMgmtError.MSG_INIT, install_target,
                                ' '.join(quote(item) for item in proc_ctx.cmd),
                                proc_ctx.ret_code, proc_ctx.out, proc_ctx.err))

        self.remote_init_map[platform['install target']] = REMOTE_INIT_FAILED
        self.ready = True
Пример #9
0
def create_client_keys(srvd, install_target):
    """Create or renew authentication keys for suite 'reg' in the .service
     directory.
     Generate a pair of ZMQ authentication keys"""

    cli_pub_key = KeyInfo(KeyType.PUBLIC,
                          KeyOwner.CLIENT,
                          suite_srv_dir=srvd,
                          install_target=install_target,
                          server_held=False)
    # ZMQ keys generated in .service directory.
    # ZMQ keys need to be created with stricter file permissions, changing
    # umask default denials.
    old_umask = os.umask(0o177)  # u=rw only set as default for file creation
    client_public_full_key_path, _client_private_full_key_path = (
        zmq.auth.create_certificates(srvd, KeyOwner.CLIENT.value))

    os.rename(client_public_full_key_path, cli_pub_key.full_key_path)
    # Return file permissions to default settings.
    os.umask(old_umask)
Пример #10
0
def test_client_requires_valid_server_public_key_in_private_key_file():
    """Client should not be able to connect to host/port without
    server public key."""
    suite_name = f"test_suite-{time()}"
    port = random.choice(PORT_RANGE)
    client = ZMQSocketBase(zmq.REP, suite=suite_name)

    test_suite_srv_dir = get_suite_srv_dir(reg=suite_name)
    key_info = KeyInfo(
        KeyType.PRIVATE,
        KeyOwner.CLIENT,
        suite_srv_dir=test_suite_srv_dir)
    directory = os.path.expanduser("~/cylc-run")
    tmpdir = os.path.join(directory, suite_name)
    os.makedirs(key_info.key_path, exist_ok=True)

    _pub, _priv = zmq.auth.create_certificates(key_info.key_path, "client")

    with pytest.raises(ClientError, match=r"Failed to load the suite's public "
                                          r"key, so cannot connect."):
        client.start(HOST, port, srv_public_key_loc="fake_location")

    client.stop()
    rmtree(tmpdir, ignore_errors=True)
Пример #11
0
    def _remote_init_callback(
            self, proc_ctx, platform, tmphandle,
            curve_auth, client_pub_key_dir):
        """Callback when "cylc remote-init" exits"""
        self.ready = True
        try:
            tmphandle.close()
        except OSError:  # E.g. ignore bad unlink, etc
            pass
        self.install_target = platform['install target']
        if proc_ctx.ret_code == 0:
            if REMOTE_INIT_DONE in proc_ctx.out:
                src_path = get_suite_run_dir(self.suite)
                dst_path = get_remote_suite_run_dir(platform, self.suite)
                try:
                    process = procopen(construct_rsync_over_ssh_cmd(
                        src_path,
                        dst_path,
                        platform,
                        self.rsync_includes),
                        stdoutpipe=True,
                        stderrpipe=True,
                        universal_newlines=True)

                    out, err = process.communicate(timeout=600)
                    install_target = platform['install target']
                    if out:
                        RSYNC_LOG.info(
                            'File installation information for '
                            f'{install_target}:\n {out}')
                    if err:
                        LOG.error(
                            'File installation error on '
                            f'{install_target}:\n {err}')
                except Exception as ex:
                    LOG.error(f"Problem during rsync: {ex}")
                    self.remote_init_map[self.install_target] = (
                        REMOTE_INIT_FAILED)
                    return
            if "KEYSTART" in proc_ctx.out:
                regex_result = re.search(
                    'KEYSTART((.|\n|\r)*)KEYEND', proc_ctx.out)
                key = regex_result.group(1)
                suite_srv_dir = get_suite_srv_dir(self.suite)
                public_key = KeyInfo(
                    KeyType.PUBLIC,
                    KeyOwner.CLIENT,
                    suite_srv_dir=suite_srv_dir,
                    install_target=self.install_target
                )
                old_umask = os.umask(0o177)
                with open(
                        public_key.full_key_path,
                        'w', encoding='utf8') as text_file:
                    text_file.write(key)
                os.umask(old_umask)
                # configure_curve must be called every time certificates are
                # added or removed, in order to update the Authenticator's
                # state.
                curve_auth.configure_curve(
                    domain='*', location=(client_pub_key_dir))
            for status in (REMOTE_INIT_DONE, REMOTE_INIT_NOT_REQUIRED):
                if status in proc_ctx.out:
                    # Good status
                    LOG.debug(proc_ctx)
                    self.remote_init_map[self.install_target] = status
                    return
        # Bad status
        LOG.error(TaskRemoteMgmtError(
            TaskRemoteMgmtError.MSG_INIT,
            platform['install target'], ' '.join(
                quote(item) for item in proc_ctx.cmd),
            proc_ctx.ret_code, proc_ctx.out, proc_ctx.err))
        LOG.error(proc_ctx)
        self.remote_init_map[platform['install target']] = REMOTE_INIT_FAILED
Пример #12
0
def remote_init(install_target, rund, *dirs_to_symlink):
    """cylc remote-init

    Arguments:
        install_target (str): target to be initialised
        rund (str): suite run directory
        dirs_to_symlink (list): directories to be symlinked in form
        [directory=symlink_location, ...]
    """
    rund = os.path.expandvars(rund)
    for item in dirs_to_symlink:
        key, val = item.split("=", 1)
        if key == 'run':
            dst = rund
        else:
            dst = os.path.join(rund, key)
        src = os.path.expandvars(val)
        if '$' in src:
            print(REMOTE_INIT_FAILED)
            print(f'Error occurred when symlinking.'
                  f' {src} contains an invalid environment variable.')
            return
        make_symlink(src, dst)
    srvd = os.path.join(rund, SuiteFiles.Service.DIRNAME)
    os.makedirs(srvd, exist_ok=True)

    client_pub_keyinfo = KeyInfo(KeyType.PUBLIC,
                                 KeyOwner.CLIENT,
                                 suite_srv_dir=srvd,
                                 install_target=install_target,
                                 server_held=False)
    # Check for existence of client key dir (should only exist on server)
    # Fail if one exists - this may occur on mis-configuration of install
    # target in global.cylc
    client_key_dir = os.path.join(
        srvd, f"{KeyOwner.CLIENT.value}_{KeyType.PUBLIC.value}_keys")
    if os.path.exists(client_key_dir):
        print(REMOTE_INIT_FAILED)
        print(f"Unexpected key directory exists: {client_key_dir}"
              " Check global.cylc install target is configured correctly "
              "for this platform.")
        return
    pattern = re.compile(r"^client_\S*key$")
    for filepath in os.listdir(srvd):
        if pattern.match(filepath) and f"{install_target}" not in filepath:
            # client key for a different install target exists
            print(REMOTE_INIT_FAILED)
            print(f"Unexpected authentication key \"{filepath}\" exists. "
                  "Check global.cylc install target is configured correctly "
                  "for this platform.")
            return
    try:
        remove_keys_on_client(srvd, install_target)
        create_client_keys(srvd, install_target)
    except Exception:
        # Catching all exceptions as need to fail remote init if any problems
        # with key generation.
        print(REMOTE_INIT_FAILED)
        return
    oldcwd = os.getcwd()
    os.chdir(rund)
    # Extract job.sh from library, for use in job scripts.
    extract_resources(SuiteFiles.Service.DIRNAME, ['etc/job.sh'])
    try:
        tarhandle = tarfile.open(fileobj=sys.stdin.buffer, mode='r|')
        tarhandle.extractall()
        tarhandle.close()
    finally:
        os.chdir(oldcwd)
    print("KEYSTART", end='')
    with open(client_pub_keyinfo.full_key_path) as keyfile:
        print(keyfile.read(), end='KEYEND')
    print(REMOTE_INIT_DONE)
    return