Skip to content

Commit

Permalink
Fix GraphQL remote ip context using x-forwarded-for header if possible
Browse files Browse the repository at this point in the history
  • Loading branch information
apoorv-2204 committed Mar 17, 2023
1 parent 6aa3746 commit 790de1c
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 46 deletions.
2 changes: 2 additions & 0 deletions lib/archethic_web/endpoint.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ defmodule ArchethicWeb.Endpoint do

plug(:archethic_up)

plug(ArchethicWeb.Plugs.RemoteIP)

# The session will be stored in the cookie and signed,
# this means its contents can be read but not tampered with.
# Set :encryption_salt if you would also like to encrypt it.
Expand Down
13 changes: 3 additions & 10 deletions lib/archethic_web/graphql_schema/resolver.ex
Original file line number Diff line number Diff line change
Expand Up @@ -323,16 +323,9 @@ defmodule ArchethicWeb.GraphQLSchema.Resolver do
defp transform_node_availabilities(<<>>, acc), do: acc

def nearest_endpoints(ip) do
geo_patch = P2P.get_geo_patch(ip)
nearest_nodes = P2P.nearest_nodes(P2P.authorized_and_available_nodes(), geo_patch)

Enum.map(
nearest_nodes,
&%{
ip: :inet.ntoa(&1.ip),
port: &1.http_port
}
)
P2P.authorized_and_available_nodes()
|> P2P.nearest_nodes(P2P.get_geo_patch(ip))
|> Enum.map(&%{ip: :inet.ntoa(&1.ip), port: &1.http_port})
end

def network_transactions(type, page) do
Expand Down
31 changes: 31 additions & 0 deletions lib/archethic_web/plugs/remote_ip.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
defmodule ArchethicWeb.Plugs.RemoteIP do
@moduledoc """
Get actual behind the reverse proxy IP address
"""

@behaviour Plug

def init(opts), do: opts

def call(conn, _) do
conn
|> Plug.Conn.get_req_header("x-forwarded-for")
|> List.first()
|> parse_ip(conn)
end

defp parse_ip(nil, conn), do: conn

defp parse_ip(ip_list, conn) do
ip_str =
String.split(ip_list, ",")
|> List.first()
|> String.trim()
|> String.to_charlist()

case :inet.parse_address(ip_str) do
{:ok, ip} -> Map.put(conn, :remote_ip, ip)
_ -> conn
end
end
end
2 changes: 1 addition & 1 deletion src/c/nat/miniupnp
157 changes: 122 additions & 35 deletions test/archethic_web/graphql_schema_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,20 @@ defmodule ArchethicWeb.GraphQLSchemaTest do
use ArchethicWeb.ConnCase
use ArchethicWeb.GraphQLSubscriptionCase

alias Archethic.Crypto

alias Archethic.BeaconChain.ReplicationAttestation
alias Archethic.BeaconChain.SummaryTimer
alias Archethic.BeaconChain.SummaryAggregate

alias Archethic.P2P
alias Archethic.P2P.Message.GetTransactionChainLength
alias Archethic.P2P.Message.TransactionChainLength
alias Archethic.P2P.Message.Balance
alias Archethic.P2P.Message.GenesisAddress
alias Archethic.P2P.Message.GetBalance
alias Archethic.P2P.Message.GetLastTransactionAddress
alias Archethic.P2P.Message.GetTransaction
alias Archethic.P2P.Message.GetTransactionChain
alias Archethic.P2P.Message.GetTransactionInputs
alias Archethic.P2P.Message.LastTransactionAddress
alias Archethic.P2P.Message.NotFound
alias Archethic.P2P.Message.TransactionInputList
alias Archethic.P2P.Message.TransactionList
alias Archethic.P2P.Message.GetGenesisAddress
alias Archethic.P2P.Message.GetBeaconSummariesAggregate
alias Archethic.P2P.Message.GetCurrentSummaries
alias Archethic.P2P.Node

alias Archethic.PubSub

alias Archethic.TransactionChain.Transaction
alias Archethic.TransactionChain.TransactionData
alias Archethic.TransactionChain.TransactionData.Ownership
alias Archethic.TransactionChain.TransactionInput
alias Archethic.TransactionChain.VersionedTransactionInput
alias Archethic.TransactionChain.TransactionSummary

alias Archethic.Mining
alias Archethic.{Crypto, BeaconChain, P2P, TransactionChain, Mining, PubSub}

alias BeaconChain.{ReplicationAttestation, SummaryAggregate, SummaryTimer}
alias TransactionChain.{Transaction, TransactionData, TransactionData.Ownership}
alias TransactionChain.{TransactionInput, TransactionSummary, VersionedTransactionInput}

