简体   繁体   中英

Phoenix Resolve Request Host from %Plug.Conn{} remote ip

A little summary of what I'm trying to do. I have a Phoenix app running on an AWS Elastic Beanstalk instance and I'm sending REST API requests containing PDFs for manipulation (splitting, merging etc). Each Request is saved in the database. This is what my requests schema looks like:

 schema "requests" do
    field :body, :string
    field :endpoint, :string
    field :method, :string
    field :request_host, :string
    field :response_body, :string
    field :response_code, :integer
    field :work_group_id, :integer
    field :identifier, :string
    field :responded_at, :utc_datetime

    timestamps()
  end

Through Graphql, I'm making a request from a separate RAILS server and displaying the hostname, inserted_at field, and response_code field of all the requests.

I'm experiencing problems trying to resolve the client's host name. This is the Erlang method I'm using where the method argument remote_ip is relatively conn.remote_ip :

 {:ok, {:hostent, request_host, _, _, _, _}} = :inet.gethostbyaddr(remote_ip)

This method returns my Phoenix app's request host and not the clients'.
What am I doing wrong here?
Thanks in advance :)

As described in the docs :

[The remote_ip ] field is meant to be overwritten by plugs that understand eg the X-Forwarded-For header or HAProxy's PROXY protocol. It defaults to peer's IP.

This is elaborated upon here :

When your app is running behind a proxy like Nginx, then the request will look like it's coming from Nginx, ie the IP will be 127.0.0.1. Similarly, If Nginx is behind a CDN, then all the requests will come from the IP of the CDN.

So you can write a plug to overwrite the remote_ip field of the Plug.Conn . Following is an example of such a plug. This example is copied from this blog post .

defmodule MyApp.Plug.PublicIp do
  @moduledoc "Get public IP address of request from x-forwarded-for header"
  @behaviour Plug
  @app :my_app

  def init(opts), do: opts

  def call(%{assigns: %{ip: _}} = conn, _opts), do: conn
  def call(conn, _opts) do
    process(conn, Plug.Conn.get_req_header(conn, "x-forwarded-for"))
  end

  def process(conn, []) do
    Plug.Conn.assign(conn, :ip, to_string(:inet.ntoa(get_peer_ip(conn))))
  end
  def process(conn, vals) do
    if Application.get_env(@app, :trust_x_forwarded_for, false) do ip_address = get_ip_address(conn, vals) # Rewrite standard remote_ip field with value from header
      # See https://hexdocs.pm/plug/Plug.Conn.html
      conn = %{conn | remote_ip: ip_address}

      Plug.Conn.assign(conn, :ip, to_string(:inet.ntoa(ip_address)))
    else
      Plug.Conn.assign(conn, :ip, to_string(:inet.ntoa(get_peer_ip(conn))))
    end
  end

  defp get_ip_address(conn, vals)
  defp get_ip_address(conn, []), do: get_peer_ip(conn)
  defp get_ip_address(conn, [val | _]) do
    # Split into multiple values
    comps = val
      |> String.split(~r{\s*,\s*}, trim: true)
      |> Enum.filter(&(&1 != "unknown"))          # Get rid of "unknown" values
      |> Enum.map(&(hd(String.split(&1, ":"))))   # Split IP from port, if any
      |> Enum.filter(&(&1 != ""))                 # Filter out blanks
      |> Enum.map(&(parse_address(&1)))           # Parse address into :inet.ip_address tuple
      |> Enum.filter(&(is_public_ip(&1)))         # Elminate internal IP addreses, e.g. 192.168.1.1

    case comps do
      [] -> get_peer_ip(conn)
      [comp | _] -> comp
    end
  end

  @spec get_peer_ip(Plug.Conn.t) :: :inet.ip_address
  defp get_peer_ip(conn) do
    {ip, _port} = conn.peer
    ip
  end

  @spec parse_address(String.t) :: :inet.ip_address
  defp parse_address(ip) do
    case :inet.parse_ipv4strict_address(to_charlist(ip)) do
      {:ok, ip_address} -> ip_address
      {:error, :einval} -> :einval
    end
  end

  # Whether the input is a valid, public IP address
  # http://en.wikipedia.org/wiki/Private_network
  @spec is_public_ip(:inet.ip_address | atom) :: boolean
  defp is_public_ip(ip_address) do
    case ip_address do
      {10, _, _, _}     -> false
      {192, 168, _, _}  -> false
      {172, second, _, _} when second >= 16 and second <= 31 -> false
      {127, 0, 0, _}    -> false
      {_, _, _, _}      -> true
      :einval           -> false
    end
  end
end

This library is available to simplify handling of IP addresses behind proxies.

https://github.com/ajvondrak/remote_ip

The author has a nice writeup of applicable use-cases and why the library is probably a better choice than rolling your own: here

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM