Try to resolve country coordinates from LB-provided country code and use US as default

This commit is contained in:
Andrew Dryga
2023-10-31 18:50:20 -06:00
parent 835c278a34
commit 4deb5797ff
13 changed files with 467 additions and 8 deletions

View File

@@ -43,18 +43,26 @@ defmodule API.Sockets do
def load_balancer_ip_location(x_headers) do
location_region =
case API.Sockets.get_header(x_headers, "x-geo-location-region") do
{"x-geo-location-region", ""} -> nil
{"x-geo-location-region", location_region} -> location_region
_other -> nil
end
location_city =
case API.Sockets.get_header(x_headers, "x-geo-location-city") do
{"x-geo-location-city", ""} -> nil
{"x-geo-location-city", location_city} -> location_city
_other -> nil
end
{location_lat, location_lon} =
case API.Sockets.get_header(x_headers, "x-geo-location-coordinates") do
{"x-geo-location-coordinates", ","} ->
{nil, nil}
{"x-geo-location-coordinates", ""} ->
{nil, nil}
{"x-geo-location-coordinates", coordinates} ->
[lat, lon] = String.split(coordinates, ",", parts: 2)
lat = String.to_float(lat)
@@ -65,6 +73,9 @@ defmodule API.Sockets do
{nil, nil}
end
{location_lat, location_lon} =
Domain.Geo.maybe_put_default_coordinates(location_region, {location_lat, location_lon})
{location_region, location_city, {location_lat, location_lon}}
end

View File

@@ -79,6 +79,28 @@ defmodule API.Client.SocketTest do
assert {:ok, socket} = connect(Socket, attrs, connect_info: connect_info(subject))
assert client = Repo.one(Domain.Clients.Client)
assert client.id == socket.assigns.client.id
assert client.last_seen_remote_ip_location_region == "Ukraine"
assert client.last_seen_remote_ip_location_city == "Kyiv"
assert client.last_seen_remote_ip_location_lat == 50.4333
assert client.last_seen_remote_ip_location_lon == 30.5167
end
test "uses region code to put default coordinates" do
subject = Fixtures.Auth.create_subject()
existing_client = Fixtures.Clients.create_client(subject: subject)
{:ok, token} = Auth.create_session_token_from_subject(subject)
attrs = connect_attrs(token: token, external_id: existing_client.external_id)
connect_info = %{connect_info(subject) | x_headers: [{"x-geo-location-region", "UA"}]}
assert {:ok, socket} = connect(Socket, attrs, connect_info: connect_info)
assert client = Repo.one(Domain.Clients.Client)
assert client.id == socket.assigns.client.id
assert client.last_seen_remote_ip_location_region == "UA"
assert client.last_seen_remote_ip_location_city == nil
assert client.last_seen_remote_ip_location_lat == 49.0
assert client.last_seen_remote_ip_location_lon == 32.0
end
end

View File

@@ -43,6 +43,22 @@ defmodule API.Gateway.SocketTest do
assert gateway.last_seen_version == @connlib_version
end
test "uses region code to put default coordinates" do
token = Fixtures.Gateways.create_token()
encrypted_secret = Gateways.encode_token!(token)
attrs = connect_attrs(token: encrypted_secret)
connect_info = %{@connect_info | x_headers: [{"x-geo-location-region", "UA"}]}
assert {:ok, socket} = connect(Socket, attrs, connect_info: connect_info)
assert gateway = Map.fetch!(socket.assigns, :gateway)
assert gateway.last_seen_remote_ip_location_region == "UA"
assert gateway.last_seen_remote_ip_location_city == nil
assert gateway.last_seen_remote_ip_location_lat == 49.0
assert gateway.last_seen_remote_ip_location_lon == 32.0
end
test "propagates trace context" do
token = Fixtures.Gateways.create_token()
encrypted_secret = Gateways.encode_token!(token)

View File

