Пример #1
0
def request(path, query=None, body=None, method='get', config=None):
    query = query or {}

    if isinstance(query, dict):
        query = urlencode(query)

    if '?' in path:
        path += '&' + query
    else:
        path += '?' + query

    config = read_home_config() if config is None else config

    if method == 'get' and body is not None:
        method = 'post'

    ssh_stream = create_ssh_stream(config)
    stdin, stdout, stderr = ssh_stream.exec_command('api ' + method + ' ' + simplejson.dumps(path))

    if body is not None:
        input = six.b(simplejson.dumps(body))
        stdin.write(input)
        stdin.flush()
        stdin.channel.shutdown_write()

    stdout = drain_stream(stdout)
    stderr = drain_stream(stderr)

    if len(stderr) > 0:
        if hasattr(stderr, 'decode'):
            stderr = stderr.decode('utf-8')

        raise ApiError('Could not request api: ' + config['host'] + path, stderr)

    return stdout
Пример #2
0
    def connect(self, channel):
        """
        In the write-thread we detect that no connection is living anymore and try always again.
        Up to the 3 connection try, we report to user. We keep trying but in silence.
        Also, when more than 10 connection tries are detected, we delay extra 15 seconds.
        """
        if self.connection_tries > 10:
            time.sleep(10)

        if self.in_connecting[channel]:
            return False

        self.in_connecting[channel] = True

        self.logger.debug('[%s] Wanna connect ...' % (channel, ))

        try:
            if self.is_connected(channel) or self.online is False:
                if self.is_connected(channel):
                    self.logger.debug('[%s] Already connected' % (channel, ))
                if self.online is False:
                    self.logger.debug('[%s] self.online=False' % (channel, ))

                return True

            self.channel_lock[channel].acquire()

            self.connected[channel] = None
            self.registered[channel] = None
            self.ssh_stream[channel] = False
            self.ssh_channel[channel] = False
            messages = None
            stderrdata = ''

            try:
                if not self.ssh_stream[channel]:
                    self.logger.debug('[%s] Open ssh connection' % (channel, ))
                    self.ssh_stream[channel] = create_ssh_stream(self.config, exit_on_failure=False)

                self.logger.debug('[%s] open channel' % (channel, ))

                self.ssh_channel[channel] = self.ssh_stream[channel].get_transport().open_session()
                self.ssh_channel[channel].exec_command('stream')
            except (KeyboardInterrupt, SystemExit):
                raise
            except Exception as e:
                self.connected[channel] = False
                self.registered[channel] = False
                self.logger.debug('[%s] connection failed: %s'  % (channel, str(e)))
                return False
            finally:
                self.channel_lock[channel].release()

            if self.ssh_channel[channel]:
                messages = self.wait_for_at_least_one_message(channel)

            if not messages:
                stderrdata = self.ssh_channel[channel].recv_stderr().decode("utf-8").strip()
                self.connected[channel] = False
                self.registered[channel] = False
            else:
                self.logger.debug('[%s] opened and received %d messages' % (channel, len(messages)))
                self.connected[channel] = True
                self.registered[channel] = self.on_connect(self.was_connected_once[channel], channel)
                self.connected_since[channel] = time.time()

                if channel == '' and self.registered[channel] and self.was_connected_once[channel]:
                    self.logger.info("Successfully reconnected.")

            if not self.registered[channel]:
                # make sure to close channel and connection first
                try:
                    self.ssh_channel[channel] and self.ssh_channel[channel].close()
                except: pass

                try:
                    self.ssh_stream[channel] and self.ssh_stream[channel].close()
                except: pass

                self.logger.debug("[%s] Client: registration failed. stderrdata: %s" % (channel, stderrdata))
                self.connected[channel] = False

                try:
                    self.logger.debug('[%s] Client: ssh_tream close due to registration failure' % (channel, ))
                    self.ssh_stream[channel].close()
                except (KeyboardInterrupt, SystemExit):
                    raise

                self.connection_tries += 1
                if not self.was_connected_once[channel] and self.go_offline_on_first_failed_attempt:
                    # initial try needs to be online, otherwise we go offline
                    self.go_offline()

                if stderrdata:
                    if 'Connection refused' not in stderrdata and 'Permission denied' not in stderrdata:
                        self.logger.error(stderrdata)

                if 'Permission denied' in stderrdata:
                    if self.connection_tries < 3:
                        self.logger.warning("Access denied. Did you setup your SSH public key correctly "
                                            "and saved it in your AETROS Trainer user account?")

                    self.close()
                    sys.exit(1)

                self.connection_error(channel, "Connection error during connecting to %s: %s" % (self.host, str(stderrdata)))
            else:
                self.was_connected_once[channel] = True

        except Exception as error:
            self.connection_error(channel, error)
        finally:
            self.in_connecting[channel] = False

        return self.is_connected(channel)
