как отправлять команды на веб-адрес AWS Session manager websocket с помощью xterm.js ?

#javascript #amazon-web-services #websocket #aws-ssm #xtermjs

#javascript #amazon-веб-сервисы #websocket #aws-ssm #xtermjs

Вопрос:

У меня есть URL-адрес веб-сокета, созданный AWS. URL создается aws ssm start session с использованием .net sdk. Метод запуска сеанса дает мне streamUrl, токен и идентификатор сеанса. URL-адрес в следующем формате:

 wss://ssmmessages.ap-south-1.amazonaws.com/v1/data-channel/sessionidhere?role=publish_subscribe
  

В месте «sessionidhere» есть фактический идентификатор сеанса, которым я не могу поделиться.

Я хочу открыть терминал в Интернете с помощью xterm.js . Я читал, что xterm.js может подключаться к URL-адресу websocket, отправлять сообщения и получать выходные данные.

Мой код javascript здесь :

 <!doctype html>
<html>
<head>
    <link href="~/xterm.css" rel="stylesheet" />
    <script src="~/Scripts/jquery-3.4.1.js"></script>
    <script src="~/Scripts/bootstrap.js"></script>
    <script src="~/xterm.js"></script>
</head>
<body>
    <div id="terminal"></div>
    <script type="text/javascript">
        var term = new Terminal({
            cursorBlink: "block"
        });
        var curr_line = "";
        var entries = [];
        term.open(document.getElementById('terminal'));    
        const ws = new WebSocket("wss://ssmmessages.ap-south-1.amazonaws.com/v1/data-channel/sessionid?role=publish_subscribe?token=tokenvalue");
        var curr_line = "";
        var entries = [];
      
        term.write("web shell $ ");

        term.prompt = () => {
            if (curr_line) {
                let data = {
                    method: "command", command: curr_line
                }
                ws.send(JSON.stringify(data));
            }
        };
        term.prompt();
        ws.onopen = function (e) {
            alert("[open] Connection established");
            alert("Sending to server");         
            var enc = new TextEncoder("utf-8"); // always utf-8
            // console.log(enc.encode("This is a string converted to a Uint8Array"));
            var data = "ls";
            console.log(enc.encode(data));
            alert(enc.encode(data));
            ws.send(enc.encode(data));
            alert(JSON.stringify(e));
        };
        ws.onclose = function (event) {
            if (event.wasClean) {
                alert(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
            } else {
                // e.g. server process killed or network down
                // event.code is usually 1006 in this case
                alert('[close] Connection died');
            }
        };

        ws.onerror = function (error) {
            alert(`[error] ${error.message}`);
        };

        // Receive data from socket
        ws.onmessage = msg => {
            alert(data);
            term.write("rn"   JSON.parse(msg.data).data);
            curr_line = "";
        };

        term.on("key", function (key, ev) {
            //Enter
            if (ev.keyCode === 13) {
                if (curr_line) {
                    entries.push(curr_line);
                    term.write("rn");
                    term.prompt();
                }
            } else if (ev.keyCode === 8) {
                // Backspace
                if (curr_line) {
                    curr_line = curr_line.slice(0, curr_line.length - 1);
                    term.write("b b");
                }
            } else {
                curr_line  = key;
                term.write(key);
            }
        });

        // paste value
        term.on("paste", function (data) {
            curr_line  = data;
            term.write(data);
        });
    </script>
</body>
</html>
  

Теперь, когда сеанс открывается, я получаю уведомление об установленном соединении. Соединение выполняется успешно, но всякий раз, когда я пытаюсь отправить команды, соединение закрывается, сообщая: «запрос на открытие канала передачи данных не содержит токена». Я пытался отправить команду 3 способами.

Во-первых, :

 ws.send("ls")
  

второй:

 let data = {
    method: "command", command: curr_line
}
ws.send(JSON.stringify(data));
  

Но столкнувшись с той же ошибкой, т.Е. Запрос на открытие канала передачи данных не содержит токена, соединение прервано

третье:

 var enc = new TextEncoder("utf-8"); 
var data = "ls";           
ws.send(enc.encode(data));
  

В-третьих, я не получаю никаких ошибок, но и не получаю выходных данных… Может кто-нибудь, пожалуйста, помогите?

Комментарии:

1. Кто-нибудь может помочь?

Ответ №1:

Протокол, используемый AWS Session manager, состоит из следующего :

  • откройте соединение с веб-сокетом по URL-адресу потока
  • отправьте запрос на проверку подлинности, состоящий из следующих строк в формате JSON :
 {
  "MessageSchemaVersion": "1.0",
  "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "TokenValue": "<YOUR-TOKEN-VALUE>"
}
  

С этого момента протокол больше не является JSON. Это реализовано в официальном агенте Amazon SSM, который требуется, если вы хотите запустить сеанс SSM из командной строки AWS. Полезная нагрузка должна быть отправлена и получена в соответствии с этим двоичным форматом

У меня было точно такое же требование, как и у вас несколько месяцев назад, поэтому я создал клиентскую библиотеку AWS Session manager: https://github.com/bertrandmartel/aws-ssm-session для nodejs и браузера. Если вам нужна дополнительная информация о том, как работает протокол, проверьте это

Пример кода, доступный для использования в браузере xterm.js

Сначала клонируйте проект и сгенерируйте URL-адрес и токен websocket с помощью aws-api с помощью некоторого служебного скрипта :

 git clone git@github.com:bertrandmartel/aws-ssm-session.git
cd aws-ssm-session
npm i
npm run build
node scripts/generate-session.js
  

что дает вам :

 {
  SessionId: 'xxxxxx-xxxxxxxxxxxxxx',
  TokenValue: 'YOUR_TOKEN',
  StreamUrl: 'wss://ssmmessages.eu-west-3.amazonaws.com/v1/data-channel/user-xxxxxxxxxxxxxx?role=publish_subscribe'
}
  

Затем отправьте образец приложения :

 npm install http-server -g
http-server -a localhost -p 3000
  

перейдите к http://localhost:3000/test/web , введите URI веб-сокета и токен :

введите описание изображения здесь

Пример кода для браузера :

 import { ssm } from "ssm-session";

var socket;
var terminal;

const termOptions = {
  rows: 34,
  cols: 197
};

function startSession(){
  var tokenValue = document.getElementById("tokenValue").value;
  var websocketStreamURL = document.getElementById("websocketStreamURL").value;
  
  socket = new WebSocket(websocketStreamURL);
  socket.binaryType = "arraybuffer";
  initTerminal()

  socket.addEventListener('open', function (event) {
    ssm.init(socket, {
      token: tokenValue,
      termOptions: termOptions
    });
  });
  socket.addEventListener('close', function (event) {
    console.log("Websocket closed")
  });
  socket.addEventListener('message', function (event) {
    var agentMessage = ssm.decode(event.data);
    //console.log(agentMessage);
    ssm.sendACK(socket, agentMessage);
    if (agentMessage.payloadType === 1){
      terminal.write(agentMessage.payload)
    } else if (agentMessage.payloadType === 17){
      ssm.sendInitMessage(socket, termOptions);
    }
  });
}

function stopSession(){
  if (socket){
    socket.close();
  }
  terminal.dispose()
}

function initTerminal() {
  terminal = new window.Terminal(termOptions);
  terminal.open(document.getElementById('terminal'));
  terminal.onKey(e => {
    ssm.sendText(socket, e.key);
  });
  terminal.on('paste', function(data) {
    ssm.sendText(socket, data);
  });
}
  

Комментарии:

1. Привет @Бертран Мартель, большое спасибо за ответ. Метод реализации и ваше объяснение выглядят очень хорошо и информативно. Я пытаюсь реализовать это в своем коде.

2. @Jass вы также можете увидеть этот проект администратора aws , для которого я разработал эту библиотеку. Это панель мониторинга AWS, использующая API, предназначенный для локального использования, а в разделе SSM вы можете запустить сеанс в браузере, используя библиотеку «ssm-session»

3. Да, @Bertrand Martel Я посмотрю на это. Но в приведенном выше коде импорт у меня не работает. Есть ли какая-либо другая альтернатива для импорта в браузере?

4. @Jass вы можете использовать import {ssm} from "../../src/index.js" , чтобы проверить исходный код примера здесь , указав type="module" в скрипте. Вам нужно будет получить исходный код или использовать transpiler

5. Хорошо, @Бертран Мартель, я мог бы импортировать ssm из index.js . Но в ssm. js выдает ошибку, которую вы не можете импортировать вне модуля. И в ssm.js , я не могу использовать тег скрипта для определения модуля