{"id":19156851,"url":"https://github.com/bertrandmartel/aws-ssm-session","last_synced_at":"2025-07-01T11:34:11.124Z","repository":{"id":40271886,"uuid":"253126192","full_name":"bertrandmartel/aws-ssm-session","owner":"bertrandmartel","description":"Javascript library for starting an AWS SSM session compatible with Browser and NodeJS","archived":false,"fork":false,"pushed_at":"2023-05-02T06:57:03.000Z","size":424,"stargazers_count":53,"open_issues_count":8,"forks_count":9,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-28T19:13:06.218Z","etag":null,"topics":["aws","aws-ssm","javascript","nodejs","shell","ssm","terminal","web","websocket","xterm-js"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bertrandmartel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-04-05T00:34:03.000Z","updated_at":"2025-03-07T10:48:13.000Z","dependencies_parsed_at":"2024-06-20T22:10:08.638Z","dependency_job_id":null,"html_url":"https://github.com/bertrandmartel/aws-ssm-session","commit_stats":{"total_commits":28,"total_committers":1,"mean_commits":28.0,"dds":0.0,"last_synced_commit":"00a2df03009218ebe5adaf86b6eaeaaa62cd3926"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bertrandmartel%2Faws-ssm-session","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bertrandmartel%2Faws-ssm-session/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bertrandmartel%2Faws-ssm-session/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bertrandmartel%2Faws-ssm-session/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bertrandmartel","download_url":"https://codeload.github.com/bertrandmartel/aws-ssm-session/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249057496,"owners_count":21205904,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws","aws-ssm","javascript","nodejs","shell","ssm","terminal","web","websocket","xterm-js"],"created_at":"2024-11-09T08:36:07.692Z","updated_at":"2025-04-15T10:50:46.303Z","avatar_url":"https://github.com/bertrandmartel.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# AWS SSM Session for Javascript\n\n![build](https://github.com/bertrandmartel/aws-ssm-session/workflows/build/badge.svg) [![License](http://img.shields.io/:license-mit-blue.svg)](LICENSE.md)\n[![Total alerts](https://img.shields.io/lgtm/alerts/g/bertrandmartel/aws-ssm-session.svg?logo=lgtm\u0026logoWidth=18)](https://lgtm.com/projects/g/bertrandmartel/aws-ssm-session/alerts/) [![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/bertrandmartel/aws-ssm-session.svg?logo=lgtm\u0026logoWidth=18)](https://lgtm.com/projects/g/bertrandmartel/aws-ssm-session/context:javascript)\n\nJavascript library for starting an AWS SSM session compatible with Browser and NodeJS\n\n[![npm package](https://nodei.co/npm/ssm-session.png?downloads=true\u0026downloadRank=true\u0026stars=true)](https://www.npmjs.com/package/ssm-session)\n\n|                                    Start a shell session in the Browser                                     |                                      Start a shell session using NodeJS                                      |\n| :---------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------: |\n| ![web](https://user-images.githubusercontent.com/5183022/78514983-5ad5c880-77b4-11ea-80cb-a35a1bbfd7ff.png) | ![node](https://user-images.githubusercontent.com/5183022/78514982-5a3d3200-77b4-11ea-8cc9-d7d3fdc060de.png) |\n\n## About AWS System Manager Session Manager\n\n\u003e Session Manager is a fully managed AWS Systems Manager capability that lets you manage your Amazon EC2 instances, on-premises instances, and virtual machines (VMs) through an interactive one-click browser-based shell or through the AWS CLI. Session Manager provides secure and auditable instance management without the need to open inbound ports, maintain bastion hosts, or manage SSH keys. Session Manager also makes it easy to comply with corporate policies that require controlled access to instances, strict security practices, and fully auditable logs with instance access details, while still providing end users with simple one-click cross-platform access to your managed instances.\n\n## Quick Start\n\n### NodeJS\n\n```bash\ngit clone git@github.com:bertrandmartel/aws-ssm-session.git\ncd aws-ssm-session\nnpm i\nnpm run build\nnode ./examples/node/app.js\n```\n\nYou will be prompted for AWS Region, AWS profile (default if not specified), choose your instance and a session is started directly\n\n### Browser\n\nWe need to generate the Websocket stream URL and token value using AWS API using a NodeJS script :\n\n```bash\ngit clone git@github.com:bertrandmartel/aws-ssm-session.git\ncd aws-ssm-session\nnpm i\nnpm run build\nnode scripts/generate-session.js\n```\n\nIn another shell start the local webserver\n\n```bash\ncd examples/web\nnpm start\n```\n\nGo to http://localhost:8080 and enter your token \u0026 stream value from the output of the first shell then click \"start session\"\n\n## Installation\n\nFrom npm :\n\n```bash\nnpm i --save ssm-session\n```\n\n## Usage\n\n```javascript\nconst { ssm } = require(\"ssm-session\");\n```\n\nor\n\n```javascript\nimport { ssm } from \"ssm-session\";\n```\n\n## Example\n\n### Browser\n\nThe following code starts a session and use [Xterm.js](https://xtermjs.org/) to write the result and listen to key events, checkout the [`web` directory](https://github.com/bertrandmartel/aws-ssm-session/tree/master/web)\n\n```javascript\nimport { Terminal } from \"xterm\";\nimport \"xterm/css/xterm.css\";\n\nimport { ssm } from \"ssm-session\";\n\nvar socket;\nvar terminal;\nconst textDecoder = new TextDecoder();\nconst textEncoder = new TextEncoder();\n\nconst termOptions = {\n  rows: 34,\n  cols: 197,\n  fontFamily: \"Fira Code, courier-new, courier, monospace\",\n};\n\n$(document).ready(function () {\n  $(\".toast\").toast({\n    delay: 3500,\n  });\n});\n$(\"#startSessionBtn\").click(startSession);\n$(\"#stopSessionBtn\").click(stopSession);\nfunction startSession() {\n  var tokenValue = document.getElementById(\"tokenValue\").value;\n  var websocketStreamURL = document.getElementById(\"websocketStreamURL\").value;\n  if (!tokenValue) {\n    showMessage(\"Token value is required to start session\");\n    return;\n  }\n  if (!websocketStreamURL) {\n    showMessage(\"Websocket stream URL is required to start session\");\n    return;\n  }\n\n  socket = new WebSocket(websocketStreamURL);\n  socket.binaryType = \"arraybuffer\";\n  initTerminal();\n\n  socket.addEventListener(\"open\", function (event) {\n    ssm.init(socket, {\n      token: tokenValue,\n      termOptions: termOptions,\n    });\n  });\n  socket.addEventListener(\"close\", function (event) {\n    showMessage(\"Websocket closed\");\n  });\n  socket.addEventListener(\"message\", function (event) {\n    var agentMessage = ssm.decode(event.data);\n    ssm.sendACK(socket, agentMessage);\n    if (agentMessage.payloadType === 1) {\n      terminal.write(textDecoder.decode(agentMessage.payload));\n    } else if (agentMessage.payloadType === 17) {\n      ssm.sendInitMessage(socket, termOptions);\n    }\n  });\n}\n\nfunction stopSession() {\n  if (socket) {\n    socket.close();\n  }\n  terminal.dispose();\n}\n\nfunction showMessage(message) {\n  $(\"#toastMessage\").text(message);\n  $(\"#alertMessage\").toast(\"show\");\n}\n\nfunction initTerminal() {\n  terminal = new Terminal(termOptions);\n  terminal.open(document.getElementById(\"terminal\"));\n  terminal.onData(function (data) {\n    ssm.sendText(socket, textEncoder.encode(data));\n  });\n  terminal.focus();\n}\n```\n\n### NodeJS\n\nThe following code uses [ws](https://github.com/websockets/ws) as websocket client and listens to key events, from the [examples/node](https://github.com/bertrandmartel/aws-ssm-session/tree/master/examples/node) directory :\n\n```javascript\n\"use strict\";\n\nconst session = require(\"../../scripts/aws-get-session\");\nconst WebSocket = require(\"ws\");\nconst readline = require(\"readline\");\nconst { ssm } = require(\"ssm-session\");\nconst util = require(\"util\");\n\nconst textDecoder = new util.TextDecoder();\nconst textEncoder = new util.TextEncoder();\n\nconst termOptions = {\n  rows: 34,\n  cols: 197,\n};\n\n(async () =\u003e {\n  var startSessionRes = await session();\n\n  const rl = readline.createInterface({\n    input: process.stdin,\n    output: null,\n  });\n  readline.emitKeypressEvents(process.stdin);\n\n  const connection = new WebSocket(startSessionRes.StreamUrl);\n\n  process.stdin.on(\"keypress\", (str, key) =\u003e {\n    if (connection.readyState === connection.OPEN) {\n      ssm.sendText(connection, textEncoder.encode(str));\n    }\n  });\n\n  connection.onopen = () =\u003e {\n    ssm.init(connection, {\n      token: startSessionRes.TokenValue,\n      termOptions: termOptions,\n    });\n  };\n\n  connection.onerror = (error) =\u003e {\n    console.log(`WebSocket error: ${error}`);\n  };\n\n  connection.onmessage = (event) =\u003e {\n    var agentMessage = ssm.decode(event.data);\n    ssm.sendACK(connection, agentMessage);\n    if (agentMessage.payloadType === 1) {\n      process.stdout.write(textDecoder.decode(agentMessage.payload));\n    } else if (agentMessage.payloadType === 17) {\n      ssm.sendInitMessage(connection, termOptions);\n    }\n  };\n\n  connection.onclose = () =\u003e {\n    console.log(\"websocket closed\");\n  };\n})();\n```\n\n## ECS tasks\n\nThis library also works with [ECS Tasks execute commands](https://aws.amazon.com/fr/blogs/containers/new-using-amazon-ecs-exec-access-your-containers-fargate-ec2/) on Fargate and Amazon EC2.\n\nYou can use the nodejs script to generate the session details:\n\n```\nnode ./scripts/generate-session-ecs.js\n```\n\nIt will prompt user with aws region, aws profile, ecs cluster, tasks, containers and will generate the session details including Token and websocket stream URL.\n\nNote that ECS restricts by default a maximum of 2 simultaneous connections. You will need to explicitely terminate the connections by listing and deleting them if necessary (see [Terminating the connections](#terminating-the-connections-))\n\n- list ssm sessions that are targetting ECS tasks:\n\n```bash\naws ssm describe-sessions --state Active | jq -r '.Sessions[] | select(.Target |  startswith(\"ecs:\")) | .SessionId'\n```\n\n- delete one session by session ID:\n\n```bash\naws ssm terminate-session --session-id [SESSION_ID]\n```\n\nNote: For EC2 instance, the target is the instance ID. For ECS taks, the target is a concatenation of cluster name, task ID and container ID, see [this](https://github.com/aws/aws-cli/blob/45b0063b2d0b245b17a57fd9eebd9fcc87c4426a/awscli/customizations/ecs/executecommand.py#L70-L72)\n\n## Terminating the connections\n\nTo terminate the connection, one would either execute the `exit` command or use the CLI/API with :\n\n- list ssm sessions:\n\n```bash\naws ssm describe-sessions --state Active | jq -r '.Sessions[]  | .SessionId'\n```\n\n- delete one session by session ID:\n\n```bash\naws ssm terminate-session --session-id [SESSION_ID]\n```\n\n## How it works ?\n\nThe flow is the following :\n\n- get the SSM Managed instance list using AWS API : [ssm describe-instances-information](https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_DescribeInstanceInformation.html)\n- call the start session API on one target instance using AWS API : [ssm start-session API](https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_StartSession.html). This gives you the websocket URL and a Token value that will be used for authentication\n- open a websocket connection on this URL\n- send an authentication request composed of the following JSON stringified :\n\n```json\n{\n  \"MessageSchemaVersion\": \"1.0\",\n  \"RequestId\": \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\",\n  \"TokenValue\": \"\u003cYOUR-TOKEN-VALUE\u003e\"\n}\n```\n\nFrom this moment the protocol is not JSON anymore. It is implemented in the offical [Amazon SSM agent](https://github.com/aws/amazon-ssm-agent) which is required if you want start a SSM session from the AWS CLI. The payload must be sent \u0026 receive according [this binary format](https://github.com/aws/amazon-ssm-agent/blob/c65d8ac29a8bbe6cd3f7cea778c1eeb1b06d49a3/agent/session/contracts/agentmessage.go)\n\nAlso more specifically from [amazon-ssm-agent source code](https://github.com/aws/amazon-ssm-agent/blob/c65d8ac29a8bbe6cd3f7cea778c1eeb1b06d49a3/agent/session/contracts/agentmessage.go):\n\n```\n// HL - HeaderLength is a 4 byte integer that represents the header length.\n// MessageType is a 32 byte UTF-8 string containing the message type.\n// SchemaVersion is a 4 byte integer containing the message schema version number.\n// CreatedDate is an 8 byte integer containing the message create epoch millis in UTC.\n// SequenceNumber is an 8 byte integer containing the message sequence number for serialized message streams.\n// Flags is an 8 byte unsigned integer containing a packed array of control flags:\n//   Bit 0 is SYN - SYN is set (1) when the recipient should consider Seq to be the first message number in the stream\n//   Bit 1 is FIN - FIN is set (1) when this message is the final message in the sequence.\n// MessageId is a 16 byte UTF-8 string containing a random UUID identifying this message.\n// Payload digest is a 32 byte containing the SHA-256 hash of the payload.\n// Payload Type is a 4 byte integer containing the payload type.\n// Payload length is an 4 byte unsigned integer containing the byte length of data in the Payload field.\n// Payload is a variable length byte data.\n```\n\nIn Javascript it gives the following :\n\n```javascript\nvar agentMessage = {\n  headerLength: getInt(buf.slice(0, 4)), // 4 bytes\n  messageType: getString(buf.slice(4, 36)).trim(), // 32 bytes\n  schemaVersion: getInt(buf.slice(36, 40)), // 4 bytes\n  createdDate: getLong(buf.slice(40, 48)), // 8 bytes\n  sequenceNumber: getLong(buf.slice(48, 56)), // 8 bytes\n  flags: getLong(buf.slice(56, 64)), // 8 bytes\n  messageId: parseUuid(buf.slice(64, 80)), // 16 bytes\n  payloadDigest: getString(buf.slice(80, 112)), // 32 bytes\n  payloadType: getInt(buf.slice(112, 116)), // 4 bytes\n  payloadLength: getInt(buf.slice(116, 120)), // 4 bytes\n  payload: buf.slice(120, buf.byteLength), //variable length\n};\n```\n\nByte order is Big endian\n\nFor the communication part :\n\n- each message with type \"output_stream_data\" must be acknowledged using an \"acknowledge\" type message which is referencing the messageID (uuid) of the message that has been received.\n- when you send text, you send a message with type \"input_stream_data\", this message must be sent with an incremental sequence number (note the sequenceNumber field in the model above). The message will then be acknowledged by the server\n\nThere are possibly some features I didn't implement, for instance I didn't implement yet the ping message which is used to prevent the shell from being terminated due to inactivity\n\n## Note about simultaneous terminal session\n\nThere is this sequence number that is required and re-initiliazed to 0 each time you call the `init()` function. If you need to have more than 1 terminal at the same time, there will be an issue because each session must have its own sequential number.\n\nOne way is to use you own sequential number and set it to 0 before the call to `init()` and increment it before calling `sendText()`. It will be like this :\n\nIn websocket open :\n\n```javascript\ncustomSeqNum = 0;\nssm.init(connection, {\n  token: startSessionRes.TokenValue,\n  termOptions: termOptions,\n});\n```\n\nWhen you write text:\n\n```javascript\nssm.sendText(connection, str, customSeqNum);\n```\n\nSo this way you can open any number of sessions simultaneously\n\n## Dependencies\n\n- An embedded version of sha256: https://github.com/geraintluff/sha256\n\n## License\n\nThe MIT License (MIT) Copyright (c) 2020 Bertrand Martel\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbertrandmartel%2Faws-ssm-session","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbertrandmartel%2Faws-ssm-session","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbertrandmartel%2Faws-ssm-session/lists"}