mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
fix(portal): use public key for TURN creds (#9870)
As a followup to #9856, after talking with @bmanifold, we determined using the public_key as the username for TURN credentials is a safer bet because: - It's by definition public and therefore does not need to be obfuscated - It's shorter-lived than the token, especially for the gateway - It essentially represents the data plane connection for client/gateway and naturally rotates along with the key state for those
This commit is contained in:
@@ -110,7 +110,7 @@ defmodule API.Client.Channel do
|
||||
relays:
|
||||
Views.Relay.render_many(
|
||||
relays,
|
||||
socket.assigns.turn_salt,
|
||||
socket.assigns.client.public_key,
|
||||
socket.assigns.subject.expires_at
|
||||
),
|
||||
interface:
|
||||
@@ -447,7 +447,7 @@ defmodule API.Client.Channel do
|
||||
connected:
|
||||
Views.Relay.render_many(
|
||||
relays,
|
||||
socket.assigns.turn_salt,
|
||||
socket.assigns.client.public_key,
|
||||
socket.assigns.subject.expires_at
|
||||
)
|
||||
}
|
||||
@@ -505,7 +505,7 @@ defmodule API.Client.Channel do
|
||||
connected:
|
||||
Views.Relay.render_many(
|
||||
relays,
|
||||
socket.assigns.turn_salt,
|
||||
socket.assigns.client.public_key,
|
||||
socket.assigns.subject.expires_at
|
||||
)
|
||||
})
|
||||
|
||||
@@ -28,14 +28,8 @@ defmodule API.Client.Socket do
|
||||
account_id: subject.account.id
|
||||
})
|
||||
|
||||
# For Relay credentials
|
||||
turn_salt =
|
||||
Domain.Crypto.hash(:sha256, token)
|
||||
|> Base.url_encode64(padding: false)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:turn_salt, turn_salt)
|
||||
|> assign(:subject, subject)
|
||||
|> assign(:client, client)
|
||||
|> assign(:opentelemetry_span_ctx, OpenTelemetry.Tracer.current_span_ctx())
|
||||
|
||||
@@ -50,7 +50,11 @@ defmodule API.Gateway.Channel do
|
||||
account_slug: account.slug,
|
||||
interface: Views.Interface.render(socket.assigns.gateway),
|
||||
relays:
|
||||
Views.Relay.render_many(relays, socket.assigns.turn_salt, relay_credentials_expire_at),
|
||||
Views.Relay.render_many(
|
||||
relays,
|
||||
socket.assigns.gateway.public_key,
|
||||
relay_credentials_expire_at
|
||||
),
|
||||
# These aren't used but needed for API compatibility
|
||||
config: %{
|
||||
ipv4_masquerade_enabled: true,
|
||||
@@ -183,7 +187,7 @@ defmodule API.Gateway.Channel do
|
||||
connected:
|
||||
Views.Relay.render_many(
|
||||
relays,
|
||||
socket.assigns.turn_salt,
|
||||
socket.assigns.gateway.public_key,
|
||||
relay_credentials_expire_at
|
||||
)
|
||||
}
|
||||
@@ -244,7 +248,7 @@ defmodule API.Gateway.Channel do
|
||||
connected:
|
||||
Views.Relay.render_many(
|
||||
relays,
|
||||
socket.assigns.turn_salt,
|
||||
socket.assigns.gateway.public_key,
|
||||
relay_credentials_expire_at
|
||||
)
|
||||
})
|
||||
|
||||
@@ -27,14 +27,8 @@ defmodule API.Gateway.Socket do
|
||||
version: gateway.last_seen_version
|
||||
})
|
||||
|
||||
# For Relay credentials
|
||||
turn_salt =
|
||||
Domain.Crypto.hash(:sha256, encoded_token)
|
||||
|> Base.url_encode64(padding: false)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:turn_salt, turn_salt)
|
||||
|> assign(:token_id, token.id)
|
||||
|> assign(:gateway_group, group)
|
||||
|> assign(:gateway, gateway)
|
||||
|
||||
@@ -137,8 +137,7 @@ defmodule API.Client.ChannelTest do
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
client: client,
|
||||
subject: subject,
|
||||
turn_salt: "test_salt"
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Client.Channel, "client")
|
||||
|
||||
@@ -192,8 +191,7 @@ defmodule API.Client.ChannelTest do
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
client: client,
|
||||
subject: subject,
|
||||
turn_salt: "test_salt"
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Client.Channel, "client")
|
||||
|
||||
@@ -215,8 +213,7 @@ defmodule API.Client.ChannelTest do
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
client: client,
|
||||
subject: subject,
|
||||
turn_salt: "test_salt"
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Client.Channel, "client")
|
||||
|
||||
@@ -234,8 +231,7 @@ defmodule API.Client.ChannelTest do
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
client: client,
|
||||
subject: subject,
|
||||
turn_salt: "test_salt"
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Client.Channel, "client")
|
||||
|
||||
@@ -249,8 +245,7 @@ defmodule API.Client.ChannelTest do
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
client: client,
|
||||
subject: subject,
|
||||
turn_salt: "test_salt"
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Client.Channel, "client")
|
||||
|
||||
@@ -263,8 +258,7 @@ defmodule API.Client.ChannelTest do
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
client: client,
|
||||
subject: subject,
|
||||
turn_salt: "test_salt"
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Client.Channel, "client") ==
|
||||
{:error, %{reason: :invalid_version}}
|
||||
@@ -403,8 +397,7 @@ defmodule API.Client.ChannelTest do
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
client: client,
|
||||
subject: subject,
|
||||
turn_salt: "test_salt"
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Client.Channel, "client")
|
||||
|
||||
@@ -477,8 +470,7 @@ defmodule API.Client.ChannelTest do
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
client: client,
|
||||
subject: subject,
|
||||
turn_salt: "test_salt"
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Client.Channel, "client")
|
||||
|
||||
@@ -565,8 +557,7 @@ defmodule API.Client.ChannelTest do
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
client: client,
|
||||
subject: subject,
|
||||
turn_salt: "test_salt"
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Client.Channel, "client")
|
||||
|
||||
@@ -618,8 +609,7 @@ defmodule API.Client.ChannelTest do
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
client: client,
|
||||
subject: subject,
|
||||
turn_salt: "test_salt"
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Client.Channel, "client")
|
||||
|
||||
@@ -683,8 +673,7 @@ defmodule API.Client.ChannelTest do
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
client: client,
|
||||
subject: subject,
|
||||
turn_salt: "test_salt"
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Client.Channel, "client")
|
||||
|
||||
@@ -1446,8 +1435,7 @@ defmodule API.Client.ChannelTest do
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
client: client,
|
||||
subject: subject,
|
||||
turn_salt: "test_salt"
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Client.Channel, "client")
|
||||
|
||||
@@ -1521,8 +1509,7 @@ defmodule API.Client.ChannelTest do
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
client: client,
|
||||
subject: subject,
|
||||
turn_salt: "test_salt"
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Client.Channel, "client")
|
||||
|
||||
@@ -1875,8 +1862,7 @@ defmodule API.Client.ChannelTest do
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
client: client,
|
||||
subject: subject,
|
||||
turn_salt: "test_salt"
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Client.Channel, "client")
|
||||
|
||||
@@ -1945,8 +1931,7 @@ defmodule API.Client.ChannelTest do
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
client: client,
|
||||
subject: subject,
|
||||
turn_salt: "test_salt"
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Client.Channel, "client")
|
||||
|
||||
@@ -2157,8 +2142,7 @@ defmodule API.Client.ChannelTest do
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
client: client,
|
||||
subject: subject,
|
||||
turn_salt: "test_salt"
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Client.Channel, "client")
|
||||
|
||||
@@ -2369,8 +2353,7 @@ defmodule API.Client.ChannelTest do
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
client: client,
|
||||
subject: subject,
|
||||
turn_salt: "test_salt"
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Client.Channel, "client")
|
||||
|
||||
|
||||
@@ -23,8 +23,7 @@ defmodule API.Gateway.ChannelTest do
|
||||
gateway: gateway,
|
||||
gateway_group: gateway_group,
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
turn_salt: "test_salt"
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test")
|
||||
})
|
||||
|> subscribe_and_join(API.Gateway.Channel, "gateway")
|
||||
|
||||
@@ -342,8 +341,7 @@ defmodule API.Gateway.ChannelTest do
|
||||
gateway: gateway,
|
||||
gateway_group: gateway_group,
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
turn_salt: "test_salt"
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test")
|
||||
})
|
||||
|> subscribe_and_join(API.Gateway.Channel, "gateway")
|
||||
|
||||
@@ -395,8 +393,7 @@ defmodule API.Gateway.ChannelTest do
|
||||
gateway: gateway,
|
||||
gateway_group: gateway_group,
|
||||
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
|
||||
turn_salt: "test_salt"
|
||||
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test")
|
||||
})
|
||||
|> subscribe_and_join(API.Gateway.Channel, "gateway")
|
||||
|
||||
|
||||
@@ -272,15 +272,17 @@ defmodule Domain.Relays do
|
||||
|
||||
# TODO: Relays
|
||||
# Revisit credential lifetime when https://github.com/firezone/firezone/issues/8222 is implemented
|
||||
def generate_username_and_password(%Relay{stamp_secret: stamp_secret}, salt, expires_at)
|
||||
def generate_username_and_password(%Relay{stamp_secret: stamp_secret}, public_key, expires_at)
|
||||
when is_binary(stamp_secret) do
|
||||
salt = generate_hash(public_key)
|
||||
expires_at = DateTime.to_unix(expires_at, :second)
|
||||
password = generate_hash(expires_at, stamp_secret, salt)
|
||||
password = generate_hash("#{expires_at}:#{salt}:#{stamp_secret}")
|
||||
|
||||
%{username: "#{expires_at}:#{salt}", password: password, expires_at: expires_at}
|
||||
end
|
||||
|
||||
defp generate_hash(expires_at, stamp_secret, salt) do
|
||||
:crypto.hash(:sha256, "#{expires_at}:#{stamp_secret}:#{salt}")
|
||||
defp generate_hash(string) do
|
||||
:crypto.hash(:sha256, string)
|
||||
|> Base.encode64(padding: false)
|
||||
end
|
||||
|
||||
|
||||
@@ -790,19 +790,21 @@ defmodule Domain.RelaysTest do
|
||||
test "returns username and password", %{account: account} do
|
||||
relay = Fixtures.Relays.create_relay(account: account)
|
||||
stamp_secret = "test_secret"
|
||||
turn_salt = "test_salt"
|
||||
public_key = "test_public_key"
|
||||
relay = %{relay | stamp_secret: stamp_secret}
|
||||
{:ok, expires_at, _} = DateTime.from_iso8601("2023-10-01T00:00:00Z")
|
||||
|
||||
assert %{username: username, password: password, expires_at: expires_at_unix} =
|
||||
generate_username_and_password(relay, turn_salt, expires_at)
|
||||
generate_username_and_password(relay, public_key, expires_at)
|
||||
|
||||
assert [username_expires_at_unix, username_salt] =
|
||||
String.split(username, ":", parts: 2)
|
||||
|
||||
assert [username_expires_at_unix, username_salt] = String.split(username, ":", parts: 2)
|
||||
assert username_expires_at_unix == to_string(expires_at_unix)
|
||||
assert username_salt == turn_salt
|
||||
assert username_salt == "5d9CsB7vot2KRIXMGXivBcgmjnW0ClvN5q/DxOeFotA"
|
||||
assert DateTime.from_unix!(expires_at_unix) == DateTime.truncate(expires_at, :second)
|
||||
assert username == "1696118400:test_salt"
|
||||
assert password == "P0+gMB7RdvcvPv3eYFh1VSJUJh/FoAmOjUOqU8dToD8"
|
||||
assert username == "1696118400:5d9CsB7vot2KRIXMGXivBcgmjnW0ClvN5q/DxOeFotA"
|
||||
assert password == "GmFbvRR/LGes0VUmNhzwxG2K2Ww6Y0GTaLVS4S5QJOs"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user