예제 #1
0
파일: handlers.py 프로젝트: ski2per/gru
    def exec_remote_cmd(self, cmd, probe_cmd=None):
        """
        Execute command(cmd or probe-command) on remote host

        :param cmd: Command to execute
        :param probe_cmd: Probe command to execute before 'cmd'
        :return: None
        """
        minion_id = self.get_value('minion', arg_type='query')
        LOG.debug(f'[exec_remote_cmd] Minion ID: {minion_id}')
        minion = MINIONS.get(minion_id)
        args = minion['args']
        LOG.debug(f'[exec_remote_cmd] Minion args: {args}')
        self.ssh_transport_client = self.create_ssh_client(args)

        # Use probe_cmd to detect file's existence
        if probe_cmd:
            chan = self.ssh_transport_client.get_transport().open_session()
            chan.exec_command(probe_cmd)
            ext = (chan.recv_exit_status())
            if ext:
                raise tornado.web.HTTPError(404, "Not found")

        transport = self.ssh_transport_client.get_transport()
        self.channel = transport.open_channel(kind='session')
        self.channel.exec_command(cmd)
예제 #2
0
파일: handlers.py 프로젝트: ski2per/gru
    def open(self):
        self.src_addr = self.get_client_endpoint()
        LOG.info('Connected from {}:{}'.format(*self.src_addr))

        try:
            # Get id from query argument from
            minion_id = self.get_value('id')
            LOG.debug(f"############ minion id: {minion_id}")

            minion = MINIONS.get(minion_id)
            if not minion:
                self.close(reason='Websocket failed.')
                return

            minion_obj = minion.get('minion', None)
            if minion_obj:
                # minions[minion_id]["minion"] = None
                self.set_nodelay(True)
                minion_obj.set_handler(self)
                self.minion_ref = weakref.ref(minion_obj)
                self.loop.add_handler(minion_obj.fd, minion_obj, IOLoop.READ)
            else:
                self.close(reason='Websocket authentication failed.')

        except (tornado.web.MissingArgumentError, InvalidValueError) as err:
            self.close(reason=str(err))
예제 #3
0
파일: handlers.py 프로젝트: ski2per/gru
    async def post(self):
        args = self.get_args()
        try:
            self.ssh_term_client = self.create_ssh_client(args)
            minion = await run_async_func(self.create_minion, args)
        except InvalidValueError as err:
            # Catch error in self.get_args()
            raise tornado.web.HTTPError(400, str(err))
        except (ValueError, paramiko.SSHException,
                paramiko.ssh_exception.SSHException,
                paramiko.ssh_exception.AuthenticationException,
                socket.timeout) as err:
            LOG.error("====================")
            LOG.error(err)
            # Delete dangling cache
            if str(err).lower().startswith(
                    "unable to") and conf.mode != "term":
                delete_cache(str(args[1]))

            self.result.update(status=str(err))
        else:
            # if not minions:
            # GRU[ip] = minions
            # minion.src_addr = (ip, port)
            MINIONS[minion.id] = {"minion": minion, "args": args}
            self.loop.call_later(2, recycle_minion, minion)
            self.result.update(id=minion.id, encoding=minion.encoding)
            # self.set_secure_cookie("minion", minion.id)
        self.write(self.result)
예제 #4
0
파일: handlers.py 프로젝트: ski2per/gru
    def on_close(self):
        LOG.info('Disconnected from {}:{}'.format(*self.src_addr))
        if not self.close_reason:
            self.close_reason = 'client disconnected'

        minion = self.minion_ref() if self.minion_ref else None
        if minion:
            minion.close(reason=self.close_reason)
예제 #5
0
파일: handlers.py 프로젝트: ski2per/gru
 def _extract_filename(data: bytes) -> str:
     LOG.debug(data)
     ptn = re.compile(b'filename="(.*)"')
     m = ptn.search(data)
     if m:
         name = m.group(1).decode()
     else:
         name = "untitled"
     # Replace spaces with underscore
     return re.sub(r'\s+', '_', name)
예제 #6
0
파일: handlers.py 프로젝트: ski2per/gru
    def create_minion(self, args):
        ssh_endpoint = args[:2]
        LOG.info('Connecting to {}:{}'.format(*ssh_endpoint))

        term = self.get_argument('term', '') or 'xterm'
        shell_channel = self.ssh_term_client.invoke_shell(term=term)
        shell_channel.setblocking(0)
        minion = Minion(self.loop, self.ssh_term_client, shell_channel,
                        ssh_endpoint)
        minion.encoding = conf.encoding if conf.encoding else self.get_server_encoding(
            self.ssh_term_client)
        return minion