alias P2P.{Node, Message}
alias Message.{GetTransactionChainLength, TransactionChainLength, Balance, GenesisAddress}
alias Message.{GetBalance, GetLastTransactionAddress, GetTransaction, NotFound}
alias Message.{GetTransactionChain, GetTransactionInputs, LastTransactionAddress}
alias Message.{TransactionInputList, TransactionList, GetGenesisAddress}
alias Message.{GetBeaconSummariesAggregate, GetCurrentSummaries}

alias ArchethicWeb.GraphQLSchema.Resolver

import Mox
@transaction_chain_page_size 10
Expand Down Expand Up @@ -856,6 +835,7 @@ defmodule ArchethicWeb.GraphQLSchemaTest do
P2P.add_and_connect_node(%Node{
ip: {127, 0, 0, 1},
port: 3004,
http_port: 3004,
first_public_key: <<0::8, 0::8, 1::8, :crypto.strong_rand_bytes(31)::binary>>,
last_public_key: "test",
available?: true
Expand Down Expand Up @@ -925,4 +905,111 @@ defmodule ArchethicWeb.GraphQLSchemaTest do

assert_raise MatchError, fn -> get_socket() end
end

describe "Nearest Endpoint" do
test "order of return", %{conn: conn} do
P2P.add_and_connect_node(%Node{
ip: {101, 10, 10, 1},
port: 40_005,
http_port: 4005,
first_public_key: Crypto.first_node_public_key(),
last_public_key: "key2",
network_patch: "AAA",
geo_patch: "AAA",
available?: true,
authorized?: true,
authorization_date: DateTime.utc_now(),
enrollment_date: DateTime.utc_now()
})

P2P.add_and_connect_node(%Node{
ip: {100, 10, 10, 1},
port: 40_005,
http_port: 4005,
first_public_key: "key2",
last_public_key: "key2",
network_patch: "ABC",
geo_patch: "AAA",
available?: true,
authorized?: true,
authorization_date: DateTime.utc_now(),
enrollment_date: DateTime.utc_now()
})

P2P.add_and_connect_node(%Node{
ip: {99, 10, 10, 1},
port: 40_004,
http_port: 40_004,
first_public_key: "key1",
last_public_key: "key1",
network_patch: "E0A",
geo_patch: "AAA",
available?: true,
authorized?: true,
authorization_date: DateTime.utc_now(),
enrollment_date: DateTime.utc_now()
})

P2P.add_and_connect_node(%Node{
ip: {147, 190, 18, 11},
port: 40_004,
http_port: 40_004,
first_public_key: "key3",
last_public_key: "key3",
network_patch: "ABB",
geo_patch: "AAA",
available?: true,
authorized?: true,
authorization_date: DateTime.utc_now(),
enrollment_date: DateTime.utc_now()
})

MockGeoIP
|> stub(:get_coordinates, fn _ ->
{48.8583701, 2.2922926}
end)

ip = {98, 6, 2, 5}

assert [
%{ip: '101.10.10.1', port: 4_005},
%{ip: '100.10.10.1', port: 4_005},
%{
ip: '147.190.18.11',
port: 40_004
},
%{ip: '99.10.10.1', port: 40_004}
] = Resolver.nearest_endpoints(ip)

conn = Map.put(conn, :remote_ip, ip)

conn =
post(conn, "/api", %{
"query" => "query { nearestEndpoints{ip,port} }"
})

%{
"data" => %{
"nearestEndpoints" => [
%{
"ip" => "101.10.10.1",
"port" => 4_005
},
%{
"ip" => "100.10.10.1",
"port" => 4_005
},
%{
"ip" => "147.190.18.11",
"port" => 40_004
},
%{
"ip" => "99.10.10.1",
"port" => 40_004
}
]
}
} = json_response(conn, 200)
end
end
end
25 changes: 25 additions & 0 deletions test/archethic_web/plugs/remote_ip_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule ArchethicWeb.Plugs.RemoteIPTest do
use ArchethicWeb.ConnCase, async: true

describe "Plug should get first x-forwarded remote ip " do
test "should modify remote ip if x-forwarded header exists", %{conn: conn} do
assert conn.remote_ip == {127, 0, 0, 1}

conn =
conn
|> put_req_header("x-forwarded-for", "122.15.183.19,93.5.9.0")

conn = ArchethicWeb.Plugs.RemoteIP.call(conn, [])

assert conn.remote_ip == {122, 15, 183, 19}
end

test "should not modifiy remote ip if x-forwarded header is empty", %{conn: conn} do
assert conn.remote_ip == {127, 0, 0, 1}

conn = ArchethicWeb.Plugs.RemoteIP.call(conn, [])

assert conn.remote_ip == {127, 0, 0, 1}
end
end
end

0 comments on commit 790de1c

Please sign in to comment.