Erlang's :ssh and key based Reverse Tunnel

,

:ssh … killing me softly and slowly :slightly_smiling_face:

Does anybody have a working example for using :ssh to do a (public/private) key based ssh reverse tunnel?

Having problems with :ssh and private key. Wasn’t able to connect. Various strange errors, but probably all due to my misconfigured setup. Didn’t find one yet, that I would be able to connect with private key.

Then I tried SSHex, but wan’t able to create a reverse tunnel on connect. I read somewhere that this is not supported.

Then i read about Librarian, with which I was actually able to at least connect, but then unable to create a reverse tunnel.

I need to do a reverse tunnel on initial connect.

If anybody has a working setup for :ssh I would really appreciate it.

Kind regards,
Tomaz

I have very little low level understanding of ssh, but looking at the docs, I don’t think it’s supported. Hopefully I’m missing something and incorrect, but you can investigate the specifics further here:

https://www.erlang.org/doc/apps/ssh/ssh_app#supported

Can you post the code you used against :ssh? I’ve used this reverse tunnel facility in the past although I don’t have the sample to hand.

Hi,

having issues that I don’t know how to setup a “call” to do a reverse tunnel.

I know how to do this in linux →

ssh -i /home/tomaz/.ssh/nemo/nemo_ssh_key -nN -R 2222:localhost:22 nemouser@192.168.6.130

This is a reverse tunnel from my Nerves device to the sshproxy VM in the cloud. The only thing on that VM is a change in sshd_config to allow other ports as well.
Then on my laptop I can do this

ssh -i tomaz-id_rsa tomaz@192.168.6.130 -p 2222

And land on my gateway device via SSH proxy from the 3rd machine.

BUUUT, trying this with :ssh is like this. Don’t really know how to pass all this command parts ssh -i /home/tomaz/.ssh/nemo/nemo_ssh_key -nN -R 2222:localhost:22 nemouser@192.168.6.130 to a :ssh.connect. Right now I am really stuck here.

iex(21)> :ok = :ssh.start()
:ok
iex(22)> userdir = ~c"/root/nerves_ssh"
~c"/root/nerves_ssh"
iex(23)> keycb = {:ssh_file, [identity: ~c"nemo_ssh_key"]}
{:ssh_file, [identity: ~c"nemo_ssh_key"]}
iex(24)> userdir = ~c"/root/nerves_ssh"
~c"/root/nerves_ssh"
iex(25)> ssh_option = [{:user, user}, {:user_interaction, false}, {:user_dir, userdir}, {:key_cb, keycb}]
[
  user: ~c"nemouser",
  user_interaction: false,
  user_dir: ~c"/root/nerves_ssh",
  key_cb: {:ssh_file, [identity: ~c"nemo_ssh_key"]}
]
iex(26)> :ssh.connect(String.to_charlist("192.168.6.130"), 22, ssh_option)

Take a look at ssh — ssh v5.2.3. SSH in erlang is not wrapping openssh, but is a pure erlang implemenation. Hence you cannot easily translate openssh cli parameters to erlang, you need to translate to how those same features are implemented on erlangs ssh module.

1 Like

Thank you @LostKobrakai !!

I solved this togetherwith my coleage @uhrovat

In order to have this as refence I will try to write this in a way that I wish would have this before.

So the usecase is this. I have a Nerves based devices out in the field, connected over closed LTE network. So I can’t really reach devices from my cloud base service or directly from my dev laptop. Though majority of features is supported/implemented with the use of MQTT calls between device and cloud, there is still a great feeling being able to jump on a device and do some system checks or maintenance. In my case latter as I will be dealing with custom data handling and processin solution.

OPENSSH way (tested without nerves on clasic linux box)

1.) I crated ssh reverse tunnel with this on boxA (like gateway on picture)

ssh -i /home/tomaz/.ssh/nemo/user_ssh_key -nN -R 2222:localhost:22 Auser@192.168.x.130

user_ssh_key is a key on a proxy server, and Auser is a user on sshproxy server

2.) On ssh proxy I only added

3.) On my laptop I did

ssh -i gateway-id_rsa tomaz@192.168.x.130 -p 2222

gateway-id_rsa is a key that resolves against the PKI on gateway, and tomaz is a user on gateway.

With this in place I gain access to the “TERMINAL” shell on my boxA - which kind of simulates a real nerves gateway on picture. I do think this is really importnat for later.

:SSH (Erlang way). - still without real Nerves gateway.

I’ve created a reverse tunnel on linux VM box (not actual Nerves gateway)

:ssh library expects file to be one of -> id_rsa, id_dsa or id_ecdsa. ssh — ssh v5.2.3

I actually had to rename my key to be id_rsa and not some random name even though I tried to point to it

:ok = :ssh.start()

user = ~c"Auser"
userdir = ~c"/home/tomaz/.ssh/nemo"
ssh_option = [{:user, user}, {:silently_accept_hosts, true}, {:user_interaction, false}, {:user_dir, userdir}]

{:ok, conn} = :ssh.connect(String.to_charlist("192.168.x.130"), 22, ssh_option)

:ssh.tcpip_tunnel_from_server(conn, String.to_charlist("192.168.6.130"), 3333, String.to_charlist("localhost"), 22)

And this setup did establish the ssh reverse proxy. I logged everything in /var/log/auth on a sshproxy.

Then on my laptop I did

ssh -i Auser Auser@192.168.x.130 -p 3333

And with that I got access to linux shell (terminal) on the “gateway” linux VM box.

SSH (Erlang way) - NOW ON NERVES DEVICE

Similar approach as previous example but now on actual device.

iex(13)> :ssh.tcpip_tunnel_from_server(conn, String.to_charlist("mydomain"), 3333, String.to_charlist("localhost"), 22)
{:ok, 3334}
iex(14)>

Then from my dev laptop

ssh -i /Users/tomaz/.ssh/nemo/tomaz-id_rsa nerves@192.168.x.130 -p 3333

private key has a public key on a gateway. User is for gateway, and IP is for sshproxy server.

And what I get is wooohooo!!!

Warning: Permanently added '[192.168.x.130]:3333' (ED25519) to the list of known hosts.
Interactive Elixir (1.17.3) - press Ctrl+C to exit (type h() ENTER for help)
████▄▄    ▐███
█▌  ▀▀██▄▄  ▐█
█▌  ▄▄  ▀▀  ▐█   N  E  R  V  E  S
█▌  ▀▀██▄▄  ▐█
███▌    ▀▀████
nemo 0.26.0 (0626c4d3-89d7-5e8d-0584-d8f685366269) arm rpi4
  Serial       : 1000000094278b35
  Uptime       : 2 hours, 44 minutes and 16 seconds
  Clock        : 2024-11-15 12:16:21 UTC
  Temperature  : 29.7°C

  Firmware     : Valid (B)               Applications : 62 started (nemo not started)
  Memory usage : 163 MB (4%)             Part usage   : 493 MB (2%)
  Hostname     : nerves-8b35             Load average : 0.00 0.00 0.00

  eth0         : 192.168.x.163/24, fe80::da3a:ddff:feab:daf2/64

Nerves CLI help: https://hexdocs.pm/nerves/iex-with-nerves.html

Toolshed imported. Run h(Toolshed) for more info.
iex(1)>

currently my gateway is connected with ethernet cable, since my LTE doesn’t have a signal here.

Huge thumbs up to @uhrovat for joining efforts.

Hope this post helps others in the future.

4 Likes