Пример #3
0
    def main(self, args):
        import aetros.const

        parser = argparse.ArgumentParser(
            formatter_class=argparse.RawTextHelpFormatter,
            prog=aetros.const.__prog__ + ' authenticate',
            description=
            'Authenticates the machine with a new pair of SSH keys with a user account.'
        )

        parsed_args = parser.parse_args(args)

        home_config = read_home_config()
        host = home_config['host']

        installed_key = get_ssh_key_for_host(host)
        key_exists_and_valid = False
        if installed_key:
            try:
                create_ssh_stream(home_config, exit_on_failure=False)
                key_exists_and_valid = True
            except Exception:
                pass

        if key_exists_and_valid:
            choice = six.moves.input(
                "You have already configured a valid SSH (ssk_key: " +
                installed_key + ") "
                "for " + host +
                ".\nWant to create a new key? (y/N): ").lower()
            if choice != 'y' and choice != 'yes':
                print("Aborted.")
                sys.exit(1)

        ssh_key = paramiko.RSAKey.generate(4096)
        ssh_key_private = ssh_key.key.private_bytes(
            serialization.Encoding.PEM,
            serialization.PrivateFormat.TraditionalOpenSSL,
            serialization.NoEncryption()).decode()
        ssh_key_public = 'rsa ' + ssh_key.get_base64()

        fingerprint = hashlib.md5(ssh_key.__str__()).hexdigest()
        fingerprint = ':'.join(
            a + b for a, b in zip(fingerprint[::2], fingerprint[1::2]))

        token = api.http_request('machine-token', None, {
            'host': socket.getfqdn(),
            'key': ssh_key_public
        })

        print(
            "Open following link and login to confirm this machine's SSH key in your account."
        )
        print("Public Key Fingerprint: MD5:" + fingerprint)
        print("\n   https://" + host + "/confirm-machine/" + token)
        print("\nWaiting for confirmation ...")

        while True:
            time.sleep(3)
            response = api.http_request('machine-token/authorized?id=' + token,
                                        method='post')
            if response['status'] == 'confirmed':
                print(
                    "\n" + response['username'] +
                    ' confirmed the public key. Test with "aetros id" or "ssh git@'
                    + host + '".')
                private_key_path = os.path.expanduser('~/.ssh/aetros_' +
                                                      response['username'] +
                                                      '_rsa')
                public_key_path = os.path.expanduser('~/.ssh/aetros_' +
                                                     response['username'] +
                                                     '_rsa.pub')

                if not os.path.exists(os.path.dirname(private_key_path)):
                    os.makedirs(os.path.dirname(private_key_path))

                with open(private_key_path, 'w') as f:
                    f.write(ssh_key_private)

                with open(public_key_path, 'w') as f:
                    f.write(ssh_key_public)

                os.chmod(private_key_path, 0o600)
                os.chmod(public_key_path, 0o600)

                ssh_config_path = os.path.expanduser('~/.ssh/config')

                if not os.path.exists(os.path.dirname(ssh_config_path)):
                    os.makedirs(os.path.dirname(ssh_config_path))

                host_section = 'host ' + host + '\n'
                identity_section = '    IdentityFile ~/.ssh/aetros_' + response[
                    'username'] + '_rsa\n'

                if os.path.exists(ssh_config_path):
                    import re
                    regex = re.compile(r"^host\s+" + re.escape(host) + '\s*',
                                       re.IGNORECASE | re.MULTILINE)
                    with open(ssh_config_path, 'r+') as f:
                        config = f.read()

                        if regex.match(config):
                            config = regex.sub(host_section + identity_section,
                                               config, 1)
                        else:
                            config = host_section + identity_section + config

                        f.seek(0)
                        f.write(config)
                else:
                    with open(ssh_config_path, 'w') as f:
                        f.write(host_section + identity_section)

                print("Private key " + private_key_path +
                      " installed in ~/.ssh/config for " + host + ".\n")
                user = api.user()
                print("Key installed of account %s (%s)." %
                      (user['username'], user['name']))
                sys.exit(0)
            if response['status'] == 'expired':
                print("Token expired.")
                sys.exit(1)
