mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
Add routing option for sites (#2610)
Why: * As sites are created, the default behavior right now is to route traffic through whichever path is easiest/fastest. This commit adds the ability to allow the admin to choose a routing policy for a given site.
This commit is contained in:
@@ -205,7 +205,10 @@ services:
|
||||
PUBLIC_IP6_ADDR: fcff:3990:3990::101
|
||||
LOWEST_PORT: 55555
|
||||
HIGHEST_PORT: 55666
|
||||
FIREZONE_TOKEN: "SFMyNTY.g2gDaAJtAAAAJDcyODZiNTNkLTA3M2UtNGM0MS05ZmYxLWNjODQ1MWRhZDI5OW0AAABARVg3N0dhMEhLSlVWTGdjcE1yTjZIYXRkR25mdkFEWVFyUmpVV1d5VHFxdDdCYVVkRVUzbzktRmJCbFJkSU5JS24GAFSzb0KJAWIAAVGA.waeGE26tbgkgIcMrWyck0ysv9SHIoHr0zqoM3wao84M"
|
||||
# Token for self-hosted Relay
|
||||
#FIREZONE_TOKEN: "SFMyNTY.g2gDaAJtAAAAJDcyODZiNTNkLTA3M2UtNGM0MS05ZmYxLWNjODQ1MWRhZDI5OW0AAABARVg3N0dhMEhLSlVWTGdjcE1yTjZIYXRkR25mdkFEWVFyUmpVV1d5VHFxdDdCYVVkRVUzbzktRmJCbFJkSU5JS24GAFSzb0KJAWIAAVGA.waeGE26tbgkgIcMrWyck0ysv9SHIoHr0zqoM3wao84M"
|
||||
# Token for global Relay
|
||||
FIREZONE_TOKEN: "SFMyNTY.g2gDaAJtAAAAJGMxMDM4ZTIyLTAyMTUtNDk3Ny05ZjZjLWY2NTYyMWUwMDA4Zm0AAABWT2JubmIzN2RCdE5RY2NDVS1mQll1MWg4TmFmQXAwS3lvT3dsbzJUVEl5NjBvZm9rSWxWNjBzcGExMkc1cElHLVJWS2o1cXdIVkVoMWs5bjh4QmNmOUFuBgAqiFHuiwFiAAFRgA.VyV9cW06PCZyxTefBwIlSCFTDBFEOSRQ2gfJXtMplVE"
|
||||
RUST_LOG: "debug"
|
||||
RUST_BACKTRACE: 1
|
||||
FIREZONE_API_URL: ws://api:8081
|
||||
|
||||
@@ -180,8 +180,12 @@ defmodule API.Client.Channel do
|
||||
with {:ok, resource} <-
|
||||
Resources.fetch_and_authorize_resource_by_id(resource_id, socket.assigns.subject),
|
||||
{:ok, [_ | _] = gateways} <-
|
||||
Gateways.list_connected_gateways_for_resource(resource),
|
||||
{:ok, [_ | _] = relays} <- Relays.list_connected_relays_for_resource(resource) do
|
||||
Gateways.list_connected_gateways_for_resource(resource, preload: :group),
|
||||
gateway_groups = Enum.map(gateways, & &1.group),
|
||||
{relay_hosting_type, relay_connection_type} =
|
||||
Gateways.relay_strategy(gateway_groups),
|
||||
{:ok, [_ | _] = relays} <-
|
||||
Relays.list_connected_relays_for_resource(resource, relay_hosting_type) do
|
||||
location = {
|
||||
socket.assigns.client.last_seen_remote_ip_location_lat,
|
||||
socket.assigns.client.last_seen_remote_ip_location_lon
|
||||
@@ -193,7 +197,12 @@ defmodule API.Client.Channel do
|
||||
reply =
|
||||
{:ok,
|
||||
%{
|
||||
relays: Views.Relay.render_many(relays, socket.assigns.subject.expires_at),
|
||||
relays:
|
||||
Views.Relay.render_many(
|
||||
relays,
|
||||
socket.assigns.subject.expires_at,
|
||||
relay_connection_type
|
||||
),
|
||||
resource_id: resource_id,
|
||||
gateway_id: gateway.id,
|
||||
gateway_remote_ip: gateway.last_seen_remote_ip
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
defmodule API.Client.Views.Relay do
|
||||
alias Domain.Relays
|
||||
|
||||
def render_many(relays, expires_at) do
|
||||
Enum.flat_map(relays, &render(&1, expires_at))
|
||||
def render_many(relays, expires_at, stun_or_turn) do
|
||||
Enum.flat_map(relays, &render(&1, expires_at, stun_or_turn))
|
||||
end
|
||||
|
||||
def render(%Relays.Relay{} = relay, expires_at) do
|
||||
def render(%Relays.Relay{} = relay, expires_at, stun_or_turn) do
|
||||
[
|
||||
maybe_render(relay, expires_at, relay.ipv4),
|
||||
maybe_render(relay, expires_at, relay.ipv6)
|
||||
maybe_render(relay, expires_at, relay.ipv4, stun_or_turn),
|
||||
maybe_render(relay, expires_at, relay.ipv6, stun_or_turn)
|
||||
]
|
||||
|> List.flatten()
|
||||
end
|
||||
|
||||
defp maybe_render(%Relays.Relay{}, _expires_at, nil), do: []
|
||||
defp maybe_render(%Relays.Relay{}, _expires_at, nil, _stun_or_turn), do: []
|
||||
|
||||
defp maybe_render(%Relays.Relay{} = relay, expires_at, address) do
|
||||
# STUN returns the reflective candidates to the peer and is used for hole-punching;
|
||||
# TURN is used to real actual traffic if hole-punching fails. It requires authentication.
|
||||
# WebRTC will automatically fail back to STUN if TURN fails,
|
||||
# so there is no need to send both of them along with each other.
|
||||
|
||||
defp maybe_render(%Relays.Relay{} = relay, _expires_at, address, :stun) do
|
||||
[
|
||||
%{
|
||||
type: :stun,
|
||||
uri: "stun:#{format_address(address)}:#{relay.port}"
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
defp maybe_render(%Relays.Relay{} = relay, expires_at, address, :turn) do
|
||||
%{
|
||||
username: username,
|
||||
password: password,
|
||||
@@ -23,12 +37,6 @@ defmodule API.Client.Views.Relay do
|
||||
} = Relays.generate_username_and_password(relay, expires_at)
|
||||
|
||||
[
|
||||
# WebRTC automatically falls back to STUN if TURN fails,
|
||||
# so no need to send it explicitly
|
||||
# %{
|
||||
# type: :stun,
|
||||
# uri: "stun:#{format_address(address)}:#{relay.port}"
|
||||
# },
|
||||
%{
|
||||
type: :turn,
|
||||
uri: "turn:#{format_address(address)}:#{relay.port}",
|
||||
|
||||
@@ -145,7 +145,12 @@ defmodule API.Gateway.Channel do
|
||||
|
||||
client = Clients.fetch_client_by_id!(client_id, preload: [:actor])
|
||||
resource = Resources.fetch_resource_by_id!(resource_id)
|
||||
{:ok, relays} = Relays.list_connected_relays_for_resource(resource)
|
||||
|
||||
{relay_hosting_type, relay_connection_type} =
|
||||
Gateways.relay_strategy([socket.assigns.gateway_group])
|
||||
|
||||
{:ok, relays} =
|
||||
Relays.list_connected_relays_for_resource(resource, relay_hosting_type)
|
||||
|
||||
ref = Ecto.UUID.generate()
|
||||
|
||||
@@ -153,7 +158,7 @@ defmodule API.Gateway.Channel do
|
||||
ref: ref,
|
||||
flow_id: flow_id,
|
||||
actor: Views.Actor.render(client.actor),
|
||||
relays: Views.Relay.render_many(relays, authorization_expires_at),
|
||||
relays: Views.Relay.render_many(relays, authorization_expires_at, relay_connection_type),
|
||||
resource: Views.Resource.render(resource),
|
||||
client: Views.Client.render(client, rtc_session_description, preshared_key),
|
||||
expires_at: DateTime.to_unix(authorization_expires_at, :second)
|
||||
|
||||
@@ -37,7 +37,8 @@ defmodule API.Gateway.Socket do
|
||||
|> Map.put("last_seen_remote_ip_location_lon", location_lon)
|
||||
|
||||
with {:ok, token} <- Gateways.authorize_gateway(encrypted_secret),
|
||||
{:ok, gateway} <- Gateways.upsert_gateway(token, attrs) do
|
||||
{:ok, gateway} <- Gateways.upsert_gateway(token, attrs),
|
||||
{:ok, gateway_group} <- Gateways.fetch_group_by_id(gateway.group_id) do
|
||||
OpenTelemetry.Tracer.set_attributes(%{
|
||||
gateway_id: gateway.id,
|
||||
account_id: gateway.account_id
|
||||
@@ -46,6 +47,7 @@ defmodule API.Gateway.Socket do
|
||||
socket =
|
||||
socket
|
||||
|> assign(:gateway, gateway)
|
||||
|> assign(:gateway_group, gateway_group)
|
||||
|> assign(:opentelemetry_span_ctx, OpenTelemetry.Tracer.current_span_ctx())
|
||||
|> assign(:opentelemetry_ctx, OpenTelemetry.Ctx.get_current())
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defmodule API.Gateway.Views.Relay do
|
||||
def render_many(relays, expires_at) do
|
||||
Enum.flat_map(relays, &API.Client.Views.Relay.render(&1, expires_at))
|
||||
def render_many(relays, expires_at, conn_type) do
|
||||
Enum.flat_map(relays, &API.Client.Views.Relay.render(&1, expires_at, conn_type))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -73,6 +73,7 @@ defmodule API.Client.ChannelTest do
|
||||
%{
|
||||
account: account,
|
||||
actor: actor,
|
||||
actor_group: actor_group,
|
||||
identity: identity,
|
||||
subject: subject,
|
||||
client: client,
|
||||
@@ -262,15 +263,18 @@ defmodule API.Client.ChannelTest do
|
||||
global_relay =
|
||||
Fixtures.Relays.create_relay(
|
||||
group: global_relay_group,
|
||||
ipv6: nil,
|
||||
last_seen_remote_ip_location_lat: 37,
|
||||
last_seen_remote_ip_location_lon: -120
|
||||
)
|
||||
|
||||
# Creating this Relay to verify it doesn't get returned when :managed routing option is selected
|
||||
relay = Fixtures.Relays.create_relay(account: account)
|
||||
stamp_secret = Ecto.UUID.generate()
|
||||
:ok = Domain.Relays.connect_relay(relay, stamp_secret)
|
||||
|
||||
stamp_secret_global = Ecto.UUID.generate()
|
||||
:ok = Domain.Relays.connect_relay(global_relay, stamp_secret_global)
|
||||
|
||||
# Online Gateway
|
||||
:ok = Domain.Gateways.connect_gateway(gateway)
|
||||
|
||||
@@ -287,6 +291,93 @@ defmodule API.Client.ChannelTest do
|
||||
assert gateway_id == gateway.id
|
||||
assert gateway_last_seen_remote_ip == gateway.last_seen_remote_ip
|
||||
|
||||
ipv4_turn_uri = "turn:#{global_relay.ipv4}:#{global_relay.port}"
|
||||
ipv6_turn_uri = "turn:[#{global_relay.ipv6}]:#{global_relay.port}"
|
||||
|
||||
assert [
|
||||
%{
|
||||
type: :turn,
|
||||
expires_at: expires_at_unix,
|
||||
password: password1,
|
||||
username: username1,
|
||||
uri: ^ipv4_turn_uri
|
||||
},
|
||||
%{
|
||||
type: :turn,
|
||||
expires_at: expires_at_unix,
|
||||
password: password2,
|
||||
username: username2,
|
||||
uri: ^ipv6_turn_uri
|
||||
}
|
||||
] = relays
|
||||
|
||||
assert username1 != username2
|
||||
assert password1 != password2
|
||||
|
||||
assert [expires_at, salt] = String.split(username1, ":", parts: 2)
|
||||
expires_at = expires_at |> String.to_integer() |> DateTime.from_unix!()
|
||||
socket_expires_at = DateTime.truncate(socket.assigns.subject.expires_at, :second)
|
||||
assert expires_at == socket_expires_at
|
||||
|
||||
assert is_binary(salt)
|
||||
end
|
||||
|
||||
test "returns online gateway and self-hosted relays connected to the resource", %{
|
||||
account: account,
|
||||
socket: socket,
|
||||
actor_group: actor_group
|
||||
} do
|
||||
# Gateway setup
|
||||
gateway_group = Fixtures.Gateways.create_group(account: account, routing: :self_hosted)
|
||||
gateway = Fixtures.Gateways.create_gateway(account: account, group: gateway_group)
|
||||
:ok = Domain.Gateways.connect_gateway(gateway)
|
||||
|
||||
# Resource setup
|
||||
resource =
|
||||
Fixtures.Resources.create_resource(
|
||||
account: account,
|
||||
connections: [%{gateway_group_id: gateway_group.id}]
|
||||
)
|
||||
|
||||
Fixtures.Policies.create_policy(
|
||||
account: account,
|
||||
actor_group: actor_group,
|
||||
resource: resource
|
||||
)
|
||||
|
||||
# Global Relay setup
|
||||
global_relay_group = Fixtures.Relays.create_global_group()
|
||||
|
||||
global_relay =
|
||||
Fixtures.Relays.create_relay(
|
||||
group: global_relay_group,
|
||||
last_seen_remote_ip_location_lat: 37,
|
||||
last_seen_remote_ip_location_lon: -120
|
||||
)
|
||||
|
||||
stamp_secret_global = Ecto.UUID.generate()
|
||||
:ok = Domain.Relays.connect_relay(global_relay, stamp_secret_global)
|
||||
|
||||
# Self-hosted Relay setup
|
||||
relay = Fixtures.Relays.create_relay(account: account)
|
||||
stamp_secret = Ecto.UUID.generate()
|
||||
:ok = Domain.Relays.connect_relay(relay, stamp_secret)
|
||||
|
||||
ref = push(socket, "prepare_connection", %{"resource_id" => resource.id})
|
||||
resource_id = resource.id
|
||||
|
||||
assert_reply ref, :ok, %{
|
||||
relays: relays,
|
||||
gateway_id: gateway_id,
|
||||
gateway_remote_ip: gateway_last_seen_remote_ip,
|
||||
resource_id: ^resource_id
|
||||
}
|
||||
|
||||
assert length(relays) == 2
|
||||
|
||||
assert gateway_id == gateway.id
|
||||
assert gateway_last_seen_remote_ip == gateway.last_seen_remote_ip
|
||||
|
||||
ipv4_turn_uri = "turn:#{relay.ipv4}:#{relay.port}"
|
||||
ipv6_turn_uri = "turn:[#{relay.ipv6}]:#{relay.port}"
|
||||
|
||||
@@ -316,12 +407,77 @@ defmodule API.Client.ChannelTest do
|
||||
assert expires_at == socket_expires_at
|
||||
|
||||
assert is_binary(salt)
|
||||
end
|
||||
|
||||
:ok = Domain.Relays.connect_relay(global_relay, stamp_secret)
|
||||
test "returns online gateway and stun-only relay URLs connected to the resource", %{
|
||||
account: account,
|
||||
socket: socket,
|
||||
actor_group: actor_group
|
||||
} do
|
||||
# Gateway setup
|
||||
gateway_group = Fixtures.Gateways.create_group(account: account, routing: :stun_only)
|
||||
gateway = Fixtures.Gateways.create_gateway(account: account, group: gateway_group)
|
||||
:ok = Domain.Gateways.connect_gateway(gateway)
|
||||
|
||||
# Resource setup
|
||||
resource =
|
||||
Fixtures.Resources.create_resource(
|
||||
account: account,
|
||||
connections: [%{gateway_group_id: gateway_group.id}]
|
||||
)
|
||||
|
||||
Fixtures.Policies.create_policy(
|
||||
account: account,
|
||||
actor_group: actor_group,
|
||||
resource: resource
|
||||
)
|
||||
|
||||
# Global Relay setup
|
||||
global_relay_group = Fixtures.Relays.create_global_group()
|
||||
|
||||
global_relay =
|
||||
Fixtures.Relays.create_relay(
|
||||
group: global_relay_group,
|
||||
last_seen_remote_ip_location_lat: 37,
|
||||
last_seen_remote_ip_location_lon: -120
|
||||
)
|
||||
|
||||
stamp_secret_global = Ecto.UUID.generate()
|
||||
:ok = Domain.Relays.connect_relay(global_relay, stamp_secret_global)
|
||||
|
||||
# Self-hosted Relay setup
|
||||
relay = Fixtures.Relays.create_relay(account: account)
|
||||
stamp_secret = Ecto.UUID.generate()
|
||||
:ok = Domain.Relays.connect_relay(relay, stamp_secret)
|
||||
|
||||
ref = push(socket, "prepare_connection", %{"resource_id" => resource.id})
|
||||
assert_reply ref, :ok, %{relays: relays}
|
||||
assert length(relays) == 3
|
||||
resource_id = resource.id
|
||||
|
||||
assert_reply ref, :ok, %{
|
||||
relays: relays,
|
||||
gateway_id: gateway_id,
|
||||
gateway_remote_ip: gateway_last_seen_remote_ip,
|
||||
resource_id: ^resource_id
|
||||
}
|
||||
|
||||
assert length(relays) == 2
|
||||
|
||||
assert gateway_id == gateway.id
|
||||
assert gateway_last_seen_remote_ip == gateway.last_seen_remote_ip
|
||||
|
||||
ipv4_turn_uri = "stun:#{global_relay.ipv4}:#{global_relay.port}"
|
||||
ipv6_turn_uri = "stun:[#{global_relay.ipv6}]:#{global_relay.port}"
|
||||
|
||||
assert [
|
||||
%{
|
||||
type: :stun,
|
||||
uri: ^ipv4_turn_uri
|
||||
},
|
||||
%{
|
||||
type: :stun,
|
||||
uri: ^ipv6_turn_uri
|
||||
}
|
||||
] = relays
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ defmodule API.Gateway.ChannelTest do
|
||||
subject = Fixtures.Auth.create_subject(identity: identity)
|
||||
client = Fixtures.Clients.create_client(subject: subject)
|
||||
gateway = Fixtures.Gateways.create_gateway(account: account)
|
||||
{:ok, gateway_group} = Domain.Gateways.fetch_group_by_id(gateway.group_id, subject)
|
||||
|
||||
resource =
|
||||
Fixtures.Resources.create_resource(
|
||||
@@ -19,12 +20,15 @@ defmodule API.Gateway.ChannelTest do
|
||||
API.Gateway.Socket
|
||||
|> socket("gateway:#{gateway.id}", %{
|
||||
gateway: gateway,
|
||||
gateway_group: gateway_group,
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test")
|
||||
})
|
||||
|> subscribe_and_join(API.Gateway.Channel, "gateway")
|
||||
|
||||
relay = Fixtures.Relays.create_relay(account: account)
|
||||
global_relay_group = Fixtures.Relays.create_global_group()
|
||||
global_relay = Fixtures.Relays.create_relay(group: global_relay_group)
|
||||
|
||||
%{
|
||||
account: account,
|
||||
@@ -35,6 +39,7 @@ defmodule API.Gateway.ChannelTest do
|
||||
gateway: gateway,
|
||||
resource: resource,
|
||||
relay: relay,
|
||||
global_relay: global_relay,
|
||||
socket: socket
|
||||
}
|
||||
end
|
||||
@@ -134,10 +139,10 @@ defmodule API.Gateway.ChannelTest do
|
||||
end
|
||||
|
||||
describe "handle_info/2 :request_connection" do
|
||||
test "pushes request_connection message", %{
|
||||
test "pushes request_connection message with managed relays", %{
|
||||
client: client,
|
||||
resource: resource,
|
||||
relay: relay,
|
||||
global_relay: relay,
|
||||
socket: socket
|
||||
} do
|
||||
channel_pid = self()
|
||||
@@ -226,6 +231,215 @@ defmodule API.Gateway.ChannelTest do
|
||||
|
||||
assert DateTime.from_unix!(payload.expires_at) == DateTime.truncate(expires_at, :second)
|
||||
end
|
||||
|
||||
test "pushes request_connection message with self-hosted relays", %{
|
||||
account: account,
|
||||
client: client,
|
||||
relay: relay
|
||||
} do
|
||||
gateway_group = Fixtures.Gateways.create_group(%{account: account, routing: "self_hosted"})
|
||||
gateway = Fixtures.Gateways.create_gateway(account: account, group: gateway_group)
|
||||
|
||||
resource =
|
||||
Fixtures.Resources.create_resource(
|
||||
account: account,
|
||||
connections: [%{gateway_group_id: gateway_group.id}]
|
||||
)
|
||||
|
||||
{:ok, _, socket} =
|
||||
API.Gateway.Socket
|
||||
|> socket("gateway:#{gateway.id}", %{
|
||||
gateway: gateway,
|
||||
gateway_group: gateway_group,
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test")
|
||||
})
|
||||
|> subscribe_and_join(API.Gateway.Channel, "gateway")
|
||||
|
||||
channel_pid = self()
|
||||
socket_ref = make_ref()
|
||||
expires_at = DateTime.utc_now() |> DateTime.add(30, :second)
|
||||
preshared_key = "PSK"
|
||||
rtc_session_description = "RTC_SD"
|
||||
flow_id = Ecto.UUID.generate()
|
||||
|
||||
otel_ctx = {OpenTelemetry.Ctx.new(), OpenTelemetry.Tracer.start_span("connect")}
|
||||
|
||||
stamp_secret = Ecto.UUID.generate()
|
||||
:ok = Domain.Relays.connect_relay(relay, stamp_secret)
|
||||
|
||||
send(
|
||||
socket.channel_pid,
|
||||
{:request_connection, {channel_pid, socket_ref},
|
||||
%{
|
||||
client_id: client.id,
|
||||
resource_id: resource.id,
|
||||
flow_id: flow_id,
|
||||
authorization_expires_at: expires_at,
|
||||
client_rtc_session_description: rtc_session_description,
|
||||
client_preshared_key: preshared_key
|
||||
}, otel_ctx}
|
||||
)
|
||||
|
||||
assert_push "request_connection", payload
|
||||
|
||||
assert is_binary(payload.ref)
|
||||
assert payload.flow_id == flow_id
|
||||
assert payload.actor == %{id: client.actor_id}
|
||||
|
||||
ipv4_turn_uri = "turn:#{relay.ipv4}:#{relay.port}"
|
||||
ipv6_turn_uri = "turn:[#{relay.ipv6}]:#{relay.port}"
|
||||
|
||||
assert [
|
||||
%{
|
||||
type: :turn,
|
||||
expires_at: expires_at_unix,
|
||||
password: password1,
|
||||
username: username1,
|
||||
uri: ^ipv4_turn_uri
|
||||
},
|
||||
%{
|
||||
type: :turn,
|
||||
expires_at: expires_at_unix,
|
||||
password: password2,
|
||||
username: username2,
|
||||
uri: ^ipv6_turn_uri
|
||||
}
|
||||
] = payload.relays
|
||||
|
||||
assert username1 != username2
|
||||
assert password1 != password2
|
||||
assert [username_expires_at_unix, username_salt] = String.split(username1, ":", parts: 2)
|
||||
assert username_expires_at_unix == to_string(DateTime.to_unix(expires_at, :second))
|
||||
assert DateTime.from_unix!(expires_at_unix) == DateTime.truncate(expires_at, :second)
|
||||
assert is_binary(username_salt)
|
||||
|
||||
assert payload.resource == %{
|
||||
address: resource.address,
|
||||
id: resource.id,
|
||||
name: resource.name,
|
||||
type: :dns,
|
||||
ipv4: resource.ipv4,
|
||||
ipv6: resource.ipv6,
|
||||
filters: [
|
||||
%{protocol: :tcp, port_range_end: 80, port_range_start: 80},
|
||||
%{protocol: :tcp, port_range_end: 433, port_range_start: 433},
|
||||
%{protocol: :udp, port_range_start: 100, port_range_end: 200}
|
||||
]
|
||||
}
|
||||
|
||||
assert payload.client == %{
|
||||
id: client.id,
|
||||
peer: %{
|
||||
ipv4: client.ipv4,
|
||||
ipv6: client.ipv6,
|
||||
persistent_keepalive: 25,
|
||||
preshared_key: preshared_key,
|
||||
public_key: client.public_key
|
||||
},
|
||||
rtc_session_description: rtc_session_description
|
||||
}
|
||||
|
||||
assert DateTime.from_unix!(payload.expires_at) == DateTime.truncate(expires_at, :second)
|
||||
end
|
||||
|
||||
test "pushes request_connection message with stun-only relay URLs", %{
|
||||
account: account,
|
||||
client: client,
|
||||
global_relay: relay
|
||||
} do
|
||||
gateway_group = Fixtures.Gateways.create_group(%{account: account, routing: "stun_only"})
|
||||
gateway = Fixtures.Gateways.create_gateway(account: account, group: gateway_group)
|
||||
|
||||
resource =
|
||||
Fixtures.Resources.create_resource(
|
||||
account: account,
|
||||
connections: [%{gateway_group_id: gateway_group.id}]
|
||||
)
|
||||
|
||||
{:ok, _, socket} =
|
||||
API.Gateway.Socket
|
||||
|> socket("gateway:#{gateway.id}", %{
|
||||
gateway: gateway,
|
||||
gateway_group: gateway_group,
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test")
|
||||
})
|
||||
|> subscribe_and_join(API.Gateway.Channel, "gateway")
|
||||
|
||||
channel_pid = self()
|
||||
socket_ref = make_ref()
|
||||
expires_at = DateTime.utc_now() |> DateTime.add(30, :second)
|
||||
preshared_key = "PSK"
|
||||
rtc_session_description = "RTC_SD"
|
||||
flow_id = Ecto.UUID.generate()
|
||||
|
||||
otel_ctx = {OpenTelemetry.Ctx.new(), OpenTelemetry.Tracer.start_span("connect")}
|
||||
|
||||
stamp_secret = Ecto.UUID.generate()
|
||||
:ok = Domain.Relays.connect_relay(relay, stamp_secret)
|
||||
|
||||
send(
|
||||
socket.channel_pid,
|
||||
{:request_connection, {channel_pid, socket_ref},
|
||||
%{
|
||||
client_id: client.id,
|
||||
resource_id: resource.id,
|
||||
flow_id: flow_id,
|
||||
authorization_expires_at: expires_at,
|
||||
client_rtc_session_description: rtc_session_description,
|
||||
client_preshared_key: preshared_key
|
||||
}, otel_ctx}
|
||||
)
|
||||
|
||||
assert_push "request_connection", payload
|
||||
|
||||
assert is_binary(payload.ref)
|
||||
assert payload.flow_id == flow_id
|
||||
assert payload.actor == %{id: client.actor_id}
|
||||
|
||||
ipv4_turn_uri = "stun:#{relay.ipv4}:#{relay.port}"
|
||||
ipv6_turn_uri = "stun:[#{relay.ipv6}]:#{relay.port}"
|
||||
|
||||
assert [
|
||||
%{
|
||||
type: :stun,
|
||||
uri: ^ipv4_turn_uri
|
||||
},
|
||||
%{
|
||||
type: :stun,
|
||||
uri: ^ipv6_turn_uri
|
||||
}
|
||||
] = payload.relays
|
||||
|
||||
assert payload.resource == %{
|
||||
address: resource.address,
|
||||
id: resource.id,
|
||||
name: resource.name,
|
||||
type: :dns,
|
||||
ipv4: resource.ipv4,
|
||||
ipv6: resource.ipv6,
|
||||
filters: [
|
||||
%{protocol: :tcp, port_range_end: 80, port_range_start: 80},
|
||||
%{protocol: :tcp, port_range_end: 433, port_range_start: 433},
|
||||
%{protocol: :udp, port_range_start: 100, port_range_end: 200}
|
||||
]
|
||||
}
|
||||
|
||||
assert payload.client == %{
|
||||
id: client.id,
|
||||
peer: %{
|
||||
ipv4: client.ipv4,
|
||||
ipv6: client.ipv6,
|
||||
persistent_keepalive: 25,
|
||||
preshared_key: preshared_key,
|
||||
public_key: client.public_key
|
||||
},
|
||||
rtc_session_description: rtc_session_description
|
||||
}
|
||||
|
||||
assert DateTime.from_unix!(payload.expires_at) == DateTime.truncate(expires_at, :second)
|
||||
end
|
||||
end
|
||||
|
||||
describe "handle_in/3 connection_ready" do
|
||||
|
||||
@@ -16,6 +16,27 @@ defmodule Domain.Gateways do
|
||||
Supervisor.init(children, strategy: :one_for_one)
|
||||
end
|
||||
|
||||
def fetch_group_by_id(id) do
|
||||
with true <- Validator.valid_uuid?(id) do
|
||||
Group.Query.by_id(id)
|
||||
|> Repo.fetch()
|
||||
|> case do
|
||||
{:ok, group} ->
|
||||
group =
|
||||
group
|
||||
|> maybe_preload_online_status()
|
||||
|
||||
{:ok, group}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
else
|
||||
false -> {:error, :not_found}
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_group_by_id(id, %Auth.Subject{} = subject, opts \\ []) do
|
||||
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_gateways_permission()),
|
||||
true <- Validator.valid_uuid?(id) do
|
||||
@@ -279,7 +300,8 @@ defmodule Domain.Gateways do
|
||||
end
|
||||
end
|
||||
|
||||
def list_connected_gateways_for_resource(%Resources.Resource{} = resource) do
|
||||
def list_connected_gateways_for_resource(%Resources.Resource{} = resource, opts \\ []) do
|
||||
{preload, _opts} = Keyword.pop(opts, :preload, [])
|
||||
connected_gateways = Presence.list("gateways:#{resource.account_id}")
|
||||
|
||||
gateways =
|
||||
@@ -293,6 +315,7 @@ defmodule Domain.Gateways do
|
||||
|> Gateway.Query.by_account_id(resource.account_id)
|
||||
|> Gateway.Query.by_resource_id(resource.id)
|
||||
|> Repo.all()
|
||||
|> Repo.preload(preload)
|
||||
|
||||
{:ok, gateways}
|
||||
end
|
||||
@@ -462,4 +485,27 @@ defmodule Domain.Gateways do
|
||||
defp fetch_config! do
|
||||
Domain.Config.fetch_env!(:domain, __MODULE__)
|
||||
end
|
||||
|
||||
# Finds the most strict routing strategy for a given list of gateway groups.
|
||||
def relay_strategy(gateway_groups) when is_list(gateway_groups) do
|
||||
strictness = [
|
||||
stun_only: 3,
|
||||
self_hosted: 2,
|
||||
managed: 1
|
||||
]
|
||||
|
||||
gateway_groups
|
||||
|> Enum.max_by(fn %{routing: routing} ->
|
||||
Keyword.fetch!(strictness, routing)
|
||||
end)
|
||||
|> relay_strategy_mapping()
|
||||
end
|
||||
|
||||
defp relay_strategy_mapping(%Group{} = group) do
|
||||
case group.routing do
|
||||
:stun_only -> {:managed, :stun}
|
||||
:self_hosted -> {:self_hosted, :turn}
|
||||
:managed -> {:managed, :turn}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,6 +3,7 @@ defmodule Domain.Gateways.Group do
|
||||
|
||||
schema "gateway_groups" do
|
||||
field :name, :string
|
||||
field :routing, Ecto.Enum, values: ~w[managed self_hosted stun_only]a
|
||||
|
||||
belongs_to :account, Domain.Accounts.Account
|
||||
has_many :gateways, Domain.Gateways.Gateway, foreign_key: :group_id, where: [deleted_at: nil]
|
||||
|
||||
@@ -3,7 +3,7 @@ defmodule Domain.Gateways.Group.Changeset do
|
||||
alias Domain.{Auth, Accounts}
|
||||
alias Domain.Gateways
|
||||
|
||||
@fields ~w[name]a
|
||||
@fields ~w[name routing]a
|
||||
|
||||
def create(%Accounts.Account{} = account, attrs, %Auth.Subject{} = subject) do
|
||||
%Gateways.Group{account: account}
|
||||
|
||||
@@ -255,18 +255,24 @@ defmodule Domain.Relays do
|
||||
end)
|
||||
end
|
||||
|
||||
def list_connected_relays_for_resource(%Resources.Resource{} = resource) do
|
||||
connected_relays =
|
||||
Map.merge(
|
||||
Presence.list("relays"),
|
||||
Presence.list("relays:#{resource.account_id}")
|
||||
)
|
||||
def list_connected_relays_for_resource(%Resources.Resource{} = _resource, :managed) do
|
||||
connected_relays = Presence.list("relays")
|
||||
filter = &Relay.Query.public(&1)
|
||||
list_relays_for_resource(connected_relays, filter)
|
||||
end
|
||||
|
||||
def list_connected_relays_for_resource(%Resources.Resource{} = resource, :self_hosted) do
|
||||
connected_relays = Presence.list("relays:#{resource.account_id}")
|
||||
filter = &Relay.Query.by_account_id(&1, resource.account_id)
|
||||
list_relays_for_resource(connected_relays, filter)
|
||||
end
|
||||
|
||||
defp list_relays_for_resource(connected_relays, filter) do
|
||||
relays =
|
||||
connected_relays
|
||||
|> Map.keys()
|
||||
|> Relay.Query.by_ids()
|
||||
|> Relay.Query.public_or_by_account_id(resource.account_id)
|
||||
|> filter.()
|
||||
|> Repo.all()
|
||||
|> Enum.map(fn relay ->
|
||||
%{metas: [%{secret: stamp_secret}]} = Map.get(connected_relays, relay.id)
|
||||
|
||||
@@ -22,6 +22,14 @@ defmodule Domain.Relays.Relay.Query do
|
||||
where(queryable, [relays: relays], relays.account_id == ^account_id)
|
||||
end
|
||||
|
||||
def public(queryable \\ all()) do
|
||||
where(
|
||||
queryable,
|
||||
[relays: relays],
|
||||
is_nil(relays.account_id)
|
||||
)
|
||||
end
|
||||
|
||||
def public_or_by_account_id(queryable \\ all(), account_id) do
|
||||
where(
|
||||
queryable,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
defmodule Domain.Repo.Migrations.AddRoutingToGatewayGroup do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:gateway_groups) do
|
||||
add(:routing, :string)
|
||||
end
|
||||
|
||||
execute("UPDATE gateway_groups SET routing = 'managed'")
|
||||
|
||||
execute("ALTER TABLE gateway_groups ALTER COLUMN routing SET NOT NULL")
|
||||
end
|
||||
end
|
||||
@@ -294,6 +294,51 @@ all_group
|
||||
|
||||
IO.puts("")
|
||||
|
||||
{:ok, global_relay_group} =
|
||||
Relays.create_global_group(%{
|
||||
name: "fz-global-relays",
|
||||
tokens: [%{}]
|
||||
})
|
||||
|
||||
global_relay_group_token = hd(global_relay_group.tokens)
|
||||
|
||||
global_relay_group_token =
|
||||
maybe_repo_update.(global_relay_group_token,
|
||||
id: "c1038e22-0215-4977-9f6c-f65621e0008f",
|
||||
hash:
|
||||
"$argon2id$v=19$m=65536,t=3,p=4$XBzQrgdRFH5XhiTfWFcGWA$PTTy4D7xtahPbvGTgZLgGS8qHnfd8LJKWAnTdhB4yww",
|
||||
value:
|
||||
"Obnnb37dBtNQccCU-fBYu1h8NafAp0KyoOwlo2TTIy60ofokIlV60spa12G5pIG-RVKj5qwHVEh1k9n8xBcf9A"
|
||||
)
|
||||
|
||||
IO.puts("Created global relay groups:")
|
||||
IO.puts(" #{global_relay_group.name} token: #{Relays.encode_token!(global_relay_group_token)}")
|
||||
|
||||
IO.puts("")
|
||||
|
||||
{:ok, global_relay} =
|
||||
Relays.upsert_relay(global_relay_group_token, %{
|
||||
ipv4: {189, 172, 72, 111},
|
||||
ipv6: {0, 0, 0, 0, 0, 0, 0, 1},
|
||||
last_seen_user_agent: "iOS/12.7 (iPhone) connlib/0.7.412",
|
||||
last_seen_remote_ip: %Postgrex.INET{address: {189, 172, 72, 111}}
|
||||
})
|
||||
|
||||
for i <- 1..5 do
|
||||
{:ok, _global_relay} =
|
||||
Relays.upsert_relay(global_relay_group_token, %{
|
||||
ipv4: {189, 172, 72, 111 + i},
|
||||
ipv6: {0, 0, 0, 0, 0, 0, 0, i},
|
||||
last_seen_user_agent: "iOS/12.7 (iPhone) connlib/0.7.412",
|
||||
last_seen_remote_ip: %Postgrex.INET{address: {189, 172, 72, 111 + i}}
|
||||
})
|
||||
end
|
||||
|
||||
IO.puts("Created global relays:")
|
||||
IO.puts(" Group #{global_relay_group.name}:")
|
||||
IO.puts(" IPv4: #{global_relay.ipv4} IPv6: #{global_relay.ipv6}")
|
||||
IO.puts("")
|
||||
|
||||
relay_group =
|
||||
account
|
||||
|> Relays.Group.Changeset.create(
|
||||
@@ -342,7 +387,7 @@ IO.puts("")
|
||||
gateway_group =
|
||||
account
|
||||
|> Gateways.Group.Changeset.create(
|
||||
%{name: "mycro-aws-gws", tokens: [%{}]},
|
||||
%{name: "mycro-aws-gws", routing: "managed", tokens: [%{}]},
|
||||
admin_subject
|
||||
)
|
||||
|> Repo.insert!()
|
||||
|
||||
@@ -72,6 +72,30 @@ defmodule Domain.GatewaysTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "fetch_group_by_id/1" do
|
||||
test "returns error when UUID is invalid" do
|
||||
assert fetch_group_by_id("foo") == {:error, :not_found}
|
||||
end
|
||||
|
||||
test "does not return deleted groups", %{account: account} do
|
||||
group =
|
||||
Fixtures.Gateways.create_group(account: account)
|
||||
|> Fixtures.Gateways.delete_group()
|
||||
|
||||
assert fetch_group_by_id(group.id) == {:error, :not_found}
|
||||
end
|
||||
|
||||
test "returns group by id", %{account: account} do
|
||||
group = Fixtures.Gateways.create_group(account: account)
|
||||
assert {:ok, fetched_group} = fetch_group_by_id(group.id)
|
||||
assert fetched_group.id == group.id
|
||||
end
|
||||
|
||||
test "returns error when group does not exist" do
|
||||
assert fetch_group_by_id(Ecto.UUID.generate()) == {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_groups/1" do
|
||||
test "returns empty list when there are no groups", %{subject: subject} do
|
||||
assert list_groups(subject) == {:ok, []}
|
||||
@@ -129,7 +153,7 @@ defmodule Domain.GatewaysTest do
|
||||
describe "create_group/2" do
|
||||
test "returns error on empty attrs", %{subject: subject} do
|
||||
assert {:error, changeset} = create_group(%{}, subject)
|
||||
assert errors_on(changeset) == %{tokens: ["can't be blank"]}
|
||||
assert errors_on(changeset) == %{tokens: ["can't be blank"], routing: ["can't be blank"]}
|
||||
end
|
||||
|
||||
test "returns error on invalid attrs", %{account: account, subject: subject} do
|
||||
@@ -141,18 +165,34 @@ defmodule Domain.GatewaysTest do
|
||||
|
||||
assert errors_on(changeset) == %{
|
||||
tokens: ["can't be blank"],
|
||||
name: ["should be at most 64 character(s)"]
|
||||
name: ["should be at most 64 character(s)"],
|
||||
routing: ["can't be blank"]
|
||||
}
|
||||
|
||||
Fixtures.Gateways.create_group(account: account, name: "foo")
|
||||
attrs = %{name: "foo", tokens: [%{}]}
|
||||
attrs = %{name: "foo", tokens: [%{}], routing: "managed"}
|
||||
assert {:error, changeset} = create_group(attrs, subject)
|
||||
assert "has already been taken" in errors_on(changeset).name
|
||||
end
|
||||
|
||||
test "returns error on invalid routing value", %{subject: subject} do
|
||||
attrs = %{
|
||||
name_prefix: "foo",
|
||||
routing: "foo",
|
||||
tokens: [%{}]
|
||||
}
|
||||
|
||||
assert {:error, changeset} = create_group(attrs, subject)
|
||||
|
||||
assert errors_on(changeset) == %{
|
||||
routing: ["is invalid"]
|
||||
}
|
||||
end
|
||||
|
||||
test "creates a group", %{subject: subject} do
|
||||
attrs = %{
|
||||
name: "foo",
|
||||
routing: "managed",
|
||||
tokens: [%{}]
|
||||
}
|
||||
|
||||
@@ -163,6 +203,8 @@ defmodule Domain.GatewaysTest do
|
||||
assert group.created_by == :identity
|
||||
assert group.created_by_identity_id == subject.identity.id
|
||||
|
||||
assert group.routing == :managed
|
||||
|
||||
assert [%Gateways.Token{} = token] = group.tokens
|
||||
assert token.created_by == :identity
|
||||
assert token.created_by_identity_id == subject.identity.id
|
||||
@@ -210,13 +252,15 @@ defmodule Domain.GatewaysTest do
|
||||
group = Fixtures.Gateways.create_group(account: account)
|
||||
|
||||
attrs = %{
|
||||
name: String.duplicate("A", 65)
|
||||
name: String.duplicate("A", 65),
|
||||
routing: "foo"
|
||||
}
|
||||
|
||||
assert {:error, changeset} = update_group(group, attrs, subject)
|
||||
|
||||
assert errors_on(changeset) == %{
|
||||
name: ["should be at most 64 character(s)"]
|
||||
name: ["should be at most 64 character(s)"],
|
||||
routing: ["is invalid"]
|
||||
}
|
||||
|
||||
Fixtures.Gateways.create_group(account: account, name: "foo")
|
||||
@@ -229,11 +273,13 @@ defmodule Domain.GatewaysTest do
|
||||
group = Fixtures.Gateways.create_group(account: account)
|
||||
|
||||
attrs = %{
|
||||
name: "foo"
|
||||
name: "foo",
|
||||
routing: "stun_only"
|
||||
}
|
||||
|
||||
assert {:ok, group} = update_group(group, attrs, subject)
|
||||
assert group.name == "foo"
|
||||
assert group.routing == :stun_only
|
||||
end
|
||||
|
||||
test "returns error when subject has no permission to manage groups", %{
|
||||
@@ -962,4 +1008,35 @@ defmodule Domain.GatewaysTest do
|
||||
assert authorize_gateway(Ecto.UUID.generate()) == {:error, :invalid_token}
|
||||
end
|
||||
end
|
||||
|
||||
describe "relay_strategy/1" do
|
||||
test "managed strategy" do
|
||||
group = Fixtures.Gateways.create_group(routing: :managed)
|
||||
assert {:managed, :turn} == relay_strategy([group])
|
||||
end
|
||||
|
||||
test "self-hosted strategy" do
|
||||
group = Fixtures.Gateways.create_group(routing: :self_hosted)
|
||||
assert {:self_hosted, :turn} == relay_strategy([group])
|
||||
end
|
||||
|
||||
test "stun_only strategy" do
|
||||
group = Fixtures.Gateways.create_group(routing: :stun_only)
|
||||
assert {:managed, :stun} == relay_strategy([group])
|
||||
end
|
||||
|
||||
test "strictest strategy is returned" do
|
||||
managed_group = Fixtures.Gateways.create_group(routing: :managed)
|
||||
self_hosted_group = Fixtures.Gateways.create_group(routing: :self_hosted)
|
||||
stun_only_group = Fixtures.Gateways.create_group(routing: :stun_only)
|
||||
|
||||
assert {:managed, :stun} ==
|
||||
relay_strategy([managed_group, self_hosted_group, stun_only_group])
|
||||
|
||||
assert {:self_hosted, :turn} == relay_strategy([managed_group, self_hosted_group])
|
||||
assert {:managed, :stun} == relay_strategy([managed_group, stun_only_group])
|
||||
assert {:managed, :stun} == relay_strategy([self_hosted_group, stun_only_group])
|
||||
assert {:managed, :turn} == relay_strategy([managed_group])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -497,8 +497,17 @@ defmodule Domain.RelaysTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_connected_relays_for_resource/1" do
|
||||
test "returns empty list when there are no online relays", %{account: account} do
|
||||
describe "list_connected_relays_for_resource/2" do
|
||||
test "returns empty list when there are no managed relays online", %{account: account} do
|
||||
resource = Fixtures.Resources.create_resource(account: account)
|
||||
group = Fixtures.Relays.create_global_group()
|
||||
|
||||
Fixtures.Relays.create_relay(group: group)
|
||||
|
||||
assert list_connected_relays_for_resource(resource, :managed) == {:ok, []}
|
||||
end
|
||||
|
||||
test "returns empty list when there are no self-hosted relays online", %{account: account} do
|
||||
resource = Fixtures.Resources.create_resource(account: account)
|
||||
|
||||
Fixtures.Relays.create_relay(account: account)
|
||||
@@ -506,7 +515,7 @@ defmodule Domain.RelaysTest do
|
||||
Fixtures.Relays.create_relay(account: account)
|
||||
|> Fixtures.Relays.delete_relay()
|
||||
|
||||
assert list_connected_relays_for_resource(resource) == {:ok, []}
|
||||
assert list_connected_relays_for_resource(resource, :self_hosted) == {:ok, []}
|
||||
end
|
||||
|
||||
test "returns list of connected account relays", %{account: account} do
|
||||
@@ -516,7 +525,7 @@ defmodule Domain.RelaysTest do
|
||||
|
||||
assert connect_relay(relay, stamp_secret) == :ok
|
||||
|
||||
assert {:ok, [connected_relay]} = list_connected_relays_for_resource(resource)
|
||||
assert {:ok, [connected_relay]} = list_connected_relays_for_resource(resource, :self_hosted)
|
||||
|
||||
assert connected_relay.id == relay.id
|
||||
assert connected_relay.stamp_secret == stamp_secret
|
||||
@@ -530,7 +539,7 @@ defmodule Domain.RelaysTest do
|
||||
|
||||
assert connect_relay(relay, stamp_secret) == :ok
|
||||
|
||||
assert {:ok, [connected_relay]} = list_connected_relays_for_resource(resource)
|
||||
assert {:ok, [connected_relay]} = list_connected_relays_for_resource(resource, :managed)
|
||||
|
||||
assert connected_relay.id == relay.id
|
||||
assert connected_relay.stamp_secret == stamp_secret
|
||||
|
||||
@@ -5,6 +5,7 @@ defmodule Domain.Fixtures.Gateways do
|
||||
def group_attrs(attrs \\ %{}) do
|
||||
Enum.into(attrs, %{
|
||||
name: "group-#{unique_integer()}",
|
||||
routing: "managed",
|
||||
tokens: [%{}]
|
||||
})
|
||||
end
|
||||
|
||||
12
elixir/apps/web/lib/web/live/sites/components.ex
Normal file
12
elixir/apps/web/lib/web/live/sites/components.ex
Normal file
@@ -0,0 +1,12 @@
|
||||
defmodule Web.Sites.Components do
|
||||
use Web, :component_library
|
||||
|
||||
def pretty_print_routing(routing) do
|
||||
case routing do
|
||||
:managed -> "Firezone Managed Relays"
|
||||
:self_hosted -> "Self Hosted Relays"
|
||||
:stun_only -> "Direct Only"
|
||||
routing -> to_string(routing)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,6 @@
|
||||
defmodule Web.Sites.Edit do
|
||||
use Web, :live_view
|
||||
import Web.Sites.Components
|
||||
alias Domain.Gateways
|
||||
|
||||
def mount(%{"id" => id}, _session, socket) do
|
||||
@@ -28,12 +29,50 @@ defmodule Web.Sites.Edit do
|
||||
<.form for={@form} phx-change={:change} phx-submit={:submit}>
|
||||
<div class="grid gap-4 mb-4 sm:grid-cols-1 sm:gap-6 sm:mb-6">
|
||||
<div>
|
||||
<.input
|
||||
label="Name Prefix"
|
||||
field={@form[:name]}
|
||||
placeholder="Name of this Site"
|
||||
required
|
||||
/>
|
||||
<.input label="Name" field={@form[:name]} placeholder="Name of this Site" required />
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-lg text-slate-900 mb-2">
|
||||
Data Routing -
|
||||
<a
|
||||
class={[link_style(), "text-sm"]}
|
||||
href="https://www.firezone.dev/kb?utm_source=product"
|
||||
target="_blank"
|
||||
>
|
||||
Read about routing in Firezone
|
||||
</a>
|
||||
</p>
|
||||
<div>
|
||||
<div>
|
||||
<.input
|
||||
id="routing-option-managed"
|
||||
type="radio"
|
||||
field={@form[:routing]}
|
||||
value="managed"
|
||||
label={pretty_print_routing(:managed)}
|
||||
checked={@form[:routing].value == :managed}
|
||||
required
|
||||
/>
|
||||
<p class="ml-6 mb-4 text-sm text-slate-500 dark:text-slate-400">
|
||||
Firezone will route connections through our managed Relays only if a direct connection to a Gateway is not possible.
|
||||
Firezone can never decrypt the contents of your traffic.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<.input
|
||||
id="routing-option-stun-only"
|
||||
type="radio"
|
||||
field={@form[:routing]}
|
||||
value="stun_only"
|
||||
label={pretty_print_routing(:stun_only)}
|
||||
checked={@form[:routing].value == :stun_only}
|
||||
required
|
||||
/>
|
||||
<p class="ml-6 mb-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
Firezone will enforce direct connections to all Gateways in this Site. This could cause connectivity issues in rare cases.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<.submit_button>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
defmodule Web.Sites.New do
|
||||
use Web, :live_view
|
||||
import Web.Sites.Components
|
||||
alias Domain.Gateways
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
@@ -23,12 +24,50 @@ defmodule Web.Sites.New do
|
||||
<.form for={@form} phx-change={:change} phx-submit={:submit}>
|
||||
<div class="grid gap-4 mb-4 sm:grid-cols-1 sm:gap-6 sm:mb-6">
|
||||
<div>
|
||||
<.input
|
||||
label="Name Prefix"
|
||||
field={@form[:name]}
|
||||
placeholder="Name of this Site"
|
||||
required
|
||||
/>
|
||||
<.input label="Name" field={@form[:name]} placeholder="Name of this Site" required />
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-lg text-slate-900 mb-2">
|
||||
Data Routing -
|
||||
<a
|
||||
class={[link_style(), "text-sm"]}
|
||||
href="https://www.firezone.dev/kb?utm_source=product"
|
||||
target="_blank"
|
||||
>
|
||||
Read about routing in Firezone
|
||||
</a>
|
||||
</p>
|
||||
<div>
|
||||
<div>
|
||||
<.input
|
||||
id="routing-option-managed"
|
||||
type="radio"
|
||||
field={@form[:routing]}
|
||||
value="managed"
|
||||
label={pretty_print_routing(:managed)}
|
||||
checked={@form[:routing].value == :managed}
|
||||
required
|
||||
/>
|
||||
<p class="ml-6 mb-4 text-sm text-slate-500 dark:text-slate-400">
|
||||
Firezone will route connections through our managed Relays only if a direct connection to a Gateway is not possible.
|
||||
Firezone can never decrypt the contents of your traffic.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<.input
|
||||
id="routing-option-stun-only"
|
||||
type="radio"
|
||||
field={@form[:routing]}
|
||||
value="stun_only"
|
||||
label={pretty_print_routing(:stun_only)}
|
||||
checked={@form[:routing].value == :stun_only}
|
||||
required
|
||||
/>
|
||||
<p class="ml-6 mb-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
Firezone will enforce direct connections to all Gateways in this Site. This could cause connectivity issues in rare cases.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
defmodule Web.Sites.Show do
|
||||
use Web, :live_view
|
||||
import Web.Sites.Components
|
||||
alias Domain.{Gateways, Resources}
|
||||
|
||||
def mount(%{"id" => id}, _session, socket) do
|
||||
@@ -58,6 +59,10 @@ defmodule Web.Sites.Show do
|
||||
<:label>Name</:label>
|
||||
<:value><%= @group.name %></:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Data Routing</:label>
|
||||
<:value><%= pretty_print_routing(@group.routing) %></:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Created</:label>
|
||||
<:value>
|
||||
|
||||
@@ -77,7 +77,8 @@ defmodule Web.Live.Sites.EditTest do
|
||||
form = form(lv, "form")
|
||||
|
||||
assert find_inputs(form) == [
|
||||
"group[name]"
|
||||
"group[name]",
|
||||
"group[routing]"
|
||||
]
|
||||
end
|
||||
|
||||
@@ -136,7 +137,8 @@ defmodule Web.Live.Sites.EditTest do
|
||||
group: group,
|
||||
conn: conn
|
||||
} do
|
||||
attrs = Fixtures.Gateways.group_attrs() |> Map.take([:name])
|
||||
attrs = Fixtures.Gateways.group_attrs() |> Map.take([:name, :routing])
|
||||
attrs = %{attrs | routing: "stun_only"}
|
||||
|
||||
{:ok, lv, _html} =
|
||||
conn
|
||||
|
||||
@@ -55,7 +55,8 @@ defmodule Web.Live.Sites.NewTest do
|
||||
form = form(lv, "form")
|
||||
|
||||
assert find_inputs(form) == [
|
||||
"group[name]"
|
||||
"group[name]",
|
||||
"group[routing]"
|
||||
]
|
||||
end
|
||||
|
||||
@@ -86,7 +87,7 @@ defmodule Web.Live.Sites.NewTest do
|
||||
conn: conn
|
||||
} do
|
||||
other_gateway = Fixtures.Gateways.create_group(account: account)
|
||||
attrs = %{name: other_gateway.name}
|
||||
attrs = %{name: other_gateway.name, routing: "managed"}
|
||||
|
||||
{:ok, lv, _html} =
|
||||
conn
|
||||
@@ -106,7 +107,7 @@ defmodule Web.Live.Sites.NewTest do
|
||||
identity: identity,
|
||||
conn: conn
|
||||
} do
|
||||
attrs = Fixtures.Gateways.group_attrs() |> Map.take([:name])
|
||||
attrs = Fixtures.Gateways.group_attrs() |> Map.take([:name, :routing])
|
||||
|
||||
{:ok, lv, _html} =
|
||||
conn
|
||||
|
||||
Reference in New Issue
Block a user