chore(portal): version gate resource can change sites (#10608)

On clients prior to https://github.com/firezone/firezone/pull/10604,
sending `resource_created_or_updated` for a site change would cause a
panic. With the introduction of that PR, the portal can now send this
message without needing to "toggle" the resource first by sending
`resource_deleted`.

Fixes: #10593
This commit is contained in:
Jamil
2025-10-26 17:02:02 -07:00
committed by GitHub
parent 37aad65f28
commit 1e295742bc
4 changed files with 143 additions and 15 deletions

View File

@@ -51,7 +51,7 @@ defmodule Domain.Cache.Client do
"""
alias Domain.{Actors, Auth, Clients, Cache, Gateways, Resources, Policies}
alias Domain.{Actors, Auth, Clients, Cache, Gateways, Resources, Policies, Version}
require Logger
require OpenTelemetry.Tracer
import Ecto.UUID, only: [dump!: 1, load!: 1]
@@ -159,8 +159,8 @@ defmodule Domain.Cache.Client do
added_ids = Enum.map(added, & &1.id)
# connlib can handle all resource attribute changes except for changing sites, so we can omit the deleted IDs
# of added resources since they'll be updated gracefully.
# connlib can handle all resource attribute changes except for changing sites (on older clients),
# so we can omit the deleted IDs of added resources since they'll be updated gracefully.
removed_ids =
for r <- cache.connectable_resources -- connectable_resources,
toggle or r.id not in added_ids do
@@ -273,9 +273,11 @@ defmodule Domain.Cache.Client do
cache = %{cache | resources: resources}
toggle = Version.resource_cannot_change_sites_on_client?(client)
# For these updates we need to make sure the resource is toggled deleted then created.
# See https://github.com/firezone/firezone/issues/9881
recompute_connectable_resources(cache, client, toggle: true)
recompute_connectable_resources(cache, client, toggle: toggle)
end
@doc """

View File

@@ -58,6 +58,16 @@ defmodule Domain.ComponentVersions do
end
end
def get_component_type(%Client{last_seen_user_agent: "Mac OS" <> _rest}), do: :apple
def get_component_type(%Client{last_seen_user_agent: "iOS" <> _rest}), do: :apple
def get_component_type(%Client{last_seen_user_agent: "Android" <> _rest}),
do: :android
def get_component_type(%Client{actor: %Actor{type: :service_account}}), do: :headless
def get_component_type(_), do: :gui
defp fetch_config! do
Domain.Config.fetch_env!(:domain, __MODULE__)
end
@@ -98,14 +108,4 @@ defmodule Domain.ComponentVersions do
|> Keyword.fetch!(:versions)
end
end
defp get_component_type(%Client{last_seen_user_agent: "Mac OS" <> _rest}), do: :apple
defp get_component_type(%Client{last_seen_user_agent: "iOS" <> _rest}), do: :apple
defp get_component_type(%Client{last_seen_user_agent: "Android" <> _rest}),
do: :android
defp get_component_type(%Client{actor: %Actor{type: :service_account}}), do: :headless
defp get_component_type(_), do: :gui
end

View File

@@ -1,4 +1,9 @@
defmodule Domain.Version do
alias Domain.{
Clients.Client,
ComponentVersions
}
def fetch_version(user_agent) when is_binary(user_agent) do
user_agent
|> String.split(" ")
@@ -21,4 +26,15 @@ defmodule Domain.Version do
def fetch_gateway_version(_user_agent) do
{:error, :invalid_user_agent}
end
# TODO: Remove once all clients are on versions that support resources changing sites.
# Connlib didn't support resources changing sites until https://github.com/firezone/firezone/pull/10604
def resource_cannot_change_sites_on_client?(%Client{last_seen_version: version} = client) do
case ComponentVersions.get_component_type(client) do
:apple -> Version.compare(version, "1.5.9") == :lt
:android -> Version.compare(version, "1.5.5") == :lt
:headless -> Version.compare(version, "1.5.5") == :lt
:gui -> Version.compare(version, "1.5.9") == :lt
end
end
end

View File

@@ -1,7 +1,7 @@
defmodule Domain.VersionTest do
use ExUnit.Case, async: true
describe "fetch_version" do
describe "fetch_version/1" do
test "can decode linux headless-client version" do
assert Domain.Version.fetch_version("Fedora/42.0.0 headless-client/1.4.5 (arm64; 24.1.0)") ==
{:ok, "1.4.5"}
@@ -39,4 +39,114 @@ defmodule Domain.VersionTest do
{:ok, "1.4.5"}
end
end
describe "resource_cannot_change_sites_on_client?/1" do
test "apple client below version cannot change sites" do
client = %Domain.Clients.Client{
last_seen_version: "1.5.7",
last_seen_user_agent: "Mac OS X"
}
assert Domain.Version.resource_cannot_change_sites_on_client?(client)
end
test "apple client at version cannot change sites" do
client = %Domain.Clients.Client{
last_seen_version: "1.5.8",
last_seen_user_agent: "Mac OS X"
}
assert Domain.Version.resource_cannot_change_sites_on_client?(client)
end
test "apple client above version can change sites" do
client = %Domain.Clients.Client{
last_seen_version: "1.5.9",
last_seen_user_agent: "Mac OS X"
}
refute Domain.Version.resource_cannot_change_sites_on_client?(client)
end
test "android client below version cannot change sites" do
client = %Domain.Clients.Client{
last_seen_version: "1.5.3",
last_seen_user_agent: "Android"
}
assert Domain.Version.resource_cannot_change_sites_on_client?(client)
end
test "android client at version cannot change sites" do
client = %Domain.Clients.Client{
last_seen_version: "1.5.4",
last_seen_user_agent: "Android"
}
assert Domain.Version.resource_cannot_change_sites_on_client?(client)
end
test "android client above version can change sites" do
client = %Domain.Clients.Client{
last_seen_version: "1.5.5",
last_seen_user_agent: "Android"
}
refute Domain.Version.resource_cannot_change_sites_on_client?(client)
end
test "headless client below version cannot change sites" do
client = %Domain.Clients.Client{
last_seen_version: "1.5.3",
actor: %Domain.Actors.Actor{type: :service_account}
}
assert Domain.Version.resource_cannot_change_sites_on_client?(client)
end
test "headless client at version cannot change sites" do
client = %Domain.Clients.Client{
last_seen_version: "1.5.4",
actor: %Domain.Actors.Actor{type: :service_account}
}
assert Domain.Version.resource_cannot_change_sites_on_client?(client)
end
test "headless client above version can change sites" do
client = %Domain.Clients.Client{
last_seen_version: "1.5.5",
actor: %Domain.Actors.Actor{type: :service_account}
}
refute Domain.Version.resource_cannot_change_sites_on_client?(client)
end
test "gui client below version cannot change sites" do
client = %Domain.Clients.Client{
last_seen_version: "1.5.7",
last_seen_user_agent: "Windows"
}
assert Domain.Version.resource_cannot_change_sites_on_client?(client)
end
test "gui client at version cannot change sites" do
client = %Domain.Clients.Client{
last_seen_version: "1.5.8",
last_seen_user_agent: "Windows"
}
assert Domain.Version.resource_cannot_change_sites_on_client?(client)
end
test "gui client above version can change sites" do
client = %Domain.Clients.Client{
last_seen_version: "1.5.9",
last_seen_user_agent: "Windows"
}
refute Domain.Version.resource_cannot_change_sites_on_client?(client)
end
end
end