Пример #4
0
    def diff_objects(self, latest_commit_sha):
        """
                Push all changes to origin, based on objects, not on commits.
                Important: Call this push after every new commit, or we lose commits.
                """
        base = ['git', '--bare', '--git-dir', self.git_path]

        object_shas = []
        summary = {'commits': [], 'trees': [], 'files': []}

        def read_parents_and_tree_from(commit):
            if commit in self.synced_object_shas or commit in object_shas:
                # this commit has already been synced or read
                return None, None

            self.synced_object_shas[commit] = True
            summary['commits'].append(commit)
            object_shas.append(commit)

            object_content = subprocess.check_output(base + ['cat-file', '-p', commit]).decode('utf-8').strip()
            parents = []
            tree = ''
            for line in object_content.splitlines():
                if line.startswith('tree '):
                    tree = line[len('tree '):]
                if line.startswith('parent '):
                    parents.append(line[len('parent '):])

            return parents, tree

        def collect_files_from_tree(tree):
            if tree in self.synced_object_shas or tree in object_shas:
                # we have exactly this tree already synced or read, meaning all its objects as well
                return

            self.synced_object_shas[tree] = True
            summary['trees'].append(tree)
            object_shas.append(tree)

            object_content = subprocess.check_output(base + ['ls-tree', '-r', '-t', tree]).decode('utf-8').strip()

            for line in object_content.splitlines():
                exploded = line.split(' ')

                if len(exploded) < 3:
                    sys.stderr.write("Error: Wrong line format of ls-tree for %s: %s\n" % (tree, line,))
                    sys.exit(1)

                object_to_add = str(exploded[2][:40])
                path = str(exploded[2][41:])

                if object_to_add in self.synced_object_shas or object_to_add in object_shas:
                    # have it already in the list or already synced
                    continue

                object_shas.append(object_to_add)
                self.synced_object_shas[object_to_add] = True
                summary['files'].append([object_to_add, path])

        commits_to_check = [latest_commit_sha]

        while len(commits_to_check):
            sha = commits_to_check.pop(0)
            parents, tree = read_parents_and_tree_from(sha)

            if parents:
                for parent in parents:
                    if parent not in commits_to_check:
                        commits_to_check.append(parent)

            if tree:
                collect_files_from_tree(tree)

        is_debug2() and self.logger.debug("shas_to_check %d: %s " % (len(object_shas), str(object_shas),))

        if not object_shas:
            return [], summary

        try:
            is_debug2() and self.logger.debug("Do git-cat-file-check.sh")

            ssh_stream = create_ssh_stream(read_home_config(), exit_on_failure=False)
            channel = ssh_stream.get_transport().open_session()
            channel.exec_command('git-cat-file-check.sh "%s"' % (self.model_name + '.git',))
            channel.sendall('\n'.join(object_shas))
            channel.shutdown_write()

            def readall(c):
                content = b''
                while True:
                    try:
                        chunk = c.recv(1024)
                        if chunk == b'':
                            break
                        content += chunk
                    except (KeyboardInterrupt, SystemExit):
                        return

                return content

            missing_objects = readall(channel).decode('utf-8').splitlines()
            channel.close()
            ssh_stream.close()

            # make sure we have in summary only SHAs we actually will sync
            for stype in six.iterkeys(summary):
                ids = summary[stype][:]
                for sha in ids:
                    if stype == 'files':
                        if sha[0] not in missing_objects:
                            summary[stype].remove(sha)
                    else:
                        if sha not in missing_objects:
                            summary[stype].remove(sha)

            return missing_objects, summary
        except (KeyboardInterrupt, SystemExit):
            raise
        except Exception as e:
            self.logger.error("Failed to generate diff_objects: %s" % (str(e),))
            for sha in object_shas:
                if sha in self.synced_object_shas:
                    del self.synced_object_shas[sha]
            return None, None
