LINUX.ORG.RU

История изменений

Исправление theNamelessOne, (текущая версия) :

Лорчую вариант с SSH-туннелем — дёшево и сердито. Тебе нужна будет только своя VPS [1] c SSH-сервером, у которого:

  • должен быть сохранён твой публичный SSH-ключ;
  • в конфиге должно быть AllowTcpForwarding = yes;
  • в конфиге должно быть GatewayPorts = yes или GatewayPorts = clientspecified;
  • внешний порт (в примерах ниже 8888 и listen_port) должен быть разрешён в фаерволе.

Дальше запускаешь туннель:

$ ssh -R 8888:127.0.0.1:4000 daddy@romalinux.xxx -NT

После этого запросы на romalinux.xxx:8888 будут проксироваться на твою приложуху, локально запущенную на порте 4000.

А если в твоём ЯП есть либы для SSH, то можно их использовать для того, чтобы запускать туннель автоматически при запуске приложения (и убивать при завершении приложения). Например для Elixir можно накалякать работающее решение на двух функциях из стандартной библиотеки Erlang:

defmodule SSHTunnel do
  use GenServer

  require Logger

  def start_link(opts) do
    {server_opts, opts} =
      Keyword.split(opts, [:debug, :name, :timeout, :spawn_opt, :hibernate_after])

    Logger.debug("tunnel opts: #{inspect(opts)}")

    GenServer.start_link(__MODULE__, Map.new(opts), server_opts)
  end

  @impl GenServer
  def init(opts) do
    ssh_host = to_erlang_host(Map.fetch!(opts, :ssh_host))
    ssh_port = opts[:ssh_port] || 22

    ssh_opts =
      case opts do
        %{ssh_user: ssh_user} when is_binary(ssh_user) ->
          [user: to_charlist(ssh_user)]

        _other ->
          []
      end

    listen_host = to_erlang_host(opts[:listen_host] || {0, 0, 0, 0})
    listen_port = opts[:listen_port] || 0

    local_host = to_erlang_host(opts[:local_host] || {127, 0, 0, 1})
    local_port = Map.fetch!(opts, :local_port)

    with {:ok, conn} <- :ssh.connect(ssh_host, ssh_port, ssh_opts),
         {:ok, true_listen_port} <-
           :ssh.tcpip_tunnel_from_server(conn, listen_host, listen_port, local_host, local_port) do
      Logger.info(
        "started TCP tunnel from #{format_host(ssh_host)}:#{true_listen_port} to #{format_host(local_host)}:#{format_host(local_port)}"
      )

      {:ok, conn}
    else
      {:error, error} ->
        Logger.error("failed to connect: #{inspect(error)}")

        {:stop, error}
    end
  end

  defp to_erlang_host(host) when is_binary(host), do: to_charlist(host)
  defp to_erlang_host(host), do: host

  defp format_host(host) when is_tuple(host) and tuple_size(host) in [4, 8] do
    :inet.ntoa(host)
  end

  defp format_host(host), do: host
end

Пример спеки для супервизора, эквивалентный вызову команды ssh выше:

{SSHTunnel, ssh_host: "romalinux.xxx", listen_port: 8888, local_port: 4000, ssh_user: "daddy"}

[1]: если своей VPS нет, то вместо неё уже советовали https://serveo.net/, принцип там тот же.

Исходная версия theNamelessOne, :

Лорчую вариант с SSH-туннелем — дёшево и сердито. Тебе нужна будет только своя VPS [1] c SSH-сервером, у которого:

  • должен быть сохранёт твой публичный SSH-ключ;
  • в конфиге должно быть AllowTcpForwarding = yes;
  • в конфиге должно быть GatewayPorts = yes или GatewayPorts = clientspecified;
  • внешний порт (в примерах ниже 8888 и listen_port) должен быть разрешён в фаерволе.

Дальше запускаешь туннель:

$ ssh -R 8888:127.0.0.1:4000 daddy@romalinux.xxx -NT

После этого запросы на romalinux.xxx:8888 будут проксироваться на твою приложуху, локально запущенную на порте 4000.

А если в твоём ЯП есть либы для SSH, то можно их использовать для того, чтобы запускать туннель автоматически при запуске приложения (и убивать при завершении приложения). Например для Elixir можно накалякать работающее решение на двух функциях из стандартной библиотеки Erlang:

defmodule SSHTunnel do
  use GenServer

  require Logger

  def start_link(opts) do
    {server_opts, opts} =
      Keyword.split(opts, [:debug, :name, :timeout, :spawn_opt, :hibernate_after])

    Logger.debug("tunnel opts: #{inspect(opts)}")

    GenServer.start_link(__MODULE__, Map.new(opts), server_opts)
  end

  @impl GenServer
  def init(opts) do
    ssh_host = to_erlang_host(Map.fetch!(opts, :ssh_host))
    ssh_port = opts[:ssh_port] || 22

    ssh_opts =
      case opts do
        %{ssh_user: ssh_user} when is_binary(ssh_user) ->
          [user: to_charlist(ssh_user)]

        _other ->
          []
      end

    listen_host = to_erlang_host(opts[:listen_host] || {0, 0, 0, 0})
    listen_port = opts[:listen_port] || 0

    local_host = to_erlang_host(opts[:local_host] || {127, 0, 0, 1})
    local_port = Map.fetch!(opts, :local_port)

    with {:ok, conn} <- :ssh.connect(ssh_host, ssh_port, ssh_opts),
         {:ok, true_listen_port} <-
           :ssh.tcpip_tunnel_from_server(conn, listen_host, listen_port, local_host, local_port) do
      Logger.info(
        "started TCP tunnel from #{format_host(ssh_host)}:#{true_listen_port} to #{format_host(local_host)}:#{format_host(local_port)}"
      )

      {:ok, conn}
    else
      {:error, error} ->
        Logger.error("failed to connect: #{inspect(error)}")

        {:stop, error}
    end
  end

  defp to_erlang_host(host) when is_binary(host), do: to_charlist(host)
  defp to_erlang_host(host), do: host

  defp format_host(host) when is_tuple(host) and tuple_size(host) in [4, 8] do
    :inet.ntoa(host)
  end

  defp format_host(host), do: host
end

Пример спеки для супервизора, эквивалентный вызову команды ssh выше:

{SSHTunnel, ssh_host: "romalinux.xxx", listen_port: 8888, local_port: 4000, ssh_user: "daddy"}

[1]: если своей VPS нет, то вместо неё уже советовали https://serveo.net/, принцип там тот же.