diff --git a/apps/fg_http/lib/fg_http/devices/device.ex b/apps/fg_http/lib/fg_http/devices/device.ex index b77e71f6e..2d810d2d4 100644 --- a/apps/fg_http/lib/fg_http/devices/device.ex +++ b/apps/fg_http/lib/fg_http/devices/device.ex @@ -11,6 +11,8 @@ defmodule FgHttp.Devices.Device do schema "devices" do field :name, :string field :public_key, :string + field :server_pubkey, :string, virtual: true + field :private_key, :string, virtual: true field :ifname, :string field :last_ip, EctoNetwork.INET field :last_seen_at, :utc_datetime_usec @@ -25,5 +27,6 @@ defmodule FgHttp.Devices.Device do device |> cast(attrs, [:last_ip, :ifname, :user_id, :name, :public_key]) |> validate_required([:user_id, :ifname, :name, :public_key]) + |> unique_constraint([:name]) end end diff --git a/apps/fg_http/lib/fg_http_web/controllers/device_controller.ex b/apps/fg_http/lib/fg_http_web/controllers/device_controller.ex index 27499b230..d45210d36 100644 --- a/apps/fg_http/lib/fg_http_web/controllers/device_controller.ex +++ b/apps/fg_http/lib/fg_http_web/controllers/device_controller.ex @@ -5,7 +5,6 @@ defmodule FgHttpWeb.DeviceController do use FgHttpWeb, :controller alias FgHttp.{Devices, Devices.Device} - alias Phoenix.PubSub plug FgHttpWeb.Plugs.SessionLoader @@ -20,22 +19,17 @@ defmodule FgHttpWeb.DeviceController do end def create(conn, %{"device" => %{"public_key" => _public_key} = device_params}) do + # XXX: Get device name from browser our_params = %{ "user_id" => conn.assigns.session.id, - "name" => "Default", - "ifname" => "wg0" + "ifname" => "wg0", + "name" => "Device #{DateTime.utc_now() |> DateTime.to_unix(:microsecond)}" } all_params = Map.merge(device_params, our_params) case Devices.create_device(all_params) do - {:ok, device} -> - PubSub.broadcast( - :fg_http_pub_sub, - "config", - {:verify_device, device.public_key} - ) - + {:ok, _device} -> redirect(conn, to: Routes.device_path(conn, :index)) {:error, %Ecto.Changeset{} = changeset} -> diff --git a/apps/fg_http/lib/fg_http_web/live/new_device_live.ex b/apps/fg_http/lib/fg_http_web/live/new_device_live.ex index 181e7f495..bfa4ae962 100644 --- a/apps/fg_http/lib/fg_http_web/live/new_device_live.ex +++ b/apps/fg_http/lib/fg_http_web/live/new_device_live.ex @@ -11,32 +11,47 @@ defmodule FgHttpWeb.NewDeviceLive do @doc """ Called when the view mounts. The for a device being added goes like follows: - 1. Present QR code to user - 2. User's device connects, :device_connected is received from the FgVpn application - 3. User confirms device, :verify_device message is broadcasted - 4. FgVpn receives :verify_device and adds the device to the config file + 1. :add_device is broadcasted + 2. FgVpn picks this up and creates a new peer, adds to config, broadcasts :peer_added + 3. :peer_added is handled here which confirms the details to the user + 4. User confirms device, clicks create + # XXX: Add ttl to device creation that removes stale devices """ @impl true def mount(_params, %{"user_id" => user_id}, socket) do if connected?(socket) do # Subscribe to :device_connected events PubSub.subscribe(:fg_http_pub_sub, "view") - # :timer.send_after(@mocked_timer, self(), {:pubkey, FgCrypto.rand_string()}) end + # Fire off event to generate private key, psk, and add device to config + PubSub.broadcast(:fg_http_pub_sub, "config", {:add_device, self()}) + device = %Device{user_id: user_id} {:ok, assign(socket, :device, device)} end @doc """ - Handles device connect. + Handles device added. """ @impl true - def handle_info({:device_connected, pubkey}, socket) do - device = %{socket.assigns.device | public_key: pubkey, last_ip: "127.0.0.1"} + def handle_info({:peer_added, caller_pid, privkey, pubkey, server_pubkey}, socket) do + if caller_pid == self() do + device = %{ + socket.assigns.device + | public_key: pubkey, + private_key: privkey, + server_pubkey: server_pubkey, + last_ip: "127.0.0.1", + name: "Device #{pubkey}" + } - # Updates @device in the LiveView and causes re-render - {:noreply, assign(socket, :device, device)} + # Updates @device in the LiveView and causes re-render if the intended target is this pid + {:noreply, assign(socket, :device, device)} + else + # Noop, let the correct pid handle this broadcast + {:noreply, socket} + end end end diff --git a/apps/fg_http/lib/fg_http_web/live/new_device_live.html.leex b/apps/fg_http/lib/fg_http_web/live/new_device_live.html.leex index 19a201b34..2bad7fa08 100644 --- a/apps/fg_http/lib/fg_http_web/live/new_device_live.html.leex +++ b/apps/fg_http/lib/fg_http_web/live/new_device_live.html.leex @@ -1,46 +1,45 @@ -
-
-
- Add the following to your WireGuard™ configuration file: -
+<%= unless @device.public_key do %> +

+ Adding new peer to WireGuard server... +

-
-
-[Peer]
-PublicKey = <%= Application.fetch_env!(:fg_vpn, :pubkey) %>
-AllowedIPs = 0.0.0.0/0, ::/0
-Endpoint = <%= Application.fetch_env!(:fg_http, :vpn_endpoint) %>
-
-
-
-
+ + +

+ <%= link "Back to Devices", to: Routes.device_path(@socket, :index) %> +

+<% else %> +
+
- Or scan the QR code with your mobile phone: + Add the following to your WireGuard™ configuration file:
- -
-
-
+
+  
+  [Interface]
+  PrivateKey = <%= @device.private_key %>
 
-
-
- <%= unless @device.public_key do %> -

- Waiting for device connection... -

- - - -
- When we receive a connection from your device, we'll prompt you verify it here. + [Peer] + PublicKey = <%= @device.server_pubkey %> + AllowedIPs = 0.0.0.0/0, ::/0 + Endpoint = <%= Application.fetch_env!(:fg_http, :vpn_endpoint) %> + +
+
+
+
+ Or scan the QR code with your mobile phone:
+ +
+
-

- <%= link "Back to Devices", to: Routes.device_path(@socket, :index) %> -

- <% else %> -

Device connected!

+
+ +
+
+

Device added!

Device Public Key:
@@ -49,7 +48,6 @@ Endpoint = <%= Application.fetch_env!(:fg_http, :vpn_endpoint) %>
<%= @device.last_ip %>
- <%# XXX: Use the public key sent by the actual device and not the one here %>
<%= @@ -65,6 +63,6 @@ Endpoint = <%= Application.fetch_env!(:fg_http, :vpn_endpoint) %> <% end %>
- <% end %> +
- +<% end %> diff --git a/apps/fg_http/test/fg_http_web/live/new_device_live_test.exs b/apps/fg_http/test/fg_http_web/live/new_device_live_test.exs index 77d784186..249821baa 100644 --- a/apps/fg_http/test/fg_http_web/live/new_device_live_test.exs +++ b/apps/fg_http/test/fg_http_web/live/new_device_live_test.exs @@ -11,9 +11,11 @@ defmodule FgHttpWeb.NewDeviceLiveTest do test "mount and handle_info/2", %{authed_conn: conn} do {:ok, view, html} = live_isolated(conn, FgHttpWeb.NewDeviceLive) - assert html =~ "When we receive a connection from your device, we'll prompt" - assert render(view) =~ "When we receive a connection from your device, we'll prompt" - send(view.pid, {:device_connected, "test pubkey"}) - assert render(view) =~ "test pubkey" + assert html =~ "Adding new peer to WireGuard server..." + assert render(view) =~ "Adding new peer to WireGuard server..." + send(view.pid, {:peer_added, view.pid, "test-privkey", "test-pubkey", "server-pubkey"}) + assert render(view) =~ "test-pubkey" + assert render(view) =~ "server-pubkey" + assert render(view) =~ "test-privkey" end end diff --git a/apps/fg_vpn/lib/fg_vpn/config.ex b/apps/fg_vpn/lib/fg_vpn/config.ex index 10b1ce44a..061258c14 100644 --- a/apps/fg_vpn/lib/fg_vpn/config.ex +++ b/apps/fg_vpn/lib/fg_vpn/config.ex @@ -26,10 +26,19 @@ defmodule FgVpn.Config do end @impl true - def handle_info({:verify_device, pubkey}, config) do + def handle_info({:add_device, caller_pid}, config) do + {_server_privkey, server_pubkey} = read_privkey() + {privkey, pubkey} = CLI.genkey() new_peers = [pubkey | config[:peers]] new_config = %{config | peers: new_peers} write!(new_config) + + PubSub.broadcast( + :fg_http_pub_sub, + "view", + {:peer_added, caller_pid, privkey, pubkey, server_pubkey} + ) + {:noreply, new_config} end @@ -51,6 +60,8 @@ defmodule FgVpn.Config do @doc """ Reads PrivateKey from configuration file + + Returns a {privkey, pubkey} tuple """ def read_privkey do read_config_file() @@ -95,15 +106,16 @@ defmodule FgVpn.Config do @config_header <> interface_to_config(config) <> peers_to_config(config) end - defp interface_to_config(config) do - listen_port = - Application.get_env(:fg_http, :vpn_endpoint) - |> String.split(":") - |> List.last() + defp listen_port do + Application.get_env(:fg_http, :vpn_endpoint) + |> String.split(":") + |> List.last() + end + defp interface_to_config(config) do ~s""" [Interface] - ListenPort = #{listen_port} + ListenPort = #{listen_port()} PrivateKey = #{config[:privkey]} PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o #{ config[:default_int] @@ -140,8 +152,18 @@ defmodule FgVpn.Config do end) end + defp get_server_keys do + {privkey, pubkey} = read_privkey() + + if is_nil(privkey) do + CLI.genkey() + else + {privkey, pubkey} + end + end + defp read_and_rewrite_config do - {privkey, pubkey} = read_privkey() || CLI.genkey() + {privkey, _pubkey} = get_server_keys() config = %{ default_int: CLI.default_interface(), @@ -150,7 +172,6 @@ defmodule FgVpn.Config do } write!(config) - Application.put_env(:fg_vpn, :pubkey, pubkey) config end diff --git a/apps/fg_vpn/test/fg_vpn/config_test.exs b/apps/fg_vpn/test/fg_vpn/config_test.exs index 33d0e949a..7bbe11e85 100644 --- a/apps/fg_vpn/test/fg_vpn/config_test.exs +++ b/apps/fg_vpn/test/fg_vpn/config_test.exs @@ -78,13 +78,13 @@ defmodule FgVpn.ConfigTest do @tag stubbed_config: @empty test "writes peers to config when device is verified", %{test_pid: test_pid} do - send(test_pid, {:verify_device, "test-pubkey"}) + send(test_pid, {:add_device, test_pid}) # XXX: Avoid sleeping Process.sleep(100) assert %{ - peers: ["test-pubkey"], + peers: [_], default_int: _, privkey: _ } = :sys.get_state(test_pid) diff --git a/config/dev.exs b/config/dev.exs index 8ffec60c5..6ff225301 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -71,9 +71,6 @@ config :fg_http, FgHttpWeb.Endpoint, ] ] -config :fg_vpn, - pubkey: "JId8GN8iPmdQXOLSdcsSkaW4i60e1/rpHB/03rsaKBk=" - config :fg_vpn, wireguard_conf_path: Path.expand("~/.wg-fireguard.conf") diff --git a/config/releases.exs b/config/releases.exs index 4cf514e6a..f0652f726 100644 --- a/config/releases.exs +++ b/config/releases.exs @@ -59,9 +59,6 @@ wg_listen_port = System.get_env("WG_LISTEN_PORT" || "51820") wg_listen_address = System.get_env("WG_LISTEN_ADDRESS") || "localhost" url_host = System.get_env("URL_HOST") || "localhost" -# Will be replaced when FgVpn starts up and config is rewritten -config :fg_vpn, pubkey: nil - config :fg_http, disable_signup: disable_signup config :fg_http, FgHttp.Repo, diff --git a/config/test.exs b/config/test.exs index a03bd1024..c70031be7 100644 --- a/config/test.exs +++ b/config/test.exs @@ -38,9 +38,5 @@ config :fg_http, FgHttpWeb.Endpoint, ], server: false -config :fg_vpn, - privkey: "cAM9MY5NrQ067ZgOkE3NX3h7cMSOBRjj/w4acCuMknk=", - pubkey: "DcAqEvFtS0wuvrpvOYi0ncDTcZKpdFq7LKHQcMuAzSw=" - # Print only warnings and errors during test config :logger, level: :warn