Пример #5
0
    def connect(self, channel):
        """
        In the write-thread we detect that no connection is living anymore and try always again.
        Up to the 3 connection try, we report to user. We keep trying but in silence.
        Also, when more than 10 connection tries are detected, we delay extra 15 seconds.
        """
        if self.connection_tries > 10:
            time.sleep(10)

        if self.in_connecting[channel]:
            return False

        self.in_connecting[channel] = True

        self.logger.debug('[%s] Wanna connect ...' % (channel, ))

        try:
            if self.is_connected(channel) or self.online is False:
                if self.is_connected(channel):
                    self.logger.debug('[%s] Already connected' % (channel, ))
                if self.online is False:
                    self.logger.debug('[%s] self.online=False' % (channel, ))

                return True

            self.channel_lock[channel].acquire()

            self.connected[channel] = None
            self.registered[channel] = None
            self.ssh_stream[channel] = False
            self.ssh_channel[channel] = False
            messages = None
            stderrdata = ''

            try:
                if not self.ssh_stream[channel]:
                    self.logger.debug('[%s] Open ssh connection' % (channel, ))
                    self.ssh_stream[channel] = create_ssh_stream(
                        self.config, exit_on_failure=False)

                self.logger.debug('[%s] open channel' % (channel, ))

                self.ssh_channel[channel] = self.ssh_stream[
                    channel].get_transport().open_session()
                self.ssh_channel[channel].exec_command('stream')
            except (KeyboardInterrupt, SystemExit):
                raise
            except Exception as e:
                self.connected[channel] = False
                self.registered[channel] = False
                self.logger.debug('[%s] connection failed: %s' %
                                  (channel, str(e)))
                return False
            finally:
                self.channel_lock[channel].release()

            if self.ssh_channel[channel]:
                messages = self.wait_for_at_least_one_message(channel)

            if not messages:
                stderrdata = self.ssh_channel[channel].recv_stderr().decode(
                    "utf-8").strip()
                self.connected[channel] = False
                self.registered[channel] = False
            else:
                self.logger.debug('[%s] opened and received %d messages' %
                                  (channel, len(messages)))
                self.connected[channel] = True
                self.registered[channel] = self.on_connect(
                    self.was_connected_once[channel], channel)
                self.connected_since[channel] = time.time()

                if channel == '' and self.registered[
                        channel] and self.was_connected_once[channel]:
                    self.logger.info("Successfully reconnected.")

            if not self.registered[channel]:
                # make sure to close channel and connection first
                try:
                    self.ssh_channel[channel] and self.ssh_channel[
                        channel].close()
                except:
                    pass

                try:
                    self.ssh_stream[channel] and self.ssh_stream[
                        channel].close()
                except:
                    pass

                self.logger.debug(
                    "[%s] Client: registration failed. stderrdata: %s" %
                    (channel, stderrdata))
                self.connected[channel] = False

                try:
                    self.logger.debug(
                        '[%s] Client: ssh_tream close due to registration failure'
                        % (channel, ))
                    self.ssh_stream[channel].close()
                except (KeyboardInterrupt, SystemExit):
                    raise

                self.connection_tries += 1
                if not self.was_connected_once[
                        channel] and self.go_offline_on_first_failed_attempt:
                    # initial try needs to be online, otherwise we go offline
                    self.go_offline()

                if stderrdata:
                    if 'Connection refused' not in stderrdata and 'Permission denied' not in stderrdata:
                        self.logger.error(stderrdata)

                if 'Permission denied' in stderrdata:
                    if self.connection_tries < 3:
                        self.logger.warning(
                            "Access denied. Did you setup your SSH public key correctly "
                            "and saved it in your AETROS Trainer user account?"
                        )

                    self.close()
                    sys.exit(1)

                self.connection_error(
                    channel, "Connection error during connecting to %s: %s" %
                    (self.host, str(stderrdata)))
            else:
                self.was_connected_once[channel] = True

        except Exception as error:
            self.connection_error(channel, error)
        finally:
            self.in_connecting[channel] = False

        return self.is_connected(channel)
