Source code for csle_rest_api.web_sockets.container_terminal.container_terminal

from flask import request
from flask_socketio import ConnectionRefusedError
from flask import Blueprint
import csle_rest_api.util.rest_api_util as rest_api_util
import csle_rest_api.constants.constants as api_constants
import csle_common.constants.constants as constants
from csle_common.metastore.metastore_facade import MetastoreFacade
from csle_rest_api import socketio


[docs]def get_container_terminal_bp(app): """ Gets the blue print of the Web socket API for the container terminal :param app: the Flask app :return: the blue print """ def read_and_forward_container_terminal_output() -> None: """ Reads output from a given file descriptor and sends the output to the web socket :return: None """ max_read_bytes = 1024 * 20 while True: socketio.sleep(0.01) ssh_channel = app.config[api_constants.MGMT_WEBAPP.CONTAINER_TERMINAL_SSH_SHELL] data_ready = ssh_channel.recv_ready() if data_ready: output = ssh_channel.recv(max_read_bytes).decode(errors="ignore") socketio.emit(api_constants.MGMT_WEBAPP.WS_CONTAINER_TERMINAL_OUTPUT_MSG, {api_constants.MGMT_WEBAPP.OUTPUT_PROPERTY: output}, namespace=f"{constants.COMMANDS.SLASH_DELIM}" f"{api_constants.MGMT_WEBAPP.WS_CONTAINER_TERMINAL_NAMESPACE}") @socketio.on(api_constants.MGMT_WEBAPP.WS_CONTAINER_TERMINAL_INPUT_MSG, namespace=f"{constants.COMMANDS.SLASH_DELIM}" f"{api_constants.MGMT_WEBAPP.WS_CONTAINER_TERMINAL_NAMESPACE}") def container_terminal_input(data) -> None: """ Receives input msg on a websocket and writes it to the PTY representing the bash shell of the Container terminal. The pty sees this as if you are typing in a real terminal. :param data: the input data to write :return: None """ cmd = data[api_constants.MGMT_WEBAPP.INPUT_PROPERTY].encode() ssh_channel = app.config[api_constants.MGMT_WEBAPP.CONTAINER_TERMINAL_SSH_SHELL] ssh_channel.send(cmd) @socketio.on(api_constants.MGMT_WEBAPP.WS_RESIZE_MSG, namespace=f"{constants.COMMANDS.SLASH_DELIM}" f"{api_constants.MGMT_WEBAPP.WS_CONTAINER_TERMINAL_NAMESPACE}") def container_terminal_resize(data) -> None: """ Handler when receiving a message on a websocket to resize the PTY window of a container terminal. The handler parses the data and resizes the window accordingly. :param data: data with information about the new PTY size :return: None """ rest_api_util.set_container_terminal_winsize( ssh_channel=app.config[api_constants.MGMT_WEBAPP.CONTAINER_TERMINAL_SSH_SHELL], row=data[api_constants.MGMT_WEBAPP.ROWS_PROPERTY], col=data[api_constants.MGMT_WEBAPP.COLS_PROPERTY]) @socketio.on(api_constants.MGMT_WEBAPP.WS_CONNECT_MSG, namespace=f"{constants.COMMANDS.SLASH_DELIM}" f"{api_constants.MGMT_WEBAPP.WS_CONTAINER_TERMINAL_NAMESPACE}") def container_terminal_connect() -> None: """ Handler for new websocket connection requests for the /container-terminal namespace. First checks if the user is authorized and then sets up the connection :return: None """ authorized = rest_api_util.check_if_user_is_authorized(request=request, requires_admin=True) config = MetastoreFacade.get_config(id=1) if authorized is not None or config is None: raise ConnectionRefusedError() ip_str = request.args.get(api_constants.MGMT_WEBAPP.IP_QUERY_PARAM) if ip_str is not None: ip = ip_str.replace("-", ".") term = u'xterm' ssh_conn = rest_api_util.ssh_connect(ip=ip) ssh_channel = ssh_conn.invoke_shell(term=term) ssh_channel.setblocking(0) rest_api_util.set_container_terminal_winsize(ssh_channel=ssh_channel, row=50, col=50) app.config[api_constants.MGMT_WEBAPP.CONTAINER_TERMINAL_SSH_SHELL] = ssh_channel app.config[api_constants.MGMT_WEBAPP.CONTAINER_TERMINAL_SSH_CONNECTION] = ssh_conn socketio.start_background_task(target=read_and_forward_container_terminal_output) else: ConnectionRefusedError() container_terminal_bp = Blueprint(api_constants.MGMT_WEBAPP.WS_CONTAINER_TERMINAL_NAMESPACE, __name__) return container_terminal_bp