예제 #7
0
파일: handlers.py 프로젝트: ski2per/gru
    def get_server_encoding(self, ssh):
        try:
            _, stdout, _ = ssh.exec_command("locale charmap")
        except paramiko.SSHException as err:
            LOG.error(str(err))
        else:
            result = stdout.read().decode().strip()
            if result:
                return result

        LOG.warning('!!! Unable to detect default encoding')
        return 'utf-8'
예제 #8
0
파일: handlers.py 프로젝트: ski2per/gru
    def get_args(self):
        data = json_decode(self.request.body)
        LOG.debug(data)

        # Minion login won't pass hostname in form data
        hostname = data.get("hostname", "localhost")
        username = data["username"]
        password = data["password"]
        port = int(data["port"])
        args = (hostname, port, username, password)
        LOG.debug(f"Args for SSH: {args}")
        return args
예제 #9
0
파일: main.py 프로젝트: ski2per/gru
def main():
    LOG.info(f'Gru mode: {conf.mode}')
    loop = tornado.ioloop.IOLoop.current()
    app = Term1nal(loop=loop)
    ssl_ctx = get_ssl_context(conf)
    server_settings = dict(
        xheaders=True,
        max_body_size=6000 * 1024 * 1024,  # 6G
    )
    app.listen(conf.port, conf.address, **server_settings)
    if ssl_ctx:
        server_settings.update(ssl_options=ssl_ctx)
        app.listen(conf.ssl_port, conf.host, **server_settings)

    loop.start()
예제 #10
0
파일: handlers.py 프로젝트: ski2per/gru
    async def data_received(self, data):
        # A simple multipart/form-data
        # b'------WebKitFormBoundarysiqXYmhALsFpsMuh\r\nContent-Disposition: form-data; name="upload";
        # filename="hello.txt"\r\nContent-Type: text/plain\r\n\r\n
        # hello\r\n\r\nworld\r\n\r\n------WebKitFormBoundarysiqXYmhALsFpsMuh--\r\n'
        """

        :param data:
        :return: None
        """
        if not self.boundary:
            self.boundary = self._get_boundary()
            LOG.debug(f"multipart/form-data boundary: {self.boundary}")

        # Split data with multipart/form-data boundary
        sep = f'--{self.boundary}'
        chunks = data.split(sep.encode('ISO-8859-1'))
        chunks_len = len(chunks)

        # DEBUG
        # print("=====================================")
        # print(f"Stream idx: {self.stream_idx}")
        # print(f"CHUNKS length: {len(chunks)}")

        # Data is small enough in one stream
        if chunks_len == 3:
            form_data_info, raw = self._partition_chunk(chunks[1])
            self.filename = self._extract_filename(form_data_info)
            await run_async_func(self.exec_remote_cmd,
                                 f'cat > /tmp/{self.filename}')
            await run_async_func(self._write_chunk, raw)
            await run_async_func(self.ssh_transport_client.close)
        else:
            if self.stream_idx == 0:
                form_data_info, raw = self._partition_chunk(chunks[1])
                self.filename = self._extract_filename(form_data_info)
                await run_async_func(self.exec_remote_cmd,
                                     f'cat > /tmp/{self.filename}')
                await run_async_func(self._write_chunk, raw)
            else:
                # Form data in the middle data stream
                if chunks_len == 1:
                    await run_async_func(self._write_chunk, chunks[0])
                else:
                    # 'chunks_len' == 2, the LAST stream
                    await run_async_func(self._write_chunk, chunks[0])
                    await run_async_func(self.ssh_transport_client.close)
        self.stream_idx += 1
예제 #11
0
파일: handlers.py 프로젝트: ski2per/gru
 def create_ssh_client(args) -> paramiko.SSHClient:
     print(f"[create_ssh_client]args: {args}")
     ssh = paramiko.SSHClient()
     ssh.set_missing_host_key_policy(paramiko.client.MissingHostKeyPolicy)
     try:
         ssh.connect(*args,
                     allow_agent=False,
                     look_for_keys=False,
                     timeout=conf.timeout)
     except socket.error:
         print(args[:2])
         raise ValueError('Unable to connect to {}:{}'.format(*args[:2]))
     except (paramiko.AuthenticationException,
             paramiko.ssh_exception.AuthenticationException):
         raise ValueError('Authentication failed.')
     except EOFError:
         LOG.error("Got EOFError, retry")
         ssh.connect(*args,
                     allow_agent=False,
                     look_for_keys=False,
                     timeout=conf.timeout)
     return ssh