Пример #6
0
    def main(self, args):
        import aetros.const

        parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
                                         prog=aetros.const.__prog__ + ' authenticate',
            description='Authenticates the machine with a new pair of SSH keys with a user account.')

        parsed_args = parser.parse_args(args)

        home_config = read_home_config()
        host = home_config['host']

        installed_key = get_ssh_key_for_host(host)
        key_exists_and_valid = False
        if installed_key:
            try:
                create_ssh_stream(home_config, exit_on_failure=False)
                key_exists_and_valid = True
            except Exception: pass

        if key_exists_and_valid:
            choice = six.moves.input("You have already configured a valid SSH (ssk_key: "+installed_key+") "
                                     "for "+host+".\nWant to create a new key? The old won't be removed. (y/N): ").lower()
            if choice != 'y' and choice != 'yes':
                print("Aborted.")
                sys.exit(1)

        ssh_key = paramiko.RSAKey.generate(4096)
        ssh_key_private = ssh_key.key.private_bytes(
            serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, serialization.NoEncryption()
        ).decode()
        ssh_key_public = 'rsa ' + ssh_key.get_base64()

        string_key = ssh_key.__str__()

        if not isinstance(string_key, six.binary_type):
            string_key = string_key.encode('utf-8')
        md5 = hashlib.md5(string_key)

        fingerprint  = md5.hexdigest()
        fingerprint = ':'.join(a + b for a, b in zip(fingerprint[::2], fingerprint[1::2]))

        try:
            token = api.http_request('machine-token', None, {
                'host': socket.getfqdn(),
                'key': ssh_key_public
            })
        except requests.exceptions.SSLError:
            sys.exit(1)

        print("Open following link and login to confirm this machine's SSH key in your account.")
        print("Public Key Fingerprint: MD5:" + fingerprint)
        print("\n   " + home_config['url'] + "/confirm-machine/" + token)
        print("\nWaiting for confirmation ...")

        key_prefix = home_config['host'] + '_'

        while True:
            time.sleep(3)
            response = api.http_request('machine-token/authorized?id=' + token, method='post')
            if response['status'] == 'confirmed':


                print("\n" + response['username'] + ' confirmed the public key. Test with "aetros id" or "ssh git@'+host+'".')
                private_key_path = os.path.expanduser('~/.ssh/' + key_prefix + response['username']+'_rsa')
                public_key_path = os.path.expanduser('~/.ssh/' + key_prefix + response['username']+'_rsa.pub')

                if not os.path.exists(os.path.dirname(private_key_path)):
                    os.makedirs(os.path.dirname(private_key_path))

                with open(private_key_path, 'w') as f:
                    f.write(ssh_key_private)

                with open(public_key_path, 'w') as f:
                    f.write(ssh_key_public)

                os.chmod(private_key_path, 0o600)
                os.chmod(public_key_path, 0o600)

                ssh_config_path = os.path.expanduser('~/.ssh/config')

                if not os.path.exists(os.path.dirname(ssh_config_path)):
                    os.makedirs(os.path.dirname(ssh_config_path))

                host_section = 'host '+host+'\n'
                identity_section = '    IdentityFile ~/.ssh/' + key_prefix + response['username']+'_rsa\n'

                if os.path.exists(ssh_config_path):
                    import re
                    regex = re.compile(r"^host\s+" + re.escape(host)+'\s*', re.IGNORECASE | re.MULTILINE)
                    with open(ssh_config_path, 'r+') as f:
                        config = f.read()

                        if regex.match(config):
                            config = regex.sub(host_section + identity_section, config, 1)
                        else:
                            config = host_section + identity_section + config

                        f.seek(0)
                        f.write(config)
                else:
                    with open(ssh_config_path, 'w') as f:
                        f.write(host_section + identity_section)

                print("Private key " + private_key_path + " installed in ~/.ssh/config for "+host+".\n")
                user = api.user()
                print("Key installed of account %s (%s)." % (user['username'], user['name']))
                sys.exit(0)
            if response['status'] == 'expired':
                print("Token expired.")
                sys.exit(1)