mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
refactor(portal): Enforce internet resource site exclusion (#8448)
Finishes up the Internet Resource migration by enforcing: - No internet resources in non-internet sites - No regular resources in internet sites - Removing the prompt to migrate ~~I've already migrated the existing internet resources in customer's accounts. No one that was using the internet resource hadn't already migrated.~~ Edit: I started to head down that path, then decided doing this here in a data migration was going to be a better approach. Fixes #8212
This commit is contained in:
@@ -28,6 +28,14 @@ defmodule API.Client.ChannelTest do
|
||||
gateway_group_token = Fixtures.Gateways.create_token(account: account, group: gateway_group)
|
||||
gateway = Fixtures.Gateways.create_gateway(account: account, group: gateway_group)
|
||||
|
||||
internet_gateway_group = Fixtures.Gateways.create_internet_group(account: account)
|
||||
|
||||
internet_gateway_group_token =
|
||||
Fixtures.Gateways.create_token(account: account, group: internet_gateway_group)
|
||||
|
||||
internet_gateway =
|
||||
Fixtures.Gateways.create_gateway(account: account, group: internet_gateway_group)
|
||||
|
||||
dns_resource =
|
||||
Fixtures.Resources.create_resource(
|
||||
account: account,
|
||||
@@ -51,10 +59,9 @@ defmodule API.Client.ChannelTest do
|
||||
)
|
||||
|
||||
internet_resource =
|
||||
Fixtures.Resources.create_resource(
|
||||
type: :internet,
|
||||
Fixtures.Resources.create_internet_resource(
|
||||
account: account,
|
||||
connections: [%{gateway_group_id: gateway_group.id}]
|
||||
connections: [%{gateway_group_id: internet_gateway_group.id}]
|
||||
)
|
||||
|
||||
unauthorized_resource =
|
||||
@@ -142,6 +149,9 @@ defmodule API.Client.ChannelTest do
|
||||
gateway_group_token: gateway_group_token,
|
||||
gateway_group: gateway_group,
|
||||
gateway: gateway,
|
||||
internet_gateway_group: internet_gateway_group,
|
||||
internet_gateway_group_token: internet_gateway_group_token,
|
||||
internet_gateway: internet_gateway,
|
||||
dns_resource: dns_resource,
|
||||
cidr_resource: cidr_resource,
|
||||
ip_resource: ip_resource,
|
||||
@@ -255,6 +265,7 @@ defmodule API.Client.ChannelTest do
|
||||
|
||||
test "sends list of available resources after join", %{
|
||||
client: client,
|
||||
internet_gateway_group: internet_gateway_group,
|
||||
gateway_group: gateway_group,
|
||||
dns_resource: dns_resource,
|
||||
cidr_resource: cidr_resource,
|
||||
@@ -337,8 +348,8 @@ defmodule API.Client.ChannelTest do
|
||||
type: :internet,
|
||||
gateway_groups: [
|
||||
%{
|
||||
id: gateway_group.id,
|
||||
name: gateway_group.name
|
||||
id: internet_gateway_group.id,
|
||||
name: internet_gateway_group.name
|
||||
}
|
||||
],
|
||||
can_be_disabled: true
|
||||
@@ -1215,10 +1226,10 @@ defmodule API.Client.ChannelTest do
|
||||
|
||||
test "returns online gateway connected to an internet resource", %{
|
||||
account: account,
|
||||
internet_gateway_group_token: gateway_group_token,
|
||||
internet_gateway: gateway,
|
||||
internet_resource: resource,
|
||||
client: client,
|
||||
gateway_group_token: gateway_group_token,
|
||||
gateway: gateway,
|
||||
socket: socket
|
||||
} do
|
||||
Fixtures.Accounts.update_account(account,
|
||||
@@ -1722,8 +1733,8 @@ defmodule API.Client.ChannelTest do
|
||||
|
||||
test "returns gateway that support Internet resources", %{
|
||||
account: account,
|
||||
internet_gateway_group: internet_gateway_group,
|
||||
internet_resource: resource,
|
||||
subject: subject,
|
||||
socket: socket
|
||||
} do
|
||||
account =
|
||||
@@ -1742,24 +1753,15 @@ defmodule API.Client.ChannelTest do
|
||||
last_seen_at: DateTime.utc_now() |> DateTime.add(-10, :second)
|
||||
)
|
||||
|
||||
gateway_group = Fixtures.Gateways.create_group(account: account)
|
||||
|
||||
gateway =
|
||||
Fixtures.Gateways.create_gateway(
|
||||
account: account,
|
||||
group: gateway_group,
|
||||
group: internet_gateway_group,
|
||||
context: %{
|
||||
user_agent: "iOS/12.5 (iPhone) connlib/1.2.0"
|
||||
}
|
||||
)
|
||||
|
||||
{:updated, resource} =
|
||||
Domain.Resources.update_resource(
|
||||
resource,
|
||||
%{connections: [%{gateway_group_id: gateway_group.id}]},
|
||||
subject
|
||||
)
|
||||
|
||||
:ok = Domain.Gateways.connect_gateway(gateway)
|
||||
|
||||
ref = push(socket, "prepare_connection", %{"resource_id" => resource.id})
|
||||
@@ -1770,7 +1772,7 @@ defmodule API.Client.ChannelTest do
|
||||
gateway =
|
||||
Fixtures.Gateways.create_gateway(
|
||||
account: account,
|
||||
group: gateway_group,
|
||||
group: internet_gateway_group,
|
||||
context: %{
|
||||
user_agent: "iOS/12.5 (iPhone) connlib/1.3.0"
|
||||
}
|
||||
|
||||
@@ -134,10 +134,12 @@ defmodule API.Gateway.ChannelTest do
|
||||
relay: relay,
|
||||
socket: socket
|
||||
} do
|
||||
internet_gateway_group = Fixtures.Gateways.create_internet_group(account: account)
|
||||
|
||||
resource =
|
||||
Fixtures.Resources.create_resource(
|
||||
type: :internet,
|
||||
account: account
|
||||
Fixtures.Resources.create_internet_resource(
|
||||
account: account,
|
||||
connections: [%{gateway_group_id: internet_gateway_group.id}]
|
||||
)
|
||||
|
||||
channel_pid = self()
|
||||
|
||||
@@ -23,6 +23,13 @@ defmodule Domain.Resources do
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_internet_resource(%Accounts.Account{} = account) do
|
||||
Resource.Query.all()
|
||||
|> Resource.Query.by_account_id(account.id)
|
||||
|> Resource.Query.by_type(:internet)
|
||||
|> Repo.fetch(Resource.Query)
|
||||
end
|
||||
|
||||
def fetch_internet_resource(%Auth.Subject{} = subject, opts \\ []) do
|
||||
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_resources_permission()) do
|
||||
Resource.Query.all()
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
defmodule Domain.Repo.Migrations.MigrateInternetResources do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
# Step 1
|
||||
#
|
||||
# Delete all connections where the resource is an internet resource and the gateway group is not the internet site.
|
||||
# The Internet resource is / will not be a multi-site resource.
|
||||
execute("""
|
||||
DELETE FROM resource_connections rc
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM resources r
|
||||
WHERE r.id = rc.resource_id AND r.type = 'internet'
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM gateway_groups gg
|
||||
WHERE gg.id = rc.gateway_group_id
|
||||
AND gg.name = 'Internet'
|
||||
AND gg.managed_by = 'system'
|
||||
)
|
||||
""")
|
||||
|
||||
# Step 2
|
||||
#
|
||||
# Insert a connection to the internet site for each internet resource that does not already have one
|
||||
execute("""
|
||||
INSERT INTO resource_connections (
|
||||
resource_id,
|
||||
gateway_group_id,
|
||||
account_id,
|
||||
created_by
|
||||
)
|
||||
SELECT r.id, gg.id, r.account_id, 'system'
|
||||
FROM resources r
|
||||
JOIN gateway_groups gg ON r.account_id = gg.account_id
|
||||
WHERE r.type = 'internet'
|
||||
AND gg.name = 'Internet'
|
||||
AND gg.managed_by = 'system'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM resource_connections rc
|
||||
WHERE rc.resource_id = r.id
|
||||
AND rc.gateway_group_id = gg.id
|
||||
)
|
||||
""")
|
||||
|
||||
# Step 3
|
||||
#
|
||||
# Recreate existing constraint so that both hold true:
|
||||
# - only internet resources can be in the internet site
|
||||
# - on non-internet resources can be in other sites
|
||||
# See 20250224205226_require_internet_resource_in_internet_site for the previous migration.
|
||||
# We want to now enforce that only the Internet Resource can be in the Internet Site.
|
||||
|
||||
execute("""
|
||||
DROP TRIGGER IF EXISTS internet_resource_in_internet_gg ON resource_connections;
|
||||
""")
|
||||
|
||||
execute("""
|
||||
DROP FUNCTION IF EXISTS enforce_internet_resource_in_internet_gg();
|
||||
""")
|
||||
|
||||
execute("""
|
||||
CREATE OR REPLACE FUNCTION enforce_internet_resource_in_internet_gg()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
resource_type text;
|
||||
site_name text;
|
||||
site_managed_by text;
|
||||
BEGIN
|
||||
-- Fetch the resource type and gateway group details
|
||||
SELECT r.type INTO resource_type
|
||||
FROM resources r
|
||||
WHERE r.id = NEW.resource_id;
|
||||
|
||||
SELECT gg.name, gg.managed_by INTO site_name, site_managed_by
|
||||
FROM gateway_groups gg
|
||||
WHERE gg.id = NEW.gateway_group_id;
|
||||
|
||||
-- Rule: Prevent non-'internet' resources in the 'Internet' gateway group
|
||||
IF (site_name = 'Internet' AND site_managed_by = 'system' AND resource_type != 'internet')
|
||||
OR (resource_type = 'internet' AND (site_name != 'Internet' OR site_managed_by != 'system')) THEN
|
||||
RAISE EXCEPTION 'Only internet resource type is allowed in the Internet site'
|
||||
USING ERRCODE = '23514', CONSTRAINT = 'internet_resource_in_internet_site';
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
""")
|
||||
|
||||
execute("""
|
||||
CREATE TRIGGER internet_resource_in_internet_gg
|
||||
BEFORE INSERT OR UPDATE OF resource_id, gateway_group_id
|
||||
ON resource_connections
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION enforce_internet_resource_in_internet_gg()
|
||||
""")
|
||||
end
|
||||
end
|
||||
@@ -28,6 +28,19 @@ defmodule Domain.Fixtures.Gateways do
|
||||
group
|
||||
end
|
||||
|
||||
def create_internet_group(attrs \\ %{}) do
|
||||
attrs = group_attrs(attrs)
|
||||
|
||||
{account, _attrs} =
|
||||
pop_assoc_fixture(attrs, :account, fn assoc_attrs ->
|
||||
Fixtures.Accounts.create_account(assoc_attrs)
|
||||
end)
|
||||
|
||||
{:ok, group} = Gateways.create_internet_group(account)
|
||||
|
||||
group
|
||||
end
|
||||
|
||||
def delete_group(group) do
|
||||
group = Repo.preload(group, :account)
|
||||
|
||||
|
||||
@@ -49,6 +49,29 @@ defmodule Domain.Fixtures.Resources do
|
||||
resource
|
||||
end
|
||||
|
||||
def create_internet_resource(attrs \\ %{}) do
|
||||
attrs = resource_attrs(attrs)
|
||||
|
||||
{account, attrs} =
|
||||
pop_assoc_fixture(attrs, :account, fn assoc_attrs ->
|
||||
Fixtures.Accounts.create_account(assoc_attrs)
|
||||
end)
|
||||
|
||||
{subject, attrs} =
|
||||
pop_assoc_fixture(attrs, :subject, fn assoc_attrs ->
|
||||
assoc_attrs
|
||||
|> Enum.into(%{account: account, actor: [type: :account_admin_user]})
|
||||
|> Fixtures.Auth.create_subject()
|
||||
end)
|
||||
|
||||
{:ok, resource} =
|
||||
attrs
|
||||
|> Map.put(:type, :internet)
|
||||
|> Domain.Resources.create_resource(subject)
|
||||
|
||||
resource
|
||||
end
|
||||
|
||||
def delete_resource(resource) do
|
||||
resource = Repo.preload(resource, :account)
|
||||
|
||||
|
||||
@@ -243,57 +243,7 @@ defmodule Web.Sites.Index do
|
||||
<:help>
|
||||
Use the Internet Site to manage secure, private access to the public internet for your workforce.
|
||||
</:help>
|
||||
|
||||
<:content :if={
|
||||
Domain.Accounts.internet_resource_enabled?(@account) &&
|
||||
needs_internet_resource_migration?(@internet_resource, @internet_gateway_group)
|
||||
}>
|
||||
<div class="px-1 text-neutral-500">
|
||||
<p class="mb-2">
|
||||
ACTION REQUIRED: Please migrate your existing Internet Resource to this Site before <strong>March 15, 2025</strong>.
|
||||
</p>
|
||||
<p class="mb-8 text-sm">
|
||||
<.website_link path="/blog/migrate-your-internet-resource">
|
||||
Read more about why this is necessary.
|
||||
</.website_link>
|
||||
</p>
|
||||
<.button_with_confirmation
|
||||
id="migrate_internet_resource"
|
||||
style="warning"
|
||||
confirm_style="warning"
|
||||
icon="hero-exclamation-triangle-solid"
|
||||
on_confirm="migrate_internet_resource"
|
||||
>
|
||||
<:dialog_title>
|
||||
Confirm Internet Resource Migration from {@existing_internet_resource_group_name}
|
||||
</:dialog_title>
|
||||
<:dialog_content>
|
||||
<p class="text-center my-8">
|
||||
<.icon name="hero-exclamation-triangle-solid" class="w-16 h-16 text-primary-500" />
|
||||
</p>
|
||||
<p class="mb-2">
|
||||
Migrating the Internet Resource will permanently
|
||||
move it from the <strong>{@existing_internet_resource_group_name}</strong>
|
||||
Site to the <strong>Internet</strong>
|
||||
Site. This cannot be reversed.
|
||||
</p>
|
||||
<p class="mb-2">
|
||||
Any Clients connected to this Resource will be immediately disconnected.
|
||||
</p>
|
||||
<p>
|
||||
To minimize downtime, it is recommended to deploy new Gateways in the Internet Site before completing the migration of the Internet Resource.
|
||||
</p>
|
||||
</:dialog_content>
|
||||
<:dialog_confirm_button>
|
||||
Migrate Internet Resource
|
||||
</:dialog_confirm_button>
|
||||
<:dialog_cancel_button>
|
||||
Cancel
|
||||
</:dialog_cancel_button>
|
||||
Migrate Internet Resource
|
||||
</.button_with_confirmation>
|
||||
</div>
|
||||
</:content>
|
||||
<:content></:content>
|
||||
</.section>
|
||||
"""
|
||||
end
|
||||
@@ -331,64 +281,4 @@ defmodule Web.Sites.Index do
|
||||
|
||||
def handle_event(event, params, socket) when event in ["paginate", "order_by", "filter"],
|
||||
do: handle_live_table_event(event, params, socket)
|
||||
|
||||
def handle_event("migrate_internet_resource", _, socket) do
|
||||
internet_resource = socket.assigns.internet_resource
|
||||
internet_gateway_group = socket.assigns.internet_gateway_group
|
||||
|
||||
case migrate_internet_resource(
|
||||
internet_resource,
|
||||
internet_gateway_group,
|
||||
socket.assigns.subject
|
||||
) do
|
||||
{:ok, internet_resource} ->
|
||||
socket =
|
||||
socket
|
||||
|> assign(internet_resource: internet_resource)
|
||||
|> put_flash(:info, "Internet Resource migrated successfully.")
|
||||
|
||||
{:noreply, socket}
|
||||
|
||||
_ ->
|
||||
{:noreply, socket |> put_flash(:error, "Failed to migrate Internet Resource.")}
|
||||
end
|
||||
end
|
||||
|
||||
defp needs_internet_resource_migration?(nil, _), do: false
|
||||
|
||||
defp needs_internet_resource_migration?(internet_resource, internet_gateway_group) do
|
||||
# can only be in the internet site now
|
||||
length(internet_resource.connections) > 1 ||
|
||||
Enum.all?(internet_resource.connections, fn connection ->
|
||||
connection.gateway_group_id != internet_gateway_group.id
|
||||
end)
|
||||
end
|
||||
|
||||
defp migrate_internet_resource(internet_resource, internet_gateway_group, subject) do
|
||||
attrs = %{
|
||||
connections: %{
|
||||
internet_gateway_group.id => %{
|
||||
gateway_group_id: internet_gateway_group.id,
|
||||
resource_id: internet_resource.id,
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Domain.Repo.transaction(fn ->
|
||||
with {:ok, _count} <- Domain.Resources.delete_connections_for(internet_resource, subject),
|
||||
{:updated, resource} <-
|
||||
Domain.Resources.update_resource(internet_resource, attrs, subject) do
|
||||
resource
|
||||
else
|
||||
{:error, changeset} ->
|
||||
Logger.error("Failed to migrate Internet Resource",
|
||||
reason: inspect(changeset),
|
||||
account: internet_resource.account_id
|
||||
)
|
||||
|
||||
Domain.Repo.rollback(changeset)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -163,7 +163,13 @@ defmodule Web.Live.Policies.NewTest do
|
||||
identity: identity,
|
||||
conn: conn
|
||||
} do
|
||||
resource = Fixtures.Resources.create_resource(account: account, type: :internet)
|
||||
internet_gateway_group = Fixtures.Gateways.create_internet_group(account: account)
|
||||
|
||||
resource =
|
||||
Fixtures.Resources.create_internet_resource(
|
||||
account: account,
|
||||
connections: [%{gateway_group_id: internet_gateway_group.id}]
|
||||
)
|
||||
|
||||
{:ok, lv, _html} =
|
||||
conn
|
||||
|
||||
Reference in New Issue
Block a user