mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-28 02:18:50 +00:00
522/allow disabling of config creation (#559)
* Checkpoint * Optionally hide device mgmt buttons
This commit is contained in:
@@ -18,7 +18,7 @@ defmodule FzHttp.Devices.Device do
|
||||
|
||||
import FzHttp.Queries.INET
|
||||
|
||||
alias FzHttp.{Devices, Users.User}
|
||||
alias FzHttp.{Devices, Users, Users.User}
|
||||
|
||||
@description_max_length 2048
|
||||
|
||||
@@ -58,6 +58,7 @@ defmodule FzHttp.Devices.Device do
|
||||
|> put_next_ip(:ipv6)
|
||||
|> shared_changeset()
|
||||
|> validate_max_devices()
|
||||
|> validate_device_management()
|
||||
end
|
||||
|
||||
def update_changeset(device, attrs) do
|
||||
@@ -154,6 +155,21 @@ defmodule FzHttp.Devices.Device do
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_device_management(changeset) do
|
||||
user_id = changeset.changes.user_id || changeset.data.user_id
|
||||
|
||||
if Users.get_user!(user_id).role == :admin ||
|
||||
Application.fetch_env!(:fz_http, :allow_unprivileged_device_management) do
|
||||
changeset
|
||||
else
|
||||
add_error(
|
||||
changeset,
|
||||
:base,
|
||||
"Must be an administrator to manage devices."
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_omitted_if_site(changeset, fields) when is_list(fields) do
|
||||
validate_omitted(changeset, filter_site_fields(changeset, fields, use_site: true))
|
||||
end
|
||||
|
||||
@@ -60,11 +60,13 @@
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
<%= live_patch(to: Routes.device_unprivileged_index_path(@socket, :new), class: "button") do %>
|
||||
Add Device
|
||||
<% end %>
|
||||
</div>
|
||||
<%= if FzHttpWeb.DeviceView.can_manage_devices?(@current_user) do %>
|
||||
<div class="block">
|
||||
<%= live_patch(to: Routes.device_unprivileged_index_path(@socket, :new), class: "button") do %>
|
||||
Add Device
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<h4 class="title is-4">VPN Session</h4>
|
||||
|
||||
|
||||
@@ -43,7 +43,9 @@ defmodule FzHttpWeb.DeviceLive.Unprivileged.Show do
|
||||
end
|
||||
|
||||
def delete_device(device, socket) do
|
||||
if socket.assigns.current_user.id == device.user_id do
|
||||
if socket.assigns.current_user.id == device.user_id &&
|
||||
(has_role?(socket.assigns.current_user, :admin) ||
|
||||
Application.fetch_env!(:fz_http, :allow_unprivileged_device_management)) do
|
||||
Devices.delete_device(device)
|
||||
else
|
||||
{:not_authorized}
|
||||
|
||||
@@ -6,18 +6,20 @@
|
||||
<%= render(FzHttpWeb.SharedView, "device_details.html", assigns) %>
|
||||
</section>
|
||||
|
||||
<section class="section is-main-section">
|
||||
<h4 class="title is-4">
|
||||
Danger Zone
|
||||
</h4>
|
||||
<%= if FzHttpWeb.DeviceView.can_manage_devices?(@current_user) do %>
|
||||
<section class="section is-main-section">
|
||||
<h4 class="title is-4">
|
||||
Danger Zone
|
||||
</h4>
|
||||
|
||||
<button class="button is-danger"
|
||||
phx-click="delete_device"
|
||||
phx-value-device_id={@device.id}
|
||||
data-confirm="Are you sure? This will immediately disconnect this device and remove all associated data.">
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-trash"></i>
|
||||
</span>
|
||||
<span>Delete Device <%= @device.name %></span>
|
||||
</button>
|
||||
</section>
|
||||
<button class="button is-danger"
|
||||
phx-click="delete_device"
|
||||
phx-value-device_id={@device.id}
|
||||
data-confirm="Are you sure? This will immediately disconnect this device and remove all associated data.">
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-trash"></i>
|
||||
</span>
|
||||
<span>Delete Device <%= @device.name %></span>
|
||||
</button>
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
defmodule FzHttpWeb.DeviceView do
|
||||
use FzHttpWeb, :view
|
||||
|
||||
def can_manage_devices?(user) do
|
||||
has_role?(user, :admin) ||
|
||||
Application.fetch_env!(:fz_http, :allow_unprivileged_device_management)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
defmodule FzHttpWeb.DeviceLive.Unprivileged.IndexTest do
|
||||
use FzHttpWeb.ConnCase, async: false
|
||||
|
||||
# alias FzHttp.{Devices, Devices.Device}
|
||||
|
||||
describe "authenticated/device list" do
|
||||
setup :create_devices
|
||||
test "includes the device name in the list", %{
|
||||
unprivileged_user: user,
|
||||
unprivileged_conn: conn
|
||||
} do
|
||||
{:ok, devices: devices} = create_devices(user_id: user.id)
|
||||
|
||||
test "includes the device name in the list", %{admin_conn: conn, devices: devices} do
|
||||
path = Routes.device_admin_index_path(conn, :index)
|
||||
path = Routes.device_unprivileged_index_path(conn, :index)
|
||||
{:ok, _view, html} = live(conn, path)
|
||||
|
||||
for device <- devices do
|
||||
@@ -25,6 +26,32 @@ defmodule FzHttpWeb.DeviceLive.Unprivileged.IndexTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "authenticated device management disabled" do
|
||||
setup do
|
||||
restore_env(:allow_unprivileged_device_management, false, &on_exit/1)
|
||||
end
|
||||
|
||||
test "omits Add Device button", %{unprivileged_conn: conn} do
|
||||
path = Routes.device_unprivileged_index_path(conn, :index)
|
||||
{:ok, _view, html} = live(conn, path)
|
||||
|
||||
refute html =~ "Add Device"
|
||||
end
|
||||
|
||||
test "prevents creating a device", %{unprivileged_conn: conn} do
|
||||
path = Routes.device_unprivileged_index_path(conn, :new)
|
||||
{:ok, view, _html} = live(conn, path)
|
||||
|
||||
new_view =
|
||||
view
|
||||
|> element("#create-device")
|
||||
|> render_submit(%{"device" => %{"public_key" => "test-pubkey", "name" => "test-tunnel"}})
|
||||
|
||||
assert new_view =~ "Must be an administrator to manage devices."
|
||||
refute new_view =~ "Device added!"
|
||||
end
|
||||
end
|
||||
|
||||
describe "authenticated/creates device" do
|
||||
test "shows new form", %{unprivileged_conn: conn} do
|
||||
path = Routes.device_unprivileged_index_path(conn, :index)
|
||||
@@ -43,7 +70,7 @@ defmodule FzHttpWeb.DeviceLive.Unprivileged.IndexTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "creates tunnel", %{unprivileged_conn: conn} do
|
||||
test "creates device", %{unprivileged_conn: conn} do
|
||||
path = Routes.device_unprivileged_index_path(conn, :new)
|
||||
{:ok, view, _html} = live(conn, path)
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
defmodule FzHttpWeb.DeviceLive.Unprivileged.ShowTest do
|
||||
use FzHttpWeb.ConnCase, async: true
|
||||
|
||||
describe "unauthenticated" do
|
||||
setup :create_device
|
||||
|
||||
@tag :unauthed
|
||||
test "mount redirects to session path", %{unauthed_conn: conn, device: device} do
|
||||
path = Routes.device_admin_show_path(conn, :show, device)
|
||||
expected_path = Routes.root_path(conn, :index)
|
||||
assert {:error, {:redirect, %{to: ^expected_path}}} = live(conn, path)
|
||||
end
|
||||
end
|
||||
|
||||
describe "authenticated; device management disabled" do
|
||||
test "prevents deleting a device; doesn't show button", %{
|
||||
unprivileged_user: user,
|
||||
unprivileged_conn: conn
|
||||
} do
|
||||
{:ok, device: device} = create_device(user_id: user.id)
|
||||
restore_env(:allow_unprivileged_device_management, false, &on_exit/1)
|
||||
|
||||
path = Routes.device_unprivileged_show_path(conn, :show, device)
|
||||
{:ok, _view, html} = live(conn, path)
|
||||
|
||||
refute html =~ "Delete Device"
|
||||
end
|
||||
end
|
||||
|
||||
# XXX: Revisit this when RBAC is more fleshed out. Admins can now view other admins' devices.
|
||||
# describe "authenticated as other user" do
|
||||
# setup [:create_device, :create_other_user_device]
|
||||
#
|
||||
# test "mount redirects to session path", %{
|
||||
# admin_conn: conn,
|
||||
# device: _device,
|
||||
# other_device: other_device
|
||||
# } do
|
||||
# path = Routes.device_admin_show_path(conn, :show, other_device)
|
||||
# expected_path = Routes.auth_path(conn, :request)
|
||||
# assert {:error, {:redirect, %{to: ^expected_path}}} = live(conn, path)
|
||||
# end
|
||||
# end
|
||||
end
|
||||
@@ -76,8 +76,14 @@ defmodule FzHttp.TestHelpers do
|
||||
{:ok, devices: devices}
|
||||
end
|
||||
|
||||
def create_user(_) do
|
||||
user = UsersFixtures.user()
|
||||
def create_user(tags) do
|
||||
user =
|
||||
if tags[:unprivileged] do
|
||||
UsersFixtures.user(%{role: :unprivileged})
|
||||
else
|
||||
UsersFixtures.user()
|
||||
end
|
||||
|
||||
{:ok, user: user}
|
||||
end
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ config :fz_http, FzHttpWeb.Authentication,
|
||||
|
||||
config :fz_http,
|
||||
sandbox: true,
|
||||
allow_unprivileged_device_management: true,
|
||||
telemetry_id: "543aae08-5a2b-428d-b704-2956dd3f5a57",
|
||||
wireguard_endpoint: nil,
|
||||
wireguard_dns: "1.1.1.1, 1.0.0.1",
|
||||
|
||||
@@ -40,6 +40,9 @@ telemetry_id = System.fetch_env!("TELEMETRY_ID")
|
||||
guardian_secret_key = System.fetch_env!("GUARDIAN_SECRET_KEY")
|
||||
external_url = System.fetch_env!("EXTERNAL_URL")
|
||||
|
||||
allow_unprivileged_device_management =
|
||||
FzString.to_boolean(System.fetch_env!("ALLOW_UNPRIVILEGED_DEVICE_MANAGEMENT"))
|
||||
|
||||
# Local auth
|
||||
local_auth_enabled = FzString.to_boolean(System.fetch_env!("LOCAL_AUTH_ENABLED"))
|
||||
|
||||
@@ -154,6 +157,7 @@ config :fz_http, FzHttpWeb.Authentication,
|
||||
secret_key: guardian_secret_key
|
||||
|
||||
config :fz_http,
|
||||
allow_unprivileged_device_management: allow_unprivileged_device_management,
|
||||
max_devices_per_user: max_devices_per_user,
|
||||
local_auth_enabled: local_auth_enabled,
|
||||
okta_auth_enabled: okta_auth_enabled,
|
||||
|
||||
@@ -26,6 +26,8 @@ Shown below is a complete listing of the configuration options available in
|
||||
| `default['firezone']['user']` | Name of unprivileged Linux user most services and files will belong to. | `'firezone'` |
|
||||
| `default['firezone']['group']` | Name of Linux group most services and files will belong to. | `'firezone'` |
|
||||
| `default['firezone']['admin_email']` | Email address for initial Firezone user. | `"firezone@localhost"` |
|
||||
| `default['firezone']['max_devices_per_user']` | Maximum number of devices a user can have. | `10` |
|
||||
| `default['firezone']['allow_unprivileged_device_management']` | Allows non-admin users to create and manage devices. | `true` |
|
||||
| `default['firezone']['egress_interface']` | Interface name where tunneled traffic will exit. If nil, the default route interface will be used. | `nil` |
|
||||
| `default['firezone']['fips_enabled']` | Enable or disable OpenSSL FIPs mode. | `nil` |
|
||||
| `default['firezone']['logging']['enabled']` | Enable or disable logging across Firezone. Set to `false` to disable logging entirely. | `true` |
|
||||
@@ -143,7 +145,6 @@ Shown below is a complete listing of the configuration options available in
|
||||
| `default['firezone']['wireguard']['ipv6']['enabled']` | Enable or disable IPv6 for WireGuard network. | `true` |
|
||||
| `default['firezone']['wireguard']['ipv6']['network']` | WireGuard network IPv6 address pool. | `'fd00::3:2:0/120'` |
|
||||
| `default['firezone']['wireguard']['ipv6']['address']` | WireGuard interface IPv6 address. Must be within IPv6 address pool. | `'fd00::3:2:1'` |
|
||||
| `default['firezone']['wireguard']['max_devices_per_user']` | Maximum number of devices a user can have. | `10` |
|
||||
| `default['firezone']['runit']['svlogd_bin']` | Runit svlogd bin location. | `"#{node['firezone']['install_directory']}/embedded/bin/svlogd"` |
|
||||
| `default['firezone']['ssl']['directory']` | SSL directory for storing generated certs. | `'/var/opt/firezone/ssl'` |
|
||||
| `default['firezone']['ssl']['enabled']` | Enable or disable SSL for nginx. | `true` |
|
||||
|
||||
@@ -41,6 +41,10 @@ default['firezone']['admin_email'] = 'firezone@localhost'
|
||||
# Default: 10
|
||||
default['firezone']['max_devices_per_user'] = 10
|
||||
|
||||
# Allow users to create (and download) their own devices. Set to false
|
||||
# if you only want administrators to create and manage devices.
|
||||
default['firezone']['allow_unprivileged_device_management'] = true
|
||||
|
||||
default['firezone']['config_directory'] = '/etc/firezone'
|
||||
default['firezone']['install_directory'] = '/opt/firezone'
|
||||
default['firezone']['app_directory'] = "#{node['firezone']['install_directory']}/embedded/service/firezone"
|
||||
|
||||
@@ -243,6 +243,7 @@ class Firezone
|
||||
'WIREGUARD_IPV6_NETWORK' => attributes['wireguard']['ipv6']['network'],
|
||||
'WIREGUARD_IPV6_ADDRESS' => attributes['wireguard']['ipv6']['address'],
|
||||
'MAX_DEVICES_PER_USER' => attributes['max_devices_per_user'].to_s,
|
||||
'ALLOW_UNPRIVILEGED_DEVICE_MANAGEMENT' => attributes['allow_unprivileged_device_management'].to_s,
|
||||
# Allow env var to override config
|
||||
'TELEMETRY_ENABLED' => ENV.fetch('TELEMETRY_ENABLED',
|
||||
attributes['telemetry']['enabled'] == false ? 'false' : 'true'),
|
||||
|
||||
Reference in New Issue
Block a user