Reputation: 9412
I'm having trouble reconciling the differences between using the aws cli and the ruby sdk to make ssm connections to an instance. For example, if I try using the command like like this:
aws ssm start-session \
--target 'i-abc123' \
--document-name AWS-StartPortForwardingSession \
--parameters '{
"portNumber": ["3000"],
"localPortNumber": ["13000"]
}'
Starting session with SessionId: username-def456
Port 13000 opened for sessionId username-def456.
Waiting for connections...
Connection accepted for session [username-def456]
the tool will pause as it opens up a port forwarding session from port 3000 on the destination instance to port 13000 on my local machine. I can then open up a web browser on my machine and point it at http://localhost:13000
to browse around an app running on the remote instance. Also, I can look at the AWS console UI and see that there's an active session until I Ctrl-C
The trouble I'm having is when I try to use the ruby sdk to do the same thing:
result = ssm.start_session(
target: 'i-abc123',
document_name: 'AWS-StartPortForwardingSession',
parameters: {
'portNumber' => %w[3000],
'localPortNumber' => %w[13000]
}
)
The result
object is a struct that looks like the following:
=> #<struct Aws::SSM::Types::StartSessionResponse
session_id="username-def456",
token_value="...",
stream_url="wss://ssmmessages.us-east-1.amazonaws.com/v1/data-channel/username-def456?
role=publish_subscribe">
Again, I can see in the console UI that there is an active session. However, if I try to browse to http://localhost:13000
on my machine, the browser can't reach anything. How do I use the resulting stream url and token to actually create a connection to the ec2 instance?
additional details:
Upvotes: 1
Views: 1499
Reputation: 32394
Building on the information in Feico de Boer's response, I got this to work:
# Use the AWS CLI Session Manager plugin to execute a command
# on an ECS task. This method will never return - it uses
# kernel#exec to execute an TTY interactive command.
#
# @param task [Aws::ECS::Types::Task] task to connect to.
# The task is expected to have only one container that
# should have /bin/bash installed.
# @param ecs_client [Aws::ECS::Client] SDK client
# The client is expected to have the region and profile configured.
# @param command [string] optional command to run in a bash shell.
# If provided, the output will be shown, otherwise an interactive
# bash shell will be started.
def execute_ecs_shell task, ecs_client, command = ''
region = ecs_client.config.region
profile = ecs_client.config.profile
cluster = task.cluster_arn.split("/").last
container_runtime_id = task.containers.first.runtime_id
resp = ecs_client.execute_command({
cluster: task.cluster_arn,
task: task.task_arn,
# the container name is optional for single container tasks
#container: "some container name",
interactive: true,
# supposedly we'd want to do interactive: command.empty?,
# but 'false' is not currently supported by the SDK
command: if command.empty? then
'/bin/bash'
else
"/bin/bash -c '#{command}'"
end
})
session = {
"SessionId" => resp.session.session_id,
"StreamUrl" => resp.session.stream_url,
"TokenValue" => resp.session.token_value,
}.to_json
params = {
"Target" =>"ecs:#{cluster}_#{task.id}_#{containers_runtime_id}"
}.to_json
exec("session-manager-plugin", session, region, "StartSession",
profile, params, "https://ssm.#{region}.amazonaws.com")
end
The main gaps I had were to figure out the correct format for the create_session_response
and parameters
arguments. Reading the source code for session-manager-plugin helped me to understand that they mean the JSON formatted API response - which I'm pretty sure is originally an XML and the Ruby implementation converts to a structure that responds poorly to to_json
, so the AWS CLI and the plugin basically rely on a language-specific side effect of the Python SDK library. The other problem was to understand the "target" parameter that is sent to the AWS CLI ssm start-session
command and I couldn't figure it out except reading this comment on the SSM agent issue tracker - where it isn't explained either but I found a format that works for me - assuming single container tasks. If you have multi-container tasks, you'll need to specify the container name in the execute_command
parameters and also fix the target definition, YMMV.
A correct implementation would be to grab a websocket client and implement the full SSM API, using a full readline-style interactive terminal session - I might want to do that at some point because the session-manager-plugin
has annoying output before and after the session.
Upvotes: 1
Reputation: 11
What you get back from the API is a reference to a web socket. So you either need to create your own web socket to local listener proxy or use the session-manager-plugin as the aws cli utility does.
I recently figured out how to use the session-manager-plugin, the following snippet is Python but should be obvious enough to figure it out.
def start_aws_ssm_plugin(self, create_session_response, parameters, profile, region):
print('start_aws_ssm_plugin() called: ' + str( create_session_response))
arg0 = '"' + self.config.get_ssm_plugin() + '"'
arg1 = '"' + str(create_session_response).replace('\'', '\\"') + '"'
arg2 = region
arg3 = 'StartSession'
arg4 = profile
arg5 = '"' + str(parameters).replace('\'', '\\"') + '"'
arg6 = 'https://ssm.{region}.amazonaws.com'.format(region=region)
command = arg0 + ' ' + arg1 + ' ' + arg2 + ' ' + arg3 + ' ' + arg4 + ' ' + arg5 + ' ' + arg6
print(command)
# print('session-manager-plugin', arg1, arg2, arg3, arg4, arg5, arg6)
pid = subprocess.Popen(command).pid
return pid
# end def
(And yes, this was just a quick and dirty prototype.;))
Upvotes: 1