예제 #12
0
파일: handlers.py 프로젝트: ski2per/gru
    def on_message(self, message):
        LOG.debug(f'{message} from {self.src_addr}')
        minion = self.minion_ref()
        try:
            msg = json.loads(message)
        except JSONDecodeError:
            return

        if not isinstance(msg, dict):
            return

        resize = msg.get('resize')
        if resize and len(resize) == 2:
            try:
                minion.chan.resize_pty(*resize)
            except (TypeError, struct.error, paramiko.SSHException):
                pass

        data = msg.get('data')
        if data and isinstance(data, str):
            minion.data_to_dst.append(data)
            minion.do_write()
예제 #13
0
    def do_read(self):
        LOG.debug('minion {} on read'.format(self.id))
        try:
            data = self.chan.recv(self.BUFFER_SIZE)
        except (OSError, IOError) as e:
            LOG.error(e)
            if errno_from_exception(e) in _ERRNO_CONNRESET:
                self.close(reason='CHAN ERROR DOING READ ')
        else:
            LOG.debug(f'{data} from {self.dst_addr}')
            if not data:
                self.close(reason='BYE ~')
                return

            LOG.debug(f'{data} to {self.handler.src_addr}')
            try:
                self.handler.write_message(data, binary=True)
            except tornado.websocket.WebSocketClosedError:
                self.close(reason='WEBSOCKET CLOSED')
예제 #14
0
파일: handlers.py 프로젝트: ski2per/gru
    async def get(self):
        chunk_size = 1024 * 1024 * 1  # 1 MiB

        remote_file_path = self.get_value("filepath", arg_type="query")
        filename = os.path.basename(remote_file_path)
        LOG.debug(remote_file_path)

        try:
            self.exec_remote_cmd(cmd=f'cat {remote_file_path}',
                                 probe_cmd=f'ls {remote_file_path}')
        except tornado.web.HTTPError:
            self.write(f'Not found: {remote_file_path}')
            await self.finish()
            return

        self.set_header("Content-Type", "application/octet-stream")
        self.set_header("Accept-Ranges", "bytes")
        self.set_header("Content-Disposition",
                        f"attachment; filename={filename}")

        while True:
            chunk = self.channel.recv(chunk_size)
            if not chunk:
                break
            try:
                # Write the chunk to response
                self.write(chunk)
                # Send the chunk to client
                await self.flush()
            except tornado.iostream.StreamClosedError:
                break
            finally:
                del chunk
                await tornado.web.gen.sleep(0.000000001)  # 1 nanosecond

        self.ssh_transport_client.close()
        try:
            await self.finish()
        except tornado.iostream.StreamClosedError as err:
            LOG.error(err)
            LOG.debug("Maybe user cancelled download")
        LOG.info(f"Download ended: {remote_file_path}")
예제 #15
0
    def close(self, reason=None):
        if self.closed:
            return
        self.closed = True

        LOG.info(f'Closing minion {self.id}: {reason}')
        if self.handler:
            self.loop.remove_handler(self.fd)
            self.handler.close(reason=reason)
        self.chan.close()
        self.ssh.close()
        LOG.info('Connection to {}:{} lost'.format(*self.dst_addr))

        clear_minion(self)
        LOG.debug(MINIONS)
예제 #16
0
    def do_write(self):
        LOG.debug('minion {} on write'.format(self.id))
        if not self.data_to_dst:
            return

        data = ''.join(self.data_to_dst)
        LOG.debug(f'{data} to {self.dst_addr}')

        try:
            sent = self.chan.send(data)
        except (OSError, IOError) as e:
            LOG.error(e)
            if errno_from_exception(e) in _ERRNO_CONNRESET:
                self.close(reason='chan error on writing')
            else:
                self.update_handler(IOLoop.WRITE)
        else:
            self.data_to_dst = []
            data = data[sent:]
            if data:
                self.data_to_dst.append(data)
                self.update_handler(IOLoop.WRITE)
            else:
                self.update_handler(IOLoop.READ)
예제 #17
0
def recycle_minion(minion):
    if minion.handler:
        return
    LOG.warning('Recycling minion {}'.format(minion.id))
    minion.close(reason='minion recycled')
예제 #18
0
파일: handlers.py 프로젝트: ski2per/gru
 def get(self):
     LOG.debug(f"MINIONS: {MINIONS}")
     self.render('index.html', mode=conf.mode)
예제 #19
0
파일: handlers.py 프로젝트: ski2per/gru
 def prepare(self):
     LOG.info("In NotFoundHandler")
     raise tornado.web.HTTPError(status_code=404, reason="Oops!")