mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
Re-add port-based rules and gate them behind kernel version (#890)
* Revert "Revert "Add initial rough version of port based rules (#874)" (#888)"
This reverts commit 58e48457ad.
* gate port rule depending on kernel version
* fix version comparision
* allow for no port-related values when creating rule event
* Fix struct accessor
* fix getting port type in rule list component
* small fix
* oops
* hide port-related display on disabled port-rules
* Gate table headers
* update port-based rule for boot-up only and update ui
* fix tests
* fix disable button
* Minor UI and wording update
* Add firewall functional tests
* fix functional testing
* add debug log for functional debugging
* fix
* Fix functional testing by preventing overlap
* remove sudo from functional firewall tests
* fix error message
* fix firewall ci
* re-adding sudo to functional test
* fix expected results in functional test
* Apply suggestions
* Update apps/fz_http/lib/fz_http_web/live/rule_live/rule_list_component.html.heex
Signed-off-by: Jamil <jamilbk@users.noreply.github.com>
Co-authored-by: Jamil <jamilbk@users.noreply.github.com>
This commit is contained in:
@@ -67,9 +67,10 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing FzVpn.Interface module works with WireGuard"
|
||||
fz_bin="/opt/firezone/embedded/service/firezone/bin/firezone"
|
||||
ok_res=":ok"
|
||||
|
||||
echo "Testing FzVpn.Interface module works with WireGuard"
|
||||
set_interface=`sudo $fz_bin rpc "IO.inspect(FzVpn.Interface.set(\"wg-fz-test\", %{}))"`
|
||||
del_interface=`sudo $fz_bin rpc "IO.inspect(FzVpn.Interface.delete(\"wg-fz-test\"))"`
|
||||
|
||||
@@ -77,3 +78,19 @@ if [[ "$set_interface" != $ok_res || "$del_interface" != $ok_res ]]; then
|
||||
echo "WireGuard test failed!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing Firewall Rules"
|
||||
user_id="5" # Picking a high enough user_id so there is no overlap
|
||||
device="%{ip: \"10.0.0.1\", ip6: \"fd00::3:2:1\", user_id: $user_id}"
|
||||
rule="%{destination: \"10.0.0.2\", user_id: $user_id, action: :drop, port_type: nil, port_range: nil}"
|
||||
add_user=`sudo $fz_bin rpc "IO.inspect(FzWall.CLI.Live.add_user($user_id))"`
|
||||
add_device=`sudo $fz_bin rpc "IO.inspect(FzWall.CLI.Live.add_device($device))"`
|
||||
add_rule=`sudo $fz_bin rpc "IO.inspect(FzWall.CLI.Live.add_rule($rule))"`
|
||||
del_rule=`sudo $fz_bin rpc "IO.inspect(FzWall.CLI.Live.delete_rule($rule))"`
|
||||
del_device=`sudo $fz_bin rpc "IO.inspect(FzWall.CLI.Live.delete_device($device))"`
|
||||
del_user=`sudo $fz_bin rpc "IO.inspect(FzWall.CLI.Live.delete_user($user_id))"`
|
||||
|
||||
if [[ "$add_user" != $ok_res || "$add_device" != $ok_res || "$add_rule" != '""' || "$del_rule" != '""' || "$del_device" != $ok_res || "$del_user" != $ok_res ]]; then
|
||||
echo "Firewall test failed!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
15
apps/fz_common/lib/fz_kernel_version.ex
Normal file
15
apps/fz_common/lib/fz_kernel_version.ex
Normal file
@@ -0,0 +1,15 @@
|
||||
defmodule FzCommon.FzKernelVersion do
|
||||
@moduledoc """
|
||||
Helpers related to kernel version
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Compares version tuple to current kernel version
|
||||
"""
|
||||
def is_version_greater_than?(val) do
|
||||
case :os.version() do
|
||||
v when is_tuple(v) -> v > val
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
end
|
||||
90
apps/fz_http/lib/fz_http/int4range.ex
Normal file
90
apps/fz_http/lib/fz_http/int4range.ex
Normal file
@@ -0,0 +1,90 @@
|
||||
defmodule FzHttp.Int4Range do
|
||||
@moduledoc """
|
||||
Ecto type for Postgres' Int4Range type
|
||||
"""
|
||||
# Note: we represent a port range as a string: lower - upper for ease of use
|
||||
# with Phoenix LiveView and nftables
|
||||
use Ecto.Type
|
||||
@format_error "Range Error: Bad format"
|
||||
|
||||
def type, do: :int4range
|
||||
|
||||
def cast(str) when is_binary(str) do
|
||||
# We need to handle this case since postgre notifies
|
||||
# before inserting the range in the database using this format
|
||||
parse_str =
|
||||
if String.starts_with?(str, ["[", "("]) do
|
||||
&parse_bracket/1
|
||||
else
|
||||
&parse_range/1
|
||||
end
|
||||
|
||||
case parse_str.(str) do
|
||||
{:ok, range} -> cast(range)
|
||||
err -> err
|
||||
end
|
||||
end
|
||||
|
||||
def cast([num, num]) when is_number(num) do
|
||||
{:ok, Integer.to_string(num)}
|
||||
end
|
||||
|
||||
def cast([lower, upper]) when upper >= lower, do: {:ok, "#{lower} - #{upper}"}
|
||||
def cast([_, _]), do: {:error, message: "Range Error: Lower bound higher than upper bound"}
|
||||
|
||||
def load(%Postgrex.Range{
|
||||
lower: lower,
|
||||
upper: upper,
|
||||
lower_inclusive: lower_inclusive,
|
||||
upper_inclusive: upper_inclusive
|
||||
}) do
|
||||
upper = if upper != :unbound, do: upper - to_num(!upper_inclusive), else: nil
|
||||
lower = if lower != :unbound, do: lower + to_num(!lower_inclusive), else: nil
|
||||
cast([lower, upper])
|
||||
end
|
||||
|
||||
def dump(range) when is_binary(range) do
|
||||
{:ok, range_list} = parse_range(range)
|
||||
dump(range_list)
|
||||
end
|
||||
|
||||
def dump([lower, upper]) do
|
||||
{:ok,
|
||||
%Postgrex.Range{lower: lower, upper: upper, upper_inclusive: true, lower_inclusive: true}}
|
||||
end
|
||||
|
||||
def dump(_), do: :error
|
||||
|
||||
defp parse_range(range) do
|
||||
res =
|
||||
String.trim(range)
|
||||
|> String.split("-", trim: true, parts: 2)
|
||||
|> Enum.map(&String.trim/1)
|
||||
|> Enum.map(&Integer.parse/1)
|
||||
|
||||
case res do
|
||||
[{lower, _}, {upper, _}] -> {:ok, [lower, upper]}
|
||||
[{num, _}] -> {:ok, [num, num]}
|
||||
_ -> {:error, message: @format_error}
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_bracket(bracket) do
|
||||
res =
|
||||
Regex.named_captures(
|
||||
~r/(?<start>[\[|(])\s*(?<lower>\d+),\s*(?<upper>\d+)\s*(?<end>[\]|\)])/,
|
||||
bracket
|
||||
)
|
||||
|
||||
if is_nil(res) || Enum.any?(["lower", "upper", "start", "end"], &is_nil(res[&1])) do
|
||||
{:error, message: @format_error}
|
||||
else
|
||||
lower = String.to_integer(res["lower"]) + to_num(res["start"] == "(")
|
||||
upper = String.to_integer(res["upper"]) - to_num(res["end"] == ")")
|
||||
{:ok, [lower, upper]}
|
||||
end
|
||||
end
|
||||
|
||||
defp to_num(b) when b, do: 1
|
||||
defp to_num(_b), do: 0
|
||||
end
|
||||
@@ -4,9 +4,19 @@ defmodule FzHttp.Rules do
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
|
||||
import Ecto.Changeset
|
||||
alias FzHttp.{Repo, Rules.Rule, Rules.RuleSetting, Telemetry}
|
||||
|
||||
def port_rules_supported?, do: Application.fetch_env!(:fz_wall, :port_based_rules_supported)
|
||||
|
||||
defp scope(port_based_rules) when port_based_rules == true do
|
||||
Rule
|
||||
end
|
||||
|
||||
defp scope(port_based_rules) when port_based_rules == false do
|
||||
from r in Rule, where: is_nil(r.port_type)
|
||||
end
|
||||
|
||||
def list_rules, do: Repo.all(Rule)
|
||||
|
||||
def list_rules(user_id) do
|
||||
@@ -17,7 +27,9 @@ defmodule FzHttp.Rules do
|
||||
end
|
||||
|
||||
def as_settings do
|
||||
Repo.all(from(Rule))
|
||||
port_rules_supported?()
|
||||
|> scope()
|
||||
|> Repo.all()
|
||||
|> Enum.map(&setting_projection/1)
|
||||
|> MapSet.new()
|
||||
end
|
||||
@@ -38,6 +50,14 @@ defmodule FzHttp.Rules do
|
||||
|> Rule.changeset(attrs)
|
||||
end
|
||||
|
||||
def defaults(changeset) do
|
||||
%{port_type: get_field(changeset, :port_type)}
|
||||
end
|
||||
|
||||
def defaults do
|
||||
defaults(new_rule())
|
||||
end
|
||||
|
||||
def create_rule(attrs \\ %{}) do
|
||||
result =
|
||||
attrs
|
||||
|
||||
@@ -7,11 +7,15 @@ defmodule FzHttp.Rules.Rule do
|
||||
import Ecto.Changeset
|
||||
|
||||
@exclusion_msg "Destination overlaps with an existing rule"
|
||||
@port_range_msg "Port is not within valid range"
|
||||
@port_type_msg "Please specify a port-range for the given port type"
|
||||
|
||||
schema "rules" do
|
||||
field :uuid, Ecto.UUID, autogenerate: true
|
||||
field :destination, EctoNetwork.INET, read_after_writes: true
|
||||
field :action, Ecto.Enum, values: [:drop, :accept], default: :drop
|
||||
field :port_type, Ecto.Enum, values: [:tcp, :udp], default: nil
|
||||
field :port_range, FzHttp.Int4Range, default: nil
|
||||
belongs_to :user, FzHttp.Users.User
|
||||
|
||||
timestamps(type: :utc_datetime_usec)
|
||||
@@ -22,9 +26,19 @@ defmodule FzHttp.Rules.Rule do
|
||||
|> cast(attrs, [
|
||||
:user_id,
|
||||
:action,
|
||||
:destination
|
||||
:destination,
|
||||
:port_type,
|
||||
:port_range
|
||||
])
|
||||
|> validate_required([:action, :destination])
|
||||
|> check_constraint(:port_range,
|
||||
message: @port_range_msg,
|
||||
name: :port_range_is_within_valid_values
|
||||
)
|
||||
|> check_constraint(:port_type,
|
||||
message: @port_type_msg,
|
||||
name: :port_range_needs_type
|
||||
)
|
||||
|> exclusion_constraint(:destination,
|
||||
message: @exclusion_msg,
|
||||
name: :destination_overlap_excl_usr_rule
|
||||
@@ -33,5 +47,13 @@ defmodule FzHttp.Rules.Rule do
|
||||
message: @exclusion_msg,
|
||||
name: :destination_overlap_excl
|
||||
)
|
||||
|> exclusion_constraint(:destination,
|
||||
message: @exclusion_msg,
|
||||
name: :destination_overlap_excl_port
|
||||
)
|
||||
|> exclusion_constraint(:destination,
|
||||
message: @exclusion_msg,
|
||||
name: :destination_overlap_excl_usr_rule_port
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,19 +12,23 @@ defmodule FzHttp.Rules.RuleSetting do
|
||||
field :action, Ecto.Enum, values: [:drop, :accept]
|
||||
field :destination, :string
|
||||
field :user_id, :integer
|
||||
field :port_type, Ecto.Enum, values: [:tcp, :udp], default: nil
|
||||
field :port_range, FzHttp.Int4Range, default: nil
|
||||
end
|
||||
|
||||
def parse(rule) when is_struct(rule) do
|
||||
%__MODULE__{
|
||||
destination: decode(rule.destination),
|
||||
action: rule.action,
|
||||
user_id: rule.user_id
|
||||
user_id: rule.user_id,
|
||||
port_type: rule.port_type,
|
||||
port_range: rule.port_range
|
||||
}
|
||||
end
|
||||
|
||||
def parse(rule) when is_map(rule) do
|
||||
%__MODULE__{}
|
||||
|> cast(rule, [:action, :destination, :user_id])
|
||||
|> cast(rule, [:action, :destination, :user_id, :port_type, :port_range])
|
||||
|> apply_changes()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,23 +12,42 @@ defmodule FzHttpWeb.RuleLive.RuleListComponent do
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> assign(Rules.defaults())
|
||||
|> assign(
|
||||
action: action(assigns.id),
|
||||
rule_list: rule_list(assigns),
|
||||
users: users(),
|
||||
changeset: Rules.new_rule()
|
||||
changeset: Rules.new_rule(),
|
||||
port_rules_supported: Rules.port_rules_supported?()
|
||||
)}
|
||||
end
|
||||
|
||||
@impl Phoenix.LiveComponent
|
||||
def handle_event("change", %{"rule" => rule_params}, socket) do
|
||||
changeset = Rules.new_rule(rule_params)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:changeset, changeset)
|
||||
|> assign(Rules.defaults(changeset))}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("add_rule", %{"rule" => rule_params}, socket) do
|
||||
case Rules.create_rule(rule_params) do
|
||||
{:ok, _rule} ->
|
||||
{:noreply,
|
||||
assign(socket, changeset: Rules.new_rule(), rule_list: rule_list(socket.assigns))}
|
||||
if Rules.port_rules_supported?() || Map.get(rule_params, :port_type) == nil do
|
||||
case Rules.create_rule(rule_params) do
|
||||
{:ok, _rule} ->
|
||||
{:noreply,
|
||||
assign(socket, changeset: Rules.new_rule(), rule_list: rule_list(socket.assigns))
|
||||
|> assign(Rules.defaults())}
|
||||
|
||||
{:error, changeset} ->
|
||||
{:noreply, assign(socket, changeset: changeset)}
|
||||
{:error, changeset} ->
|
||||
{:noreply, assign(socket, changeset: changeset)}
|
||||
end
|
||||
else
|
||||
# While using the UI this should never happen
|
||||
{:noreply,
|
||||
put_flash(socket, :error, "Couldn't add rule. Port-based rules are not supported.")}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,12 +55,18 @@ defmodule FzHttpWeb.RuleLive.RuleListComponent do
|
||||
def handle_event("delete_rule", %{"rule_id" => rule_id}, socket) do
|
||||
rule = Rules.get_rule!(rule_id)
|
||||
|
||||
case Rules.delete_rule(rule) do
|
||||
{:ok, _rule} ->
|
||||
{:noreply, assign(socket, rule_list: rule_list(socket.assigns))}
|
||||
if Rules.port_rules_supported?() || rule.port_type == nil do
|
||||
case Rules.delete_rule(rule) do
|
||||
{:ok, _rule} ->
|
||||
{:noreply, assign(socket, rule_list: rule_list(socket.assigns))}
|
||||
|
||||
{:error, msg} ->
|
||||
{:noreply, put_flash(socket, :error, "Couldn't delete rule. #{msg}")}
|
||||
{:error, msg} ->
|
||||
{:noreply, put_flash(socket, :error, "Couldn't delete rule. #{msg}")}
|
||||
end
|
||||
else
|
||||
# While using the UI this should never happen
|
||||
{:noreply,
|
||||
put_flash(socket, :error, "Couldn't delete rule. Port-based rules are not supported.")}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -74,4 +99,12 @@ defmodule FzHttpWeb.RuleLive.RuleListComponent do
|
||||
defp user_options(users) do
|
||||
Enum.map(users, fn {id, email} -> {email, id} end)
|
||||
end
|
||||
|
||||
defp port_type_options do
|
||||
%{TCP: :tcp, UDP: :udp}
|
||||
end
|
||||
|
||||
defp port_type_display(nil), do: nil
|
||||
defp port_type_display(:tcp), do: "TCP"
|
||||
defp port_type_display(:udp), do: "UDP"
|
||||
end
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<.form let={f} for={@changeset} id={"#{@action}-form"} phx-target={@myself} phx-submit="add_rule">
|
||||
<.form let={f} for={@changeset} id={"#{@action}-form"} phx-change="change" phx-target={@myself} phx-submit="add_rule">
|
||||
<%= hidden_input f, :action, value: @action %>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
@@ -23,6 +23,32 @@
|
||||
prompt: "Optional user scope" %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<%= select f,
|
||||
:port_type,
|
||||
port_type_options(),
|
||||
prompt: "Optional port range",
|
||||
disabled: !@port_rules_supported %>
|
||||
</div>
|
||||
<p class="help is-danger">
|
||||
<%= error_tag f, :port_type %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<%= text_input f, :port_range,
|
||||
class: "input #{input_error_class(f, :port_range)}",
|
||||
placeholder: "Port Range",
|
||||
disabled: @port_type == nil %>
|
||||
</div>
|
||||
<p class="help">
|
||||
Formatted as 'start - stop' or 'port' e.g. 20-80 or 80 (Requires port type)
|
||||
</p>
|
||||
<p class="help is-danger">
|
||||
<%= error_tag f, :port_range %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="control">
|
||||
<%= submit "Add", class: "button is-primary" %>
|
||||
</div>
|
||||
@@ -30,6 +56,11 @@
|
||||
<p class="help is-danger">
|
||||
<%= error_tag f, :destination %>
|
||||
</p>
|
||||
<%= if !@port_rules_supported do %>
|
||||
<p class="help">
|
||||
Minimum kernel version 5.6.9 required for port-based rules
|
||||
</p>
|
||||
<% end %>
|
||||
</.form>
|
||||
|
||||
<table id={"#{@action}-rules"} class="table is-hoverable is-bordered is-striped is-fullwidth">
|
||||
@@ -38,12 +69,14 @@
|
||||
<tr>
|
||||
<th>Destination</th>
|
||||
<th>User Scope</th>
|
||||
<th>Port Type</th>
|
||||
<th>Port Range</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%= for rule <- @rule_list do %>
|
||||
<tr>
|
||||
<tr class={if rule.port_range != nil && !@port_rules_supported, do: "has-background-grey", else: ""}>
|
||||
<td class="has-text-left">
|
||||
<dd class="code">
|
||||
<%= rule.destination %>
|
||||
@@ -52,16 +85,30 @@
|
||||
<td class="has-text-left">
|
||||
<%= @users[rule.user_id] %>
|
||||
</td>
|
||||
<td class="has-text-left">
|
||||
<%= port_type_display(rule.port_type) %>
|
||||
</td>
|
||||
<td class="has-text-left">
|
||||
<%= if rule.port_range != nil, do: rule.port_range %>
|
||||
</td>
|
||||
<td class="has-text-right">
|
||||
<a href="#"
|
||||
phx-click="delete_rule"
|
||||
phx-value-rule_id={rule.id}
|
||||
phx-target={@myself}>
|
||||
phx-target={@myself}
|
||||
disabled={!@port_rules_supported && rule.port_range != nil} >
|
||||
Delete
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<!-- This can happen when moving the DB to an OS with an older Kernel or on the strange case of a
|
||||
kernel downgrade. -->
|
||||
<%= if !@port_rules_supported && Enum.any?(@rule_list, fn rule -> rule.port_range != nil end) do %>
|
||||
<p class="help">
|
||||
Port-based rules are only applied when Linux Kernel is 5.6.9 or greater
|
||||
</p>
|
||||
<% end %>
|
||||
</tbody>
|
||||
<% end %>
|
||||
</table>
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
defmodule FzHttp.Repo.Migrations.AddRulePortRange do
|
||||
use Ecto.Migration
|
||||
|
||||
@create_query "CREATE TYPE port_type_enum AS ENUM ('tcp', 'udp')"
|
||||
@drop_query "DROP TYPE port_type_enum"
|
||||
|
||||
def change do
|
||||
execute("ALTER TABLE rules DROP CONSTRAINT destination_overlap_excl_usr_rule")
|
||||
|
||||
execute("ALTER TABLE rules DROP CONSTRAINT destination_overlap_excl")
|
||||
|
||||
execute(@create_query, @drop_query)
|
||||
|
||||
alter table(:rules) do
|
||||
add :port_range, :int4range, default: nil
|
||||
add :port_type, :port_type_enum, default: nil
|
||||
end
|
||||
|
||||
create constraint("rules", :port_range_needs_type,
|
||||
check: "(port_range IS NULL) = (port_type IS NULL)"
|
||||
)
|
||||
|
||||
create constraint("rules", :port_range_is_within_valid_values,
|
||||
check: "port_range <@ int4range(1, 65535)"
|
||||
)
|
||||
|
||||
execute(
|
||||
"ALTER TABLE rules
|
||||
ADD CONSTRAINT destination_overlap_excl EXCLUDE USING gist (destination inet_ops WITH &&, action WITH =) WHERE (user_id IS NULL AND port_range IS NULL)",
|
||||
"ALTER TABLE rules DROP CONSTRAINT destination_overlap_excl"
|
||||
)
|
||||
|
||||
execute(
|
||||
"ALTER TABLE rules
|
||||
ADD CONSTRAINT destination_overlap_excl_usr_rule EXCLUDE USING gist (destination inet_ops WITH &&, user_id WITH =, action WITH =) WHERE (user_id IS NOT NULL AND port_range IS NULL)",
|
||||
"ALTER TABLE rules DROP CONSTRAINT destination_overlap_excl_usr_rule"
|
||||
)
|
||||
|
||||
execute(
|
||||
"ALTER TABLE rules
|
||||
ADD CONSTRAINT destination_overlap_excl_port EXCLUDE USING gist (destination inet_ops WITH &&, action WITH =, port_range WITH &&, port_type WITH =) WHERE (user_id IS NULL AND port_range IS NOT NULL)",
|
||||
"ALTER TABLE rules DROP CONSTRAINT destination_overlap_excl_rule_port"
|
||||
)
|
||||
|
||||
execute(
|
||||
"ALTER TABLE rules
|
||||
ADD CONSTRAINT destination_overlap_excl_usr_rule_port EXCLUDE USING gist (destination inet_ops WITH &&, user_id WITH =, action WITH =, port_range WITH &&, port_type WITH =) WHERE (user_id IS NOT NULL AND port_range IS NOT NULL)",
|
||||
"ALTER TABLE rules DROP CONSTRAINT destination_overlap_excl_usr_rule_port"
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -87,7 +87,16 @@ defmodule FzHttp.EventsTest do
|
||||
%{
|
||||
users: MapSet.new(),
|
||||
devices: MapSet.new(),
|
||||
rules: MapSet.new([%{destination: "10.10.10.0/24", user_id: nil, action: :drop}])
|
||||
rules:
|
||||
MapSet.new([
|
||||
%{
|
||||
destination: "10.10.10.0/24",
|
||||
port_range: nil,
|
||||
port_type: nil,
|
||||
user_id: nil,
|
||||
action: :drop
|
||||
}
|
||||
])
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -103,7 +112,15 @@ defmodule FzHttp.EventsTest do
|
||||
users: MapSet.new(),
|
||||
devices: MapSet.new(),
|
||||
rules:
|
||||
MapSet.new([%{destination: "10.10.10.0/24", user_id: nil, action: :accept}])
|
||||
MapSet.new([
|
||||
%{
|
||||
destination: "10.10.10.0/24",
|
||||
user_id: nil,
|
||||
action: :accept,
|
||||
port_type: nil,
|
||||
port_range: nil
|
||||
}
|
||||
])
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -167,7 +184,9 @@ defmodule FzHttp.EventsTest do
|
||||
%{
|
||||
user_id: rule.user_id,
|
||||
destination: FzHttp.Devices.decode(rule.destination),
|
||||
action: rule.action
|
||||
action: rule.action,
|
||||
port_range: nil,
|
||||
port_type: nil
|
||||
}
|
||||
end)
|
||||
)
|
||||
|
||||
@@ -52,7 +52,15 @@ defmodule FzHttp.Repo.NotifierTest do
|
||||
expected_state = %{
|
||||
users: MapSet.new([]),
|
||||
rules:
|
||||
MapSet.new([%{action: rule.action, destination: "10.10.10.0/24", user_id: rule.user_id}]),
|
||||
MapSet.new([
|
||||
%{
|
||||
action: rule.action,
|
||||
destination: "10.10.10.0/24",
|
||||
user_id: rule.user_id,
|
||||
port_range: nil,
|
||||
port_type: nil
|
||||
}
|
||||
]),
|
||||
devices: MapSet.new([])
|
||||
}
|
||||
|
||||
|
||||
@@ -53,12 +53,57 @@ defmodule FzHttp.RulesTest do
|
||||
assert rule.user_id == nil
|
||||
end
|
||||
|
||||
test "creates rule with port" do
|
||||
{:ok, rule} =
|
||||
Rules.create_rule(%{destination: "10.0.0.0/24", port_type: :tcp, port_range: "100-200"})
|
||||
|
||||
assert !is_nil(rule.id)
|
||||
assert rule.action == :drop
|
||||
assert rule.user_id == nil
|
||||
assert rule.port_type == :tcp
|
||||
assert rule.port_range == "100 - 200"
|
||||
end
|
||||
|
||||
test "prevents invalid CIDRs" do
|
||||
{:error, changeset} = Rules.create_rule(%{destination: "10.0 0.0/24"})
|
||||
|
||||
assert changeset.errors[:destination] ==
|
||||
{"is invalid", [type: EctoNetwork.INET, validation: :cast]}
|
||||
end
|
||||
|
||||
test "prevents invalid port_range: no port_type" do
|
||||
{:error, changeset} = Rules.create_rule(%{destination: "10.0.0.0/24", port_range: "10-20"})
|
||||
|
||||
assert changeset.errors[:port_type] ==
|
||||
{"Please specify a port-range for the given port type",
|
||||
[constraint: :check, constraint_name: "port_range_needs_type"]}
|
||||
end
|
||||
|
||||
test "prevents invalid port_type: no port_range" do
|
||||
{:error, changeset} = Rules.create_rule(%{destination: "10.0.0.0/24", port_type: :tcp})
|
||||
|
||||
assert changeset.errors[:port_type] ==
|
||||
{"Please specify a port-range for the given port type",
|
||||
[constraint: :check, constraint_name: "port_range_needs_type"]}
|
||||
end
|
||||
|
||||
test "prevents invalid port_range: outside port range" do
|
||||
{:error, changeset} =
|
||||
Rules.create_rule(%{destination: "10.0.0.0/24", port_type: :tcp, port_range: "10-90000"})
|
||||
|
||||
assert changeset.errors[:port_range] ==
|
||||
{"Port is not within valid range",
|
||||
[constraint: :check, constraint_name: "port_range_is_within_valid_values"]}
|
||||
end
|
||||
|
||||
test "prevents invalid port_range: " do
|
||||
{:error, changeset} =
|
||||
Rules.create_rule(%{destination: "10.0.0.0/24", port_type: :tcp, port_range: "20-10"})
|
||||
|
||||
assert changeset.errors[:port_range] ==
|
||||
{"Range Error: Lower bound higher than upper bound",
|
||||
[type: FzHttp.Int4Range, validation: :cast]}
|
||||
end
|
||||
end
|
||||
|
||||
describe "delete_rule/1" do
|
||||
@@ -110,6 +155,20 @@ defmodule FzHttp.RulesTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "setting_projection/1 with ports" do
|
||||
setup [:create_rule_with_ports]
|
||||
|
||||
test "projects expected fields", %{rule: rule} do
|
||||
assert %{
|
||||
destination: "10.10.10.0/24",
|
||||
port_type: :udp,
|
||||
port_range: "10 - 20",
|
||||
action: :drop,
|
||||
user_id: nil
|
||||
} = Rules.setting_projection(rule)
|
||||
end
|
||||
end
|
||||
|
||||
describe "as_settings/0" do
|
||||
setup [:create_rules]
|
||||
|
||||
|
||||
@@ -187,6 +187,12 @@ defmodule FzHttp.TestHelpers do
|
||||
{:ok, rule: rule, user: user}
|
||||
end
|
||||
|
||||
def create_rule_with_ports(opts \\ %{}) do
|
||||
rule = RulesFixtures.rule(Map.merge(%{port_range: "10 - 20", port_type: :udp}, opts))
|
||||
|
||||
{:ok, rule: rule}
|
||||
end
|
||||
|
||||
def create_user_with_valid_sign_in_token(_) do
|
||||
{:ok, user: %User{} = UsersFixtures.user(Users.sign_in_keys())}
|
||||
end
|
||||
|
||||
@@ -6,56 +6,88 @@ defmodule FzWall.CLI.Helpers.Nft do
|
||||
import FzCommon.FzNet, only: [standardized_inet: 1]
|
||||
require Logger
|
||||
@table_name "firezone"
|
||||
@main_chain "forward"
|
||||
|
||||
@doc """
|
||||
Insert a nft rule
|
||||
Insert a nft filter rule
|
||||
"""
|
||||
def insert_rule(type, source_set, dest_set, action) do
|
||||
exec!("""
|
||||
#{nft()} 'insert rule inet #{@table_name} forward #{rule_match_str(type, source_set, dest_set, action)}'
|
||||
""")
|
||||
def insert_filter_rule(chain, type, dest_set, action, layer4) do
|
||||
insert_rule(chain, rule_filter_match_str(type, dest_set, action, layer4))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes a nft rule
|
||||
Insert a nft jump rule
|
||||
"""
|
||||
def remove_rule(type, source_set, dest_set, action) do
|
||||
delete_rule_matching(rule_match_str(type, source_set, dest_set, action))
|
||||
def insert_dev_rule(ip_type, source_set, jump_chain) do
|
||||
insert_rule(@main_chain, rule_dev_match_str(ip_type, source_set, jump_chain))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds an element from a nft set
|
||||
Remove device-related rule
|
||||
"""
|
||||
# Note: we don't need remove_filter_rule because the chains are removed all together
|
||||
def remove_dev_rule(ip_type, source_set, jump_chain) do
|
||||
delete_rule_matching(rule_dev_match_str(ip_type, source_set, jump_chain))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Add element to set
|
||||
"""
|
||||
def add_elem(set, ip, nil, nil) do
|
||||
add_ip_elem(set, ip)
|
||||
end
|
||||
|
||||
def add_elem(set, ip, proto, ports) do
|
||||
add_elem_exec(set, get_elem(ip, proto, ports))
|
||||
end
|
||||
|
||||
def add_elem(set, ip) do
|
||||
exec!("""
|
||||
#{nft()} 'add element inet #{@table_name} #{set} { #{standardized_inet(ip)} }'
|
||||
""")
|
||||
add_ip_elem(set, ip)
|
||||
end
|
||||
|
||||
defp add_ip_elem(set, ip) do
|
||||
add_elem_exec(set, get_elem(ip))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes an element from a nft set
|
||||
"""
|
||||
def delete_elem(set, ip) do
|
||||
exec!("""
|
||||
#{nft()} 'delete element inet #{@table_name} #{set} { #{standardized_inet(ip)} }'
|
||||
""")
|
||||
delete_ip_elem(set, ip)
|
||||
end
|
||||
|
||||
def delete_elem(set, ip, nil, nil) do
|
||||
delete_ip_elem(set, ip)
|
||||
end
|
||||
|
||||
def delete_elem(set, ip, proto, ports) do
|
||||
delete_elem_exec(set, get_elem(ip, proto, ports))
|
||||
end
|
||||
|
||||
defp delete_ip_elem(set, ip) do
|
||||
delete_elem_exec(set, get_elem(ip))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds a nft set
|
||||
Adds a nft dev set
|
||||
"""
|
||||
def add_set(set_spec) do
|
||||
exec!("""
|
||||
#{nft()} 'add set inet #{@table_name} #{set_spec.name} { type #{set_type(set_spec.type)} ; flags interval ; }'
|
||||
""")
|
||||
def add_dev_set(name, ip_type) do
|
||||
add_set(name, dev_set_type(ip_type))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds a nft filter set
|
||||
"""
|
||||
def add_filter_set(name, ip_type, layer4) do
|
||||
add_set(name, filter_set_type(ip_type, layer4))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a nft set
|
||||
"""
|
||||
def delete_set(set_spec) do
|
||||
def delete_set(name) do
|
||||
exec!("""
|
||||
#{nft()} 'delete set inet #{@table_name} #{set_spec.name}'
|
||||
#{nft()} 'delete set inet #{@table_name} #{name}'
|
||||
""")
|
||||
end
|
||||
|
||||
@@ -71,7 +103,7 @@ defmodule FzWall.CLI.Helpers.Nft do
|
||||
"""
|
||||
def setup_chains do
|
||||
exec!(
|
||||
"#{nft()} 'add chain inet #{@table_name} forward " <>
|
||||
"#{nft()} 'add chain inet #{@table_name} #{@main_chain} " <>
|
||||
"{ type filter hook forward priority 0 ; policy accept ; }'"
|
||||
)
|
||||
|
||||
@@ -83,6 +115,20 @@ defmodule FzWall.CLI.Helpers.Nft do
|
||||
setup_masquerade()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds a regular nftable chain(not base)
|
||||
"""
|
||||
def add_chain(chain_name) do
|
||||
exec!("#{nft()} 'add chain inet #{@table_name} #{chain_name}'")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a regular nftable chain(not base)
|
||||
"""
|
||||
def delete_chain(chain_name) do
|
||||
exec!("#{nft()} 'delete chain inet #{@table_name} #{chain_name}'")
|
||||
end
|
||||
|
||||
defp setup_masquerade do
|
||||
if masquerade_ipv4?() do
|
||||
setup_masquerade(:ipv4)
|
||||
@@ -160,7 +206,7 @@ defmodule FzWall.CLI.Helpers.Nft do
|
||||
)
|
||||
|
||||
handle ->
|
||||
exec!("#{nft()} delete rule inet #{@table_name} forward handle #{handle}")
|
||||
exec!("#{nft()} delete rule inet #{@table_name} #{@main_chain} handle #{handle}")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -172,14 +218,55 @@ defmodule FzWall.CLI.Helpers.Nft do
|
||||
Regex.run(regex, rules, capture: :all_names)
|
||||
end
|
||||
|
||||
defp set_type(:ip), do: "ipv4_addr"
|
||||
defp set_type(:ip6), do: "ipv6_addr"
|
||||
defp filter_set_type(:ip, false), do: "ipv4_addr"
|
||||
defp filter_set_type(:ip6, false), do: "ipv6_addr"
|
||||
|
||||
defp rule_match_str(type, nil, dest_set, action) do
|
||||
defp filter_set_type(ip_type, true),
|
||||
do: "#{filter_set_type(ip_type, false)} . inet_proto . inet_service"
|
||||
|
||||
defp dev_set_type(ip_type), do: filter_set_type(ip_type, false)
|
||||
|
||||
defp rule_filter_match_str(type, dest_set, action, false) do
|
||||
"#{type} daddr @#{dest_set} ct state != established #{action}"
|
||||
end
|
||||
|
||||
defp rule_match_str(type, source_set, dest_set, action) do
|
||||
"#{type} saddr @#{source_set} #{rule_match_str(type, nil, dest_set, action)}"
|
||||
defp rule_filter_match_str(type, dest_set, action, true) do
|
||||
"#{type} daddr . meta l4proto . th dport @#{dest_set} ct state != established #{action}"
|
||||
end
|
||||
|
||||
defp rule_dev_match_str(ip_type, source_set, jump_chain) do
|
||||
"#{ip_type} saddr @#{source_set} jump #{jump_chain}"
|
||||
end
|
||||
|
||||
defp insert_rule(chain, rule_str) do
|
||||
exec!("""
|
||||
#{nft()} 'insert rule inet #{@table_name} #{chain} #{rule_str}'
|
||||
""")
|
||||
end
|
||||
|
||||
defp delete_elem_exec(set, elem) do
|
||||
exec!("""
|
||||
#{nft()} 'delete element inet #{@table_name} #{set} { #{elem} }'
|
||||
""")
|
||||
end
|
||||
|
||||
defp add_set(name, type) do
|
||||
exec!("""
|
||||
#{nft()} 'add set inet #{@table_name} #{name} { type #{type} ; flags interval ; }'
|
||||
""")
|
||||
end
|
||||
|
||||
defp add_elem_exec(set, elem) do
|
||||
exec!("""
|
||||
#{nft()} 'add element inet #{@table_name} #{set} { #{elem} }'
|
||||
""")
|
||||
end
|
||||
|
||||
def get_elem(ip) do
|
||||
"#{standardized_inet(ip)}"
|
||||
end
|
||||
|
||||
def get_elem(ip, proto, ports) do
|
||||
"#{standardized_inet(ip)} . #{proto} . #{ports}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,39 +4,66 @@ defmodule FzWall.CLI.Helpers.Sets do
|
||||
"""
|
||||
|
||||
@actions [:drop, :accept]
|
||||
@types [:ip, :ip6]
|
||||
@ip_types [:ip, :ip6]
|
||||
|
||||
def list_dest_sets(user_id) do
|
||||
cross(@types, @actions)
|
||||
|> Enum.map(fn {type, action} ->
|
||||
%{name: get_dest_set_name(user_id, type, action), type: type}
|
||||
defp port_rules_supported?, do: Application.fetch_env!(:fz_wall, :port_based_rules_supported)
|
||||
|
||||
def list_filter_sets(user_id) do
|
||||
get_all_filter_sets(user_id, port_rules_supported?())
|
||||
end
|
||||
|
||||
defp get_all_filter_sets(user_id, false) do
|
||||
get_filter_sets_spec(user_id, false)
|
||||
end
|
||||
|
||||
defp get_all_filter_sets(user_id, true) do
|
||||
get_all_filter_sets(user_id, false) ++ get_filter_sets_spec(user_id, true)
|
||||
end
|
||||
|
||||
defp get_filter_sets_spec(user_id, layer4) do
|
||||
cross(@ip_types, @actions)
|
||||
|> Enum.map(fn {ip_type, action} ->
|
||||
%{
|
||||
name: get_filter_set_name(user_id, ip_type, action, layer4),
|
||||
ip_type: ip_type,
|
||||
action: action,
|
||||
layer4: layer4
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
def list_sets(nil), do: list_dest_sets(nil)
|
||||
|
||||
def list_sets(user_id) do
|
||||
list_dest_sets(user_id) ++
|
||||
Enum.map(@types, fn type -> %{name: get_device_set_name(user_id, type), type: type} end)
|
||||
def list_dev_sets(user_id) do
|
||||
Enum.map(@ip_types, fn type -> %{name: get_device_set_name(user_id, type), ip_type: type} end)
|
||||
end
|
||||
|
||||
def get_types do
|
||||
@types
|
||||
def get_ip_types do
|
||||
@ip_types
|
||||
end
|
||||
|
||||
def get_actions do
|
||||
@actions
|
||||
end
|
||||
|
||||
def get_dest_set_name(nil, type, action), do: "#{type}_#{action}"
|
||||
def get_dest_set_name(user_id, type, action), do: "user_#{user_id}_#{type}_#{action}"
|
||||
def get_device_set_name(nil, _type), do: nil
|
||||
def get_device_set_name(user_id, type), do: "user_#{user_id}_#{type}_devices"
|
||||
def get_device_set_name(user_id, type), do: "user#{user_id}_#{type}_devices"
|
||||
def get_user_chain(nil), do: "forward"
|
||||
def get_user_chain(user_id), do: "user#{user_id}"
|
||||
|
||||
def cross([x | a], [y | b]) do
|
||||
def get_filter_set_name(nil, ip_type, action, false),
|
||||
do: "#{ip_type}_#{action}"
|
||||
|
||||
def get_filter_set_name(user_id, ip_type, action, false),
|
||||
do: "user#{user_id}_#{ip_type}_#{action}"
|
||||
|
||||
def get_filter_set_name(nil, ip_type, action, true),
|
||||
do: "#{ip_type}_#{action}_layer4"
|
||||
|
||||
def get_filter_set_name(user_id, ip_type, action, true),
|
||||
do: "user#{user_id}_#{ip_type}_#{action}_layer4"
|
||||
|
||||
defp cross([x | a], [y | b]) do
|
||||
[{x, y}] ++ cross([x], b) ++ cross(a, [y | b])
|
||||
end
|
||||
|
||||
def cross([], _b), do: []
|
||||
def cross(_a, []), do: []
|
||||
defp cross([], _b), do: []
|
||||
defp cross(_a, []), do: []
|
||||
end
|
||||
|
||||
@@ -8,7 +8,6 @@ defmodule FzWall.CLI.Live do
|
||||
|
||||
import FzWall.CLI.Helpers.Sets
|
||||
import FzWall.CLI.Helpers.Nft
|
||||
import FzCommon.CLI
|
||||
import FzCommon.FzNet, only: [ip_type: 1]
|
||||
require Logger
|
||||
|
||||
@@ -19,60 +18,81 @@ defmodule FzWall.CLI.Live do
|
||||
teardown_table()
|
||||
setup_table()
|
||||
setup_chains()
|
||||
setup_rules()
|
||||
setup_rules(nil)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds user sets and rules.
|
||||
"""
|
||||
def add_user(user_id) do
|
||||
add_sets(user_id)
|
||||
add_rules(user_id)
|
||||
add_user_set(user_id)
|
||||
add_chain(get_user_chain(user_id))
|
||||
set_jump_rule(user_id)
|
||||
setup_rules(user_id)
|
||||
end
|
||||
|
||||
defp add_user_set(user_id) do
|
||||
list_dev_sets(user_id)
|
||||
|> Enum.map(fn set_spec -> add_dev_set(set_spec.name, set_spec.ip_type) end)
|
||||
end
|
||||
|
||||
defp delete_user_set(user_id) do
|
||||
list_dev_sets(user_id)
|
||||
|> Enum.map(fn set_spec -> delete_set(set_spec.name) end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Remove user sets and rules.
|
||||
"""
|
||||
def delete_user(user_id) do
|
||||
delete_rules(user_id)
|
||||
delete_sets(user_id)
|
||||
delete_jump_rules(user_id)
|
||||
delete_user_set(user_id)
|
||||
delete_chain(get_user_chain(user_id))
|
||||
delete_filter_sets(user_id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds general sets and rules.
|
||||
"""
|
||||
def setup_rules do
|
||||
add_sets(nil)
|
||||
add_rules(nil)
|
||||
def setup_rules(user_id) do
|
||||
add_filter_sets(user_id)
|
||||
add_filter_rules(user_id)
|
||||
end
|
||||
|
||||
def set_jump_rule(user_id) do
|
||||
list_dev_sets(user_id)
|
||||
|> Enum.each(fn set_spec ->
|
||||
insert_dev_rule(set_spec.ip_type, set_spec.name, get_user_chain(user_id))
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds device ip to the user's sets.
|
||||
"""
|
||||
def add_device(device) do
|
||||
get_types()
|
||||
|> Enum.each(fn type -> add_to_set(device.user_id, device[type], type) end)
|
||||
list_dev_sets(device.user_id)
|
||||
|> Enum.each(fn set_spec -> add_elem(set_spec.name, device[set_spec.ip_type]) end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds rule ip to its corresponding sets.
|
||||
"""
|
||||
def add_rule(rule) do
|
||||
add_to_set(rule.user_id, rule.destination, proto(rule.destination), rule.action)
|
||||
modify_elem(&add_elem/4, rule)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Delete rule destination ip from its corresponding sets.
|
||||
"""
|
||||
def delete_rule(rule) do
|
||||
remove_from_set(rule.user_id, rule.destination, proto(rule.destination), rule.action)
|
||||
modify_elem(&delete_elem/4, rule)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Eliminates device rules from its corresponding sets.
|
||||
"""
|
||||
def delete_device(device) do
|
||||
get_types()
|
||||
get_ip_types()
|
||||
|> Enum.each(fn type -> remove_from_set(device.user_id, device[type], type) end)
|
||||
end
|
||||
|
||||
@@ -83,54 +103,35 @@ defmodule FzWall.CLI.Live do
|
||||
|> delete_elem(ip)
|
||||
end
|
||||
|
||||
defp remove_from_set(user_id, ip, type, action) do
|
||||
get_dest_set_name(user_id, type, action)
|
||||
|> delete_elem(ip)
|
||||
defp add_filter_sets(user_id) do
|
||||
list_filter_sets(user_id)
|
||||
|> Enum.each(fn set_spec ->
|
||||
add_filter_set(set_spec.name, set_spec.ip_type, set_spec.layer4)
|
||||
end)
|
||||
end
|
||||
|
||||
defp add_to_set(_user_id, nil, _type), do: :no_ip
|
||||
|
||||
defp add_to_set(user_id, ip, type) do
|
||||
get_device_set_name(user_id, type)
|
||||
|> add_elem(ip)
|
||||
defp delete_filter_sets(user_id) do
|
||||
list_filter_sets(user_id)
|
||||
|> Enum.each(fn set_spec -> delete_set(set_spec.name) end)
|
||||
end
|
||||
|
||||
defp add_to_set(user_id, ip, type, action) do
|
||||
get_dest_set_name(user_id, type, action)
|
||||
|> add_elem(ip)
|
||||
end
|
||||
|
||||
defp add_sets(user_id) do
|
||||
list_sets(user_id)
|
||||
|> Enum.each(&add_set/1)
|
||||
end
|
||||
|
||||
defp delete_sets(user_id) do
|
||||
list_sets(user_id)
|
||||
|> Enum.each(&delete_set/1)
|
||||
end
|
||||
|
||||
defp add_rules(user_id) do
|
||||
cross(get_types(), get_actions())
|
||||
|> Enum.each(fn {type, action} ->
|
||||
insert_rule(
|
||||
type,
|
||||
get_device_set_name(user_id, type),
|
||||
get_dest_set_name(user_id, type, action),
|
||||
action
|
||||
defp add_filter_rules(user_id) do
|
||||
list_filter_sets(user_id)
|
||||
|> Enum.each(fn set_spec ->
|
||||
insert_filter_rule(
|
||||
get_user_chain(user_id),
|
||||
set_spec.ip_type,
|
||||
set_spec.name,
|
||||
set_spec.action,
|
||||
set_spec.layer4
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
defp delete_rules(user_id) do
|
||||
cross(get_types(), get_actions())
|
||||
|> Enum.each(fn {type, action} ->
|
||||
remove_rule(
|
||||
type,
|
||||
get_device_set_name(user_id, type),
|
||||
get_dest_set_name(user_id, type, action),
|
||||
action
|
||||
)
|
||||
defp delete_jump_rules(user_id) do
|
||||
list_dev_sets(user_id)
|
||||
|> Enum.each(fn set_spec ->
|
||||
remove_dev_rule(set_spec.ip_type, set_spec.name, get_user_chain(user_id))
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -141,36 +142,24 @@ defmodule FzWall.CLI.Live do
|
||||
Enum.each(rules, &add_rule/1)
|
||||
end
|
||||
|
||||
def egress_address do
|
||||
case :os.type() do
|
||||
{:unix, :linux} ->
|
||||
cmd = "ip address show dev #{egress_interface()} | grep 'inet ' | awk '{print $2}'"
|
||||
|
||||
exec!(cmd)
|
||||
|> String.trim()
|
||||
|> String.split("/")
|
||||
|> List.first()
|
||||
|
||||
{:unix, :darwin} ->
|
||||
cmd = "ipconfig getifaddr #{egress_interface()}"
|
||||
|
||||
exec!(cmd)
|
||||
|> String.trim()
|
||||
|
||||
_ ->
|
||||
raise "OS not supported (yet)"
|
||||
end
|
||||
end
|
||||
|
||||
defp egress_interface do
|
||||
Application.fetch_env!(:fz_wall, :egress_interface)
|
||||
end
|
||||
|
||||
defp proto(ip) do
|
||||
case ip_type("#{ip}") do
|
||||
"IPv4" -> "ip"
|
||||
"IPv6" -> "ip6"
|
||||
"IPv4" -> :ip
|
||||
"IPv6" -> :ip6
|
||||
"unknown" -> raise "Unknown protocol."
|
||||
end
|
||||
end
|
||||
|
||||
defp modify_elem(action, rule) do
|
||||
ip_type = proto(rule.destination)
|
||||
port_type = rule.port_type
|
||||
layer4 = port_type != nil
|
||||
|
||||
action.(
|
||||
get_filter_set_name(rule.user_id, ip_type, rule.action, layer4),
|
||||
rule.destination,
|
||||
port_type,
|
||||
rule.port_range
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,5 +13,4 @@ defmodule FzWall.CLI.Sandbox do
|
||||
def delete_device(_device), do: @default_returned
|
||||
def add_user(_user), do: @default_returned
|
||||
def delete_user(_user), do: @default_returned
|
||||
def egress_address, do: "10.0.0.1"
|
||||
end
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
defmodule FzWall.CLI.SandboxTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
import FzWall.CLI
|
||||
|
||||
test "egress_address()" do
|
||||
assert is_binary(cli().egress_address())
|
||||
end
|
||||
end
|
||||
@@ -95,7 +95,8 @@ config :fz_wall,
|
||||
wireguard_ipv6_masquerade: true,
|
||||
server_process_opts: [name: {:global, :fz_wall_server}],
|
||||
egress_interface: "dummy",
|
||||
wireguard_interface_name: "wg-firezone"
|
||||
wireguard_interface_name: "wg-firezone",
|
||||
port_based_rules_supported: true
|
||||
|
||||
config :hammer,
|
||||
backend: {Hammer.Backend.ETS, [expiry_ms: 60_000 * 60 * 4, cleanup_interval_ms: 60_000 * 10]}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import Config
|
||||
|
||||
alias FzCommon.{CLI, FzInteger, FzString}
|
||||
alias FzCommon.{CLI, FzInteger, FzString, FzKernelVersion}
|
||||
|
||||
# external_url is important
|
||||
external_url = System.get_env("EXTERNAL_URL", "https://localhost")
|
||||
@@ -17,6 +17,9 @@ config :fz_http, FzHttpWeb.Endpoint,
|
||||
url: [host: host, scheme: scheme, port: port, path: path],
|
||||
check_origin: ["//127.0.0.1", "//localhost", "//#{host}"]
|
||||
|
||||
config :fz_wall,
|
||||
port_based_rules_supported: FzKernelVersion.is_version_greater_than?({5, 6, 8})
|
||||
|
||||
# Formerly releases.exs - Only evaluated in production
|
||||
if config_env() == :prod do
|
||||
# For releases, require that all these are set
|
||||
|
||||
Reference in New Issue
Block a user