Last active
February 9, 2025 02:06
-
-
Save ckoch-cars/15a88fcb0bc2f4abbed09b49879edf31 to your computer and use it in GitHub Desktop.
Erlang ssh connect with SSH key
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Specifically we are trying to do 2 things with the erlang `:ssh` module: | |
# Make a connection (use :ssh.connect/4`) and pass in the ssh key from something other than a file on disk | |
# And then with an open connection, bind the connection to a local port (i.e. setup a ssh tunnel) | |
# use :ssh.tcpip_tunnel_to_server/5 or :ssh.tcpip_tunnel_to_server/6 | |
# Example (working) code: | |
defmodule SSHTunnel do | |
@local_port 9999 | |
@remote_port 1234 | |
require Logger | |
@behaviour :ssh_client_key_api | |
def start(remote_host, user) do | |
:ssh.start() | |
ssh_key = System.get_env("TUNNEL_SSH_KEY") | |
case :ssh.connect(String.to_charlist(remote_host), 22, | |
silently_accept_hosts: true, | |
user_interaction: false, | |
auth_methods: ~c"publickey", | |
user: String.to_charlist(user), | |
key_cb: {__MODULE__, user_key: ssh_key} | |
) do | |
{:ok, conn_pid} -> | |
Logger.info("Successfully connected to #{remote_host}") | |
%{conn_pid: conn_pid} | |
{:error, reason} -> | |
Logger.error("Failed to connect: #{inspect(reason)}") | |
{:error, reason} | |
end | |
end | |
def stop(%{conn_pid: conn_pid}) do | |
:ssh.close(conn_pid) | |
Logger.info("SSH tunnel to #{@remote_port} closed") | |
:ssh.stop() | |
end | |
@doc """ | |
Given a ref/pid to an open SSH connection; configure a tunnel to bind a local port #{@local_port} | |
to the port #{@remote_port} on the remote host. | |
""" | |
def setup_port_forwarding(%{conn_pid: conn_pid}) do | |
case :ssh.tcpip_tunnel_to_server(conn_pid, :loopback, @local_port, :loopback, @remote_port) do | |
{:ok, @local_port} -> | |
Logger.info( | |
"Port forwarding from local port #{@local_port} to remote port #{@remote_port}" | |
) | |
error -> | |
Logger.error(inspect(error)) | |
end | |
end | |
@doc "this is a required but indirectly used callback" | |
@impl :ssh_client_key_api | |
def add_host_key(_hostnames, _key, _connect_opts) do | |
:ok | |
end | |
@doc "this is a required but indirectly used callback" | |
@impl :ssh_client_key_api | |
def is_host_key(_key, _host, _algorithm, _connect_opts) do | |
true | |
end | |
@doc "Read an SSH key from something other than a file on disk i.e. an env var or app config" | |
@impl :ssh_client_key_api | |
def user_key(_algorithm, opts) do | |
data = opts[:key_cb_private][:user_key] | |
case :ssh_file.decode(data, :public_key) do | |
[{key, _comments} | _rest] -> | |
{:ok, key} | |
{:error, :key_decode_failed} -> | |
Logger.error("key_decode_failed") | |
{:error, :key_decode_failed} | |
other -> | |
Logger.error("Unexpected return value from :ssh_file.decode/2 #{inspect(other)}") | |
{:error, :ssh_client_key_api_unable_to_decode_key} | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment