Add peer before sending to user

closes #75
This commit is contained in:
Jamil Bou Kheir
2020-12-27 11:26:05 -06:00
parent 2e1afa6e79
commit c23c5bb93b
10 changed files with 107 additions and 84 deletions

View File

@@ -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

View File

@@ -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} ->

View File

@@ -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

View File

@@ -1,46 +1,45 @@
<div class="columns">
<div class="column is-6">
<h6 class="is-6 title">
Add the following to your WireGuard™ configuration file:
</h6>
<%= unless @device.public_key do %>
<h4 class="subtitle is-4">
Adding new peer to WireGuard server...
</h4>
<pre>
<code id="wg-conf">
[Peer]
PublicKey = <%= Application.fetch_env!(:fg_vpn, :pubkey) %>
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = <%= Application.fetch_env!(:fg_http, :vpn_endpoint) %>
</code>
</pre>
</div>
<div class="column is-6">
<progress class="progress is-small" max="100"></progress>
<p>
<%= link "Back to Devices", to: Routes.device_path(@socket, :index) %>
</p>
<% else %>
<div class="columns">
<div class="column is-6">
<h6 class="is-6 title">
Or scan the QR code with your mobile phone:
Add the following to your WireGuard™ configuration file:
</h6>
<canvas id="qr-canvas"></canvas>
</div>
</div>
<hr>
<pre>
<code id="wg-conf">
[Interface]
PrivateKey = <%= @device.private_key %>
<div phx-hook="QrEncode" class="columns">
<div class="column is-6">
<%= unless @device.public_key do %>
<h4 class="subtitle is-4">
Waiting for device connection...
</h4>
<progress class="progress is-small" max="100"></progress>
<h6 class="title is-6">
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) %>
</code>
</pre>
</div>
<div class="column is-6">
<h6 class="is-6 title">
Or scan the QR code with your mobile phone:
</h6>
<canvas id="qr-canvas"></canvas>
</div>
</div>
<p>
<%= link "Back to Devices", to: Routes.device_path(@socket, :index) %>
</p>
<% else %>
<h4 class="subtitle is-4">Device connected!</h4>
<hr>
<div phx-hook="QrEncode" class="columns">
<div class="column is-6">
<h4 class="subtitle is-4">Device added!</h4>
<dl class="content">
<dt>Device Public Key:</dt>
@@ -49,7 +48,6 @@ Endpoint = <%= Application.fetch_env!(:fg_http, :vpn_endpoint) %>
<dd><strong><%= @device.last_ip %></strong></dd>
</dl>
<%# XXX: Use the public key sent by the actual device and not the one here %>
<div class="level">
<div class="level-left">
<%=
@@ -65,6 +63,6 @@ Endpoint = <%= Application.fetch_env!(:fg_http, :vpn_endpoint) %>
<% end %>
</div>
</div>
<% end %>
</div>
</div>
</div>
<% end %>

View File

@@ -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&apos;ll prompt"
assert render(view) =~ "When we receive a connection from your device, we&apos;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

View File

@@ -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

View File

@@ -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)

View File

@@ -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")

View File

@@ -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,

View File

@@ -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