@@ -43,6 +43,22 @@ defmodule API.Relay.SocketTest do
assert relay.last_seen_version == @connlib_version
end
test "uses region code to put default coordinates" do
token = Fixtures.Relays.create_token()
encrypted_secret = Relays.encode_token!(token)
attrs = connect_attrs(token: encrypted_secret)
connect_info = %{@connect_info | x_headers: [{"x-geo-location-region", "UA"}]}
assert {:ok, socket} = connect(Socket, attrs, connect_info: connect_info)
assert relay = Map.fetch!(socket.assigns, :relay)
assert relay.last_seen_remote_ip_location_region == "UA"
assert relay.last_seen_remote_ip_location_city == nil
assert relay.last_seen_remote_ip_location_lat == 49.0
assert relay.last_seen_remote_ip_location_lon == 32.0
end
test "propagates trace context" do
token = Fixtures.Relays.create_token()
encrypted_secret = Relays.encode_token!(token)

View File

@@ -350,9 +350,13 @@ defmodule Domain.Gateways do
|> Enum.group_by(fn gateway ->
{gateway.last_seen_remote_ip_location_lat, gateway.last_seen_remote_ip_location_lon}
end)
|> Enum.map(fn {{gateway_lat, gateway_lon}, gateway} ->
distance = Geo.distance({lat, lon}, {gateway_lat, gateway_lon})
{distance, gateway}
|> Enum.map(fn
{{nil, nil}, gateway} ->
{Geo.fetch_radius_of_earth_km!(), gateway}
{{gateway_lat, gateway_lon}, gateway} ->
distance = Geo.distance({lat, lon}, {gateway_lat, gateway_lon})
{distance, gateway}
end)
|> Enum.sort_by(&elem(&1, 0))
|> Enum.at(0)

View File

@@ -1,6 +1,268 @@
defmodule Domain.Geo do
@radius_of_earth_km 6371.0
# Since most of our customers are from US we use
# geographic center of USA as default coordinates
@default_coordinates {39.8283, -98.5795}
@coordinates_by_code %{
"AF" => {33, 65},
"AX" => {60.116667, 19.9},
"AL" => {41, 20},
"DZ" => {28, 3},
"AS" => {-14.33333333, -170},
"AD" => {42.5, 1.5},
"AO" => {-12.5, 18.5},
"AI" => {18.25, -63.16666666},
"AQ" => {-74.65, 4.48},
"AG" => {17.05, -61.8},
"AR" => {-34, -64},
"AM" => {40, 45},
"AW" => {12.5, -69.96666666},
"AU" => {-27, 133},
"AT" => {47.33333333, 13.33333333},
"AZ" => {40.5, 47.5},
"BS" => {24.25, -76},
"BH" => {26, 50.55},
"BD" => {24, 90},
"BB" => {13.16666666, -59.53333333},
"BY" => {53, 28},
"BE" => {50.83333333, 4},
"BZ" => {17.25, -88.75},
"BJ" => {9.5, 2.25},
"BM" => {32.33333333, -64.75},
"BT" => {27.5, 90.5},
"BO" => {-17, -65},
"BQ" => {12.15, -68.266667},
"BA" => {44, 18},
"BW" => {-22, 24},
"BV" => {-54.43333333, 3.4},
"BR" => {-10, -55},
"IO" => {-6, 71.5},
"VG" => {18.431383, -64.62305},
"VI" => {18.34, -64.93},
"BN" => {4.5, 114.66666666},
"BG" => {43, 25},
"BF" => {13, -2},
"BI" => {-3.5, 30},
"KH" => {13, 105},
"CM" => {6, 12},
"CA" => {60, -95},
"CV" => {16, -24},
"KY" => {19.5, -80.5},
"CF" => {7, 21},
"TD" => {15, 19},
"CL" => {-30, -71},
"CN" => {35, 105},
"CX" => {-10.5, 105.66666666},
"CC" => {-12.5, 96.83333333},
"CO" => {4, -72},
"KM" => {-12.16666666, 44.25},
"CG" => {-1, 15},
"CD" => {0, 25},
"CK" => {-21.23333333, -159.76666666},
"CR" => {10, -84},
"HR" => {45.16666666, 15.5},
"CU" => {21.5, -80},
"CW" => {12.116667, -68.933333},
"CY" => {35, 33},
"CZ" => {49.75, 15.5},
"DK" => {56, 10},
"DJ" => {11.5, 43},
"DM" => {15.41666666, -61.33333333},
"DO" => {19, -70.66666666},
"EC" => {-2, -77.5},
"EG" => {27, 30},
"SV" => {13.83333333, -88.91666666},
"GQ" => {2, 10},
"ER" => {15, 39},
"EE" => {59, 26},
"ET" => {8, 38},
"FK" => {-51.75, -59},
"FO" => {62, -7},
"FJ" => {-18, 175},
"FI" => {64, 26},
"FR" => {46, 2},
"GF" => {4, -53},
"PF" => {-15, -140},
"TF" => {-49.25, 69.167},
"GA" => {-1, 11.75},
"GM" => {13.46666666, -16.56666666},
"GE" => {42, 43.5},
"DE" => {51, 9},
"GH" => {8, -2},
"GI" => {36.13333333, -5.35},
"GR" => {39, 22},
"GL" => {72, -40},
"GD" => {12.11666666, -61.66666666},
"GP" => {16.25, -61.583333},
"GU" => {13.46666666, 144.78333333},
"GT" => {15.5, -90.25},
"GG" => {49.46666666, -2.58333333},
"GN" => {11, -10},
"GW" => {12, -15},
"GY" => {5, -59},
"HT" => {19, -72.41666666},
"HM" => {-53.1, 72.51666666},
"VA" => {41.9, 12.45},
"HN" => {15, -86.5},
"HU" => {47, 20},
"HK" => {22.25, 114.16666666},
"IS" => {65, -18},
"IN" => {20, 77},
"ID" => {-5, 120},
"CI" => {8, -5},
"IR" => {32, 53},
"IQ" => {33, 44},
"IE" => {53, -8},
"IM" => {54.25, -4.5},
"IL" => {31.5, 34.75},
"IT" => {42.83333333, 12.83333333},
"JM" => {18.25, -77.5},
"JP" => {36, 138},
"JE" => {49.25, -2.16666666},
"JO" => {31, 36},
"KZ" => {48, 68},
"KE" => {1, 38},
"KI" => {1.41666666, 173},
"KW" => {29.5, 45.75},
"KG" => {41, 75},
"LA" => {18, 105},
"LV" => {57, 25},
"LB" => {33.83333333, 35.83333333},
"LS" => {-29.5, 28.5},
"LR" => {6.5, -9.5},
"LY" => {25, 17},
"LI" => {47.26666666, 9.53333333},
"LT" => {56, 24},
"LU" => {49.75, 6.16666666},
"MO" => {22.16666666, 113.55},
"MK" => {41.83333333, 22},
"MG" => {-20, 47},
"MW" => {-13.5, 34},
"MY" => {2.5, 112.5},
"MV" => {3.25, 73},
"ML" => {17, -4},
"MT" => {35.83333333, 14.58333333},
"MH" => {9, 168},
"MQ" => {14.666667, -61},
"MR" => {20, -12},
"MU" => {-20.28333333, 57.55},
"YT" => {-12.83333333, 45.16666666},
"MX" => {23, -102},
"FM" => {6.91666666, 158.25},
"MD" => {47, 29},
"MC" => {43.73333333, 7.4},
"MN" => {46, 105},
"ME" => {42.5, 19.3},
"MS" => {16.75, -62.2},
"MA" => {32, -5},
"MZ" => {-18.25, 35},
"MM" => {22, 98},
"NA" => {-22, 17},
"NR" => {-0.53333333, 166.91666666},
"NP" => {28, 84},
"NL" => {52.5, 5.75},
"NC" => {-21.5, 165.5},
"NZ" => {-41, 174},
"NI" => {13, -85},
"NE" => {16, 8},
"NG" => {10, 8},
"NU" => {-19.03333333, -169.86666666},
"NF" => {-29.03333333, 167.95},
"KP" => {40, 127},
"MP" => {15.2, 145.75},
"NO" => {62, 10},
"OM" => {21, 57},
"PK" => {30, 70},
"PW" => {7.5, 134.5},
"PS" => {31.9, 35.2},
"PA" => {9, -80},
"PG" => {-6, 147},
"PY" => {-23, -58},
"PE" => {-10, -76},
"PH" => {13, 122},
"PN" => {-25.06666666, -130.1},
"PL" => {52, 20},
"PT" => {39.5, -8},
"PR" => {18.25, -66.5},
"QA" => {25.5, 51.25},
"XK" => {42.666667, 21.166667},
"RE" => {-21.15, 55.5},
"RO" => {46, 25},
"RU" => {60, 100},
"RW" => {-2, 30},
"BL" => {18.5, -63.41666666},
"SH" => {-15.95, -5.7},
"KN" => {17.33333333, -62.75},
"LC" => {13.88333333, -60.96666666},
"MF" => {18.08333333, -63.95},
"PM" => {46.83333333, -56.33333333},
"VC" => {13.25, -61.2},
"WS" => {-13.58333333, -172.33333333},
"SM" => {43.76666666, 12.41666666},
"ST" => {1, 7},
"SA" => {25, 45},
"SN" => {14, -14},
"RS" => {44, 21},
"SC" => {-4.58333333, 55.66666666},
"SL" => {8.5, -11.5},
"SG" => {1.36666666, 103.8},
"SX" => {18.033333, -63.05},
"SK" => {48.66666666, 19.5},
"SI" => {46.11666666, 14.81666666},
"SB" => {-8, 159},
"SO" => {10, 49},
"ZA" => {-29, 24},
"GS" => {-54.5, -37},
"KR" => {37, 127.5},
"ES" => {40, -4},
"LK" => {7, 81},
"SD" => {15, 30},
"SS" => {7, 30},
"SR" => {4, -56},
"SJ" => {78, 20},
"SZ" => {-26.5, 31.5},
"SE" => {62, 15},
"CH" => {47, 8},
"SY" => {35, 38},
"TW" => {23.5, 121},
"TJ" => {39, 71},
"TZ" => {-6, 35},
"TH" => {15, 100},
"TL" => {-8.83333333, 125.91666666},
"TG" => {8, 1.16666666},
"TK" => {-9, -172},
"TO" => {-20, -175},
"TT" => {11, -61},
"TN" => {34, 9},
"TR" => {39, 35},
"TM" => {40, 60},
"TC" => {21.75, -71.58333333},
"TV" => {-8, 178},
"UG" => {1, 32},
"UA" => {49, 32},
"AE" => {24, 54},
"GB" => {54, -2},
"US" => {38, -97},
"UY" => {-33, -56},
"UZ" => {41, 64},
"VU" => {-16, 167},
"VE" => {8, -66},
"VN" => {16.16666666, 107.83333333},
"WF" => {-13.3, -176.2},
"EH" => {24.5, -13},
"YE" => {15, 48},
"ZM" => {-15, 30},
"ZW" => {-20, 30}
}
|> Enum.map(fn {code, {lat, lon}} -> {code, {lat * 1.0, lon * 1.0}} end)
|> Map.new()
def fetch_radius_of_earth_km! do
@radius_of_earth_km
end
def distance({lat1, lon1}, {lat2, lon2}) do
d_lat = degrees_to_radians(lat2 - lat1)
d_lon = degrees_to_radians(lon2 - lon1)
@@ -18,4 +280,12 @@ defmodule Domain.Geo do
defp degrees_to_radians(deg) do
deg * :math.pi() / 180
end
def maybe_put_default_coordinates(country_code, {nil, nil}) do
Map.get(@coordinates_by_code, country_code, @default_coordinates)
end
def maybe_put_default_coordinates(_country_code, {lat, lon}) do
{lat, lon}
end
end

View File

@@ -329,9 +329,13 @@ defmodule Domain.Relays do
|> Enum.group_by(fn relay ->
{relay.last_seen_remote_ip_location_lat, relay.last_seen_remote_ip_location_lon}
end)
|> Enum.map(fn {{relay_lat, relay_lon}, relay} ->
distance = Geo.distance({lat, lon}, {relay_lat, relay_lon})
{distance, relay}
|> Enum.map(fn
{{nil, nil}, relay} ->
{Geo.fetch_radius_of_earth_km!(), relay}
{{relay_lat, relay_lon}, relay} ->
distance = Geo.distance({lat, lon}, {relay_lat, relay_lon})
{distance, relay}
end)
|> Enum.sort_by(&elem(&1, 0))
|> Enum.take(2)

View File

@@ -821,6 +821,28 @@ defmodule Domain.GatewaysTest do
assert gateway.id in [gateway_1.id, gateway_2.id, gateway_3.id]
end
test "prioritizes gateways with known location" do
gateway_1 =
Fixtures.Gateways.create_gateway(
last_seen_remote_ip_location_lat: 33.2029,
last_seen_remote_ip_location_lon: -80.0131
)
gateway_2 =
Fixtures.Gateways.create_gateway(
last_seen_remote_ip_location_lat: nil,
last_seen_remote_ip_location_lon: nil
)
gateways = [
gateway_1,
gateway_2
]
assert gateway = load_balance_gateways({32.2029, -80.0131}, gateways)
assert gateway.id == gateway_1.id
end
test "returns gateways in two closest regions to a given location" do
# Moncks Corner, South Carolina
gateway_us_east_1 =

View File

@@ -811,6 +811,29 @@ defmodule Domain.RelaysTest do
assert Enum.all?(relays, &(&1.id in [relay_1.id, relay_2.id, relay_3.id]))
end
test "prioritizes relays with known location" do
relay_1 =
Fixtures.Relays.create_relay(
last_seen_remote_ip_location_lat: 33.2029,
last_seen_remote_ip_location_lon: -80.0131
)
relay_2 =
Fixtures.Relays.create_relay(
last_seen_remote_ip_location_lat: nil,
last_seen_remote_ip_location_lon: nil
)
relays = [
relay_1,
relay_2
]
assert [fetched_relay1, fetched_relay2] = load_balance_relays({32.2029, -80.0131}, relays)
assert fetched_relay1.id == relay_1.id
assert fetched_relay2.id == relay_2.id
end
test "returns at least two relays even if they are at the same location" do
# Moncks Corner, South Carolina
relay_us_east_1 =

View File

@@ -166,18 +166,26 @@ defmodule Web.Auth do
defp get_load_balancer_ip_location(%Plug.Conn{} = conn) do
location_region =
case Plug.Conn.get_req_header(conn, "x-geo-location-region") do
["" | _] -> nil
[location_region | _] -> location_region
[] -> nil
end
location_city =
case Plug.Conn.get_req_header(conn, "x-geo-location-city") do
["" | _] -> nil
[location_city | _] -> location_city
[] -> nil
end
{location_lat, location_lon} =
case Plug.Conn.get_req_header(conn, "x-geo-location-coordinates") do
["" | _] ->
{nil, nil}
["," | _] ->
{nil, nil}
[coordinates | _] ->
[lat, lon] = String.split(coordinates, ",", parts: 2)
lat = String.to_float(lat)
@@ -188,24 +196,32 @@ defmodule Web.Auth do
{nil, nil}
end
{location_lat, location_lon} =
Domain.Geo.maybe_put_default_coordinates(location_region, {location_lat, location_lon})
{location_region, location_city, {location_lat, location_lon}}
end
defp get_load_balancer_ip_location(x_headers) do
location_region =
case get_socket_header(x_headers, "x-geo-location-region") do
{"x-geo-location-region", ""} -> nil
{"x-geo-location-region", location_region} -> location_region
_other -> nil
end
location_city =
case get_socket_header(x_headers, "x-geo-location-city") do
{"x-geo-location-city", ""} -> nil
{"x-geo-location-city", location_city} -> location_city
_other -> nil
end
{location_lat, location_lon} =
case get_socket_header(x_headers, "x-geo-location-coordinates") do
{"x-geo-location-coordinates", ""} ->
{nil, nil}
{"x-geo-location-coordinates", coordinates} ->
[lat, lon] = String.split(coordinates, ",", parts: 2)
lat = String.to_float(lat)
@@ -216,6 +232,9 @@ defmodule Web.Auth do
{nil, nil}
end
{location_lat, location_lon} =
Domain.Geo.maybe_put_default_coordinates(location_region, {location_lat, location_lon})
{location_region, location_city, {location_lat, location_lon}}
end

View File

@@ -148,6 +148,30 @@ defmodule Web.AuthTest do
assert conn.assigns.subject.context.remote_ip_location_lon == 30.5167
end
test "puts country coordinates to subject assign", %{
conn: conn,
admin_subject: subject
} do
{:ok, session_token} = Domain.Auth.create_session_token_from_subject(subject)
conn =
%{
conn
| path_params: %{"account_id_or_slug" => subject.account.id},
remote_ip: {100, 64, 100, 58}
}
|> put_req_header("x-geo-location-region", "UA")
|> delete_req_header("x-geo-location-city")
|> delete_req_header("x-geo-location-coordinates")
|> put_session(:session_token, session_token)
|> fetch_subject_and_account([])
assert conn.assigns.subject.context.remote_ip_location_region == "UA"
assert conn.assigns.subject.context.remote_ip_location_city == nil
assert conn.assigns.subject.context.remote_ip_location_lat == 49.0
assert conn.assigns.subject.context.remote_ip_location_lon == 32.0
end
test "does not authenticate to an incorrect account", %{conn: conn, admin_subject: subject} do
{:ok, session_token} = Domain.Auth.create_session_token_from_subject(subject)
@@ -296,6 +320,34 @@ defmodule Web.AuthTest do
assert socket.assigns.subject.context.remote_ip_location_lon == 30.5167
end
test "puts country coordinates to subject context", %{
conn: conn,
admin_subject: subject
} do
socket = %Phoenix.LiveView.Socket{
private: %{
connect_info: %{
user_agent: subject.context.user_agent,
peer_data: %{address: {100, 64, 100, 58}},
x_headers: [
{"x-geo-location-region", "UA"}
]
}
}
}
{:ok, session_token} = Domain.Auth.create_session_token_from_subject(subject)
session = conn |> put_session(:session_token, session_token) |> get_session()
params = %{"account_id_or_slug" => subject.account.id}
assert {:cont, socket} = on_mount(:mount_subject, params, session, socket)
assert socket.assigns.subject.context.remote_ip_location_region == "UA"
assert socket.assigns.subject.context.remote_ip_location_city == nil
assert socket.assigns.subject.context.remote_ip_location_lat == 49.0
assert socket.assigns.subject.context.remote_ip_location_lon == 32.0
end
test "assigns nil to subject assign if there isn't a valid session_token", %{
conn: conn,
socket: socket,

View File

@@ -422,7 +422,6 @@ resource "google_compute_backend_service" "default" {
"X-Geo-Location-Region:{client_region}",
"X-Geo-Location-City:{client_city}",
"X-Geo-Location-Coordinates:{client_city_lat_long}",
"X-Client-IP:{client_ip}",
]
custom_response_headers = [

View File

@@ -16,7 +16,8 @@ resource "google_artifact_registry_repository" "firezone" {
format = "DOCKER"
docker_config {
immutable_tags = false
# It's false by default but setting it explicitly produces unwanted state diff
# immutable_tags = false
}
cleanup_policies {