From c2c4608dd8030687c93c7dcef512e6db21a62cc5 Mon Sep 17 00:00:00 2001 From: David JULIEN Date: Mon, 14 Mar 2022 15:00:48 +0100 Subject: [PATCH] Use X-Forwarded-For header (if defined) to extract real client IP See https://en.wikipedia.org/wiki/X-Forwarded-For --- lib/sentry/plug_context.ex | 42 +++++++++++++++++++++++++++++--------- test/plug_context_test.exs | 29 ++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/lib/sentry/plug_context.ex b/lib/sentry/plug_context.ex index eadec50d..f00347a0 100644 --- a/lib/sentry/plug_context.ex +++ b/lib/sentry/plug_context.ex @@ -123,8 +123,8 @@ defmodule Sentry.PlugContext do cookies: handle_data(conn, cookie_scrubber), headers: handle_data(conn, header_scrubber), env: %{ - "REMOTE_ADDR" => remote_address(conn.remote_ip), - "REMOTE_PORT" => Plug.Conn.get_peer_data(conn).port, + "REMOTE_ADDR" => remote_address(conn), + "REMOTE_PORT" => remote_port(conn), "SERVER_NAME" => conn.host, "SERVER_PORT" => conn.port, "REQUEST_ID" => Plug.Conn.get_resp_header(conn, request_id) |> List.first() @@ -132,15 +132,37 @@ defmodule Sentry.PlugContext do } end - defp remote_address(address) do - address - |> :inet.ntoa() - |> case do - {:error, _} -> - "" + defp remote_address(conn) do + if header_value = get_header(conn, "x-forwarded-for") do + header_value + |> String.split(",") + |> hd() + |> String.trim() + else + conn.remote_ip + |> :inet.ntoa() + |> case do + {:error, _} -> + "" + + address -> + to_string(address) + end + end + end + + defp remote_port(conn) do + if get_header(conn, "x-forwarded-for") do + nil + else + Plug.Conn.get_peer_data(conn).port + end + end - address -> - to_string(address) + def get_header(conn, header) do + case Plug.Conn.get_req_header(conn, header) do + [] -> nil + [val | _] -> val end end diff --git a/test/plug_context_test.exs b/test/plug_context_test.exs index 4d0379b7..d3dc3d8b 100644 --- a/test/plug_context_test.exs +++ b/test/plug_context_test.exs @@ -16,6 +16,10 @@ defmodule Sentry.PlugContextTest do |> Map.take(["not-secret"]) end + defp add_x_forwarded_for(conn, ip_str) do + %{conn | req_headers: [{"x-forwarded-for", ip_str} | conn.req_headers]} + end + test "sets request context" do Sentry.PlugContext.call(conn(:get, "/test?hello=world"), []) @@ -38,6 +42,31 @@ defmodule Sentry.PlugContextTest do } = Sentry.Context.get_all() end + test "sets request context with real client ip if request is forwarded" do + Sentry.PlugContext.call( + conn(:get, "/test?hello=world") |> add_x_forwarded_for("10.0.0.1"), + [] + ) + + assert %{ + request: %{ + url: "http://www.example.com/test?hello=world", + method: "GET", + query_string: "hello=world", + data: %{ + "hello" => "world" + }, + env: %{ + "REMOTE_ADDR" => "10.0.0.1", + "REMOTE_PORT" => _, + "REQUEST_ID" => _, + "SERVER_NAME" => "www.example.com", + "SERVER_PORT" => 80 + } + } + } = Sentry.Context.get_all() + end + test "allows configuring body scrubber" do Sentry.PlugContext.call(conn(:get, "/test?hello=world&foo=bar"), body_scrubber: {__MODULE__, :body_scrubber}