mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
This reverts commit 55a311adec.
This commit is contained in:
@@ -1,90 +0,0 @@
|
||||
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,7 +4,7 @@ defmodule FzHttp.Rules do
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
import Ecto.Changeset
|
||||
|
||||
alias FzHttp.{Repo, Rules.Rule, Rules.RuleSetting, Telemetry}
|
||||
|
||||
def list_rules, do: Repo.all(Rule)
|
||||
@@ -38,14 +38,6 @@ 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,15 +7,11 @@ 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)
|
||||
@@ -26,19 +22,9 @@ defmodule FzHttp.Rules.Rule do
|
||||
|> cast(attrs, [
|
||||
:user_id,
|
||||
:action,
|
||||
:destination,
|
||||
:port_type,
|
||||
:port_range
|
||||
:destination
|
||||
])
|
||||
|> 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
|
||||
@@ -47,13 +33,5 @@ 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,23 +12,19 @@ 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,
|
||||
port_type: rule.port_type,
|
||||
port_range: rule.port_range
|
||||
user_id: rule.user_id
|
||||
}
|
||||
end
|
||||
|
||||
def parse(rule) when is_map(rule) do
|
||||
%__MODULE__{}
|
||||
|> cast(rule, [:action, :destination, :user_id, :port_type, :port_range])
|
||||
|> cast(rule, [:action, :destination, :user_id])
|
||||
|> apply_changes()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,7 +12,6 @@ defmodule FzHttpWeb.RuleLive.RuleListComponent do
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> assign(Rules.defaults())
|
||||
|> assign(
|
||||
action: action(assigns.id),
|
||||
rule_list: rule_list(assigns),
|
||||
@@ -21,23 +20,12 @@ defmodule FzHttpWeb.RuleLive.RuleListComponent do
|
||||
)}
|
||||
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))
|
||||
|> assign(Rules.defaults())}
|
||||
assign(socket, changeset: Rules.new_rule(), rule_list: rule_list(socket.assigns))}
|
||||
|
||||
{:error, changeset} ->
|
||||
{:noreply, assign(socket, changeset: changeset)}
|
||||
@@ -86,12 +74,4 @@ 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-change="change" phx-target={@myself} phx-submit="add_rule">
|
||||
<.form let={f} for={@changeset} id={"#{@action}-form"} phx-target={@myself} phx-submit="add_rule">
|
||||
<%= hidden_input f, :action, value: @action %>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
@@ -23,31 +23,6 @@
|
||||
prompt: "Optional user scope" %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<%= select f,
|
||||
:port_type,
|
||||
port_type_options(),
|
||||
prompt: "Optional port range" %>
|
||||
</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>
|
||||
@@ -63,8 +38,6 @@
|
||||
<tr>
|
||||
<th>Destination</th>
|
||||
<th>User Scope</th>
|
||||
<th>Port Type</th>
|
||||
<th>Port Range</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -79,12 +52,6 @@
|
||||
<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"
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
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,16 +87,7 @@ defmodule FzHttp.EventsTest do
|
||||
%{
|
||||
users: MapSet.new(),
|
||||
devices: MapSet.new(),
|
||||
rules:
|
||||
MapSet.new([
|
||||
%{
|
||||
destination: "10.10.10.0/24",
|
||||
port_range: nil,
|
||||
port_type: nil,
|
||||
user_id: nil,
|
||||
action: :drop
|
||||
}
|
||||
])
|
||||
rules: MapSet.new([%{destination: "10.10.10.0/24", user_id: nil, action: :drop}])
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -112,15 +103,7 @@ defmodule FzHttp.EventsTest do
|
||||
users: MapSet.new(),
|
||||
devices: MapSet.new(),
|
||||
rules:
|
||||
MapSet.new([
|
||||
%{
|
||||
destination: "10.10.10.0/24",
|
||||
user_id: nil,
|
||||
action: :accept,
|
||||
port_type: nil,
|
||||
port_range: nil
|
||||
}
|
||||
])
|
||||
MapSet.new([%{destination: "10.10.10.0/24", user_id: nil, action: :accept}])
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -184,9 +167,7 @@ defmodule FzHttp.EventsTest do
|
||||
%{
|
||||
user_id: rule.user_id,
|
||||
destination: FzHttp.Devices.decode(rule.destination),
|
||||
action: rule.action,
|
||||
port_range: nil,
|
||||
port_type: nil
|
||||
action: rule.action
|
||||
}
|
||||
end)
|
||||
)
|
||||
|
||||
@@ -52,15 +52,7 @@ 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,
|
||||
port_range: nil,
|
||||
port_type: nil
|
||||
}
|
||||
]),
|
||||
MapSet.new([%{action: rule.action, destination: "10.10.10.0/24", user_id: rule.user_id}]),
|
||||
devices: MapSet.new([])
|
||||
}
|
||||
|
||||
|
||||
@@ -53,57 +53,12 @@ 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
|
||||
@@ -155,20 +110,6 @@ 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,12 +187,6 @@ 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,88 +6,56 @@ 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 filter rule
|
||||
Insert a nft rule
|
||||
"""
|
||||
def insert_filter_rule(chain, type, dest_set, action, layer4) do
|
||||
insert_rule(chain, rule_filter_match_str(type, dest_set, action, layer4))
|
||||
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)}'
|
||||
""")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Insert a nft jump rule
|
||||
Removes a nft rule
|
||||
"""
|
||||
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))
|
||||
def remove_rule(type, source_set, dest_set, action) do
|
||||
delete_rule_matching(rule_match_str(type, source_set, dest_set, action))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Remove device-related rule
|
||||
Adds an element from a nft set
|
||||
"""
|
||||
# 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
|
||||
add_ip_elem(set, ip)
|
||||
end
|
||||
|
||||
defp add_ip_elem(set, ip) do
|
||||
add_elem_exec(set, get_elem(ip))
|
||||
exec!("""
|
||||
#{nft()} 'add element inet #{@table_name} #{set} { #{standardized_inet(ip)} }'
|
||||
""")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes an element from a nft set
|
||||
"""
|
||||
def delete_elem(set, ip) do
|
||||
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))
|
||||
exec!("""
|
||||
#{nft()} 'delete element inet #{@table_name} #{set} { #{standardized_inet(ip)} }'
|
||||
""")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds a nft dev set
|
||||
Adds a nft set
|
||||
"""
|
||||
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))
|
||||
def add_set(set_spec) do
|
||||
exec!("""
|
||||
#{nft()} 'add set inet #{@table_name} #{set_spec.name} { type #{set_type(set_spec.type)} ; flags interval ; }'
|
||||
""")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a nft set
|
||||
"""
|
||||
def delete_set(name) do
|
||||
def delete_set(set_spec) do
|
||||
exec!("""
|
||||
#{nft()} 'delete set inet #{@table_name} #{name}'
|
||||
#{nft()} 'delete set inet #{@table_name} #{set_spec.name}'
|
||||
""")
|
||||
end
|
||||
|
||||
@@ -103,7 +71,7 @@ defmodule FzWall.CLI.Helpers.Nft do
|
||||
"""
|
||||
def setup_chains do
|
||||
exec!(
|
||||
"#{nft()} 'add chain inet #{@table_name} #{@main_chain} " <>
|
||||
"#{nft()} 'add chain inet #{@table_name} forward " <>
|
||||
"{ type filter hook forward priority 0 ; policy accept ; }'"
|
||||
)
|
||||
|
||||
@@ -115,20 +83,6 @@ 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)
|
||||
@@ -206,7 +160,7 @@ defmodule FzWall.CLI.Helpers.Nft do
|
||||
)
|
||||
|
||||
handle ->
|
||||
exec!("#{nft()} delete rule inet #{@table_name} #{@main_chain} handle #{handle}")
|
||||
exec!("#{nft()} delete rule inet #{@table_name} forward handle #{handle}")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -218,55 +172,14 @@ defmodule FzWall.CLI.Helpers.Nft do
|
||||
Regex.run(regex, rules, capture: :all_names)
|
||||
end
|
||||
|
||||
defp filter_set_type(:ip, false), do: "ipv4_addr"
|
||||
defp filter_set_type(:ip6, false), do: "ipv6_addr"
|
||||
defp set_type(:ip), do: "ipv4_addr"
|
||||
defp set_type(:ip6), do: "ipv6_addr"
|
||||
|
||||
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
|
||||
defp rule_match_str(type, nil, dest_set, action) do
|
||||
"#{type} daddr @#{dest_set} ct state != established #{action}"
|
||||
end
|
||||
|
||||
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}"
|
||||
defp rule_match_str(type, source_set, dest_set, action) do
|
||||
"#{type} saddr @#{source_set} #{rule_match_str(type, nil, dest_set, action)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,52 +4,34 @@ defmodule FzWall.CLI.Helpers.Sets do
|
||||
"""
|
||||
|
||||
@actions [:drop, :accept]
|
||||
@ip_types [:ip, :ip6]
|
||||
@types [:ip, :ip6]
|
||||
|
||||
def list_filter_sets(user_id) do
|
||||
Enum.flat_map(
|
||||
[true, false],
|
||||
fn layer4 ->
|
||||
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_dest_sets(user_id) do
|
||||
cross(@types, @actions)
|
||||
|> Enum.map(fn {type, action} ->
|
||||
%{name: get_dest_set_name(user_id, type, action), type: type}
|
||||
end)
|
||||
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)
|
||||
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)
|
||||
end
|
||||
|
||||
def get_ip_types do
|
||||
@ip_types
|
||||
def get_types do
|
||||
@types
|
||||
end
|
||||
|
||||
def get_actions do
|
||||
@actions
|
||||
end
|
||||
|
||||
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 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"
|
||||
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 cross([x | a], [y | b]) do
|
||||
[{x, y}] ++ cross([x], b) ++ cross(a, [y | b])
|
||||
|
||||
@@ -8,6 +8,7 @@ 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
|
||||
|
||||
@@ -18,81 +19,60 @@ defmodule FzWall.CLI.Live do
|
||||
teardown_table()
|
||||
setup_table()
|
||||
setup_chains()
|
||||
setup_rules(nil)
|
||||
setup_rules()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds user sets and rules.
|
||||
"""
|
||||
def add_user(user_id) do
|
||||
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)
|
||||
add_sets(user_id)
|
||||
add_rules(user_id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Remove user sets and rules.
|
||||
"""
|
||||
def delete_user(user_id) do
|
||||
delete_jump_rules(user_id)
|
||||
delete_user_set(user_id)
|
||||
delete_chain(get_user_chain(user_id))
|
||||
delete_filter_sets(user_id)
|
||||
delete_rules(user_id)
|
||||
delete_sets(user_id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds general sets and rules.
|
||||
"""
|
||||
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)
|
||||
def setup_rules do
|
||||
add_sets(nil)
|
||||
add_rules(nil)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds device ip to the user's sets.
|
||||
"""
|
||||
def add_device(device) do
|
||||
list_dev_sets(device.user_id)
|
||||
|> Enum.each(fn set_spec -> add_elem(set_spec.name, device[set_spec.ip_type]) end)
|
||||
get_types()
|
||||
|> Enum.each(fn type -> add_to_set(device.user_id, device[type], type) end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds rule ip to its corresponding sets.
|
||||
"""
|
||||
def add_rule(rule) do
|
||||
modify_elem(&add_elem/4, rule)
|
||||
add_to_set(rule.user_id, rule.destination, proto(rule.destination), rule.action)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Delete rule destination ip from its corresponding sets.
|
||||
"""
|
||||
def delete_rule(rule) do
|
||||
modify_elem(&delete_elem/4, rule)
|
||||
remove_from_set(rule.user_id, rule.destination, proto(rule.destination), rule.action)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Eliminates device rules from its corresponding sets.
|
||||
"""
|
||||
def delete_device(device) do
|
||||
get_ip_types()
|
||||
get_types()
|
||||
|> Enum.each(fn type -> remove_from_set(device.user_id, device[type], type) end)
|
||||
end
|
||||
|
||||
@@ -103,35 +83,54 @@ defmodule FzWall.CLI.Live do
|
||||
|> delete_elem(ip)
|
||||
end
|
||||
|
||||
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)
|
||||
defp remove_from_set(user_id, ip, type, action) do
|
||||
get_dest_set_name(user_id, type, action)
|
||||
|> delete_elem(ip)
|
||||
end
|
||||
|
||||
defp delete_filter_sets(user_id) do
|
||||
list_filter_sets(user_id)
|
||||
|> Enum.each(fn set_spec -> delete_set(set_spec.name) 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)
|
||||
end
|
||||
|
||||
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
|
||||
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
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
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))
|
||||
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
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -142,24 +141,36 @@ defmodule FzWall.CLI.Live do
|
||||
Enum.each(rules, &add_rule/1)
|
||||
end
|
||||
|
||||
defp proto(ip) do
|
||||
case ip_type("#{ip}") do
|
||||
"IPv4" -> :ip
|
||||
"IPv6" -> :ip6
|
||||
"unknown" -> raise "Unknown protocol."
|
||||
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 modify_elem(action, rule) do
|
||||
ip_type = proto(rule.destination)
|
||||
port_type = rule.port_type
|
||||
layer4 = port_type != nil
|
||||
defp egress_interface do
|
||||
Application.fetch_env!(:fz_wall, :egress_interface)
|
||||
end
|
||||
|
||||
action.(
|
||||
get_filter_set_name(rule.user_id, ip_type, rule.action, layer4),
|
||||
rule.destination,
|
||||
port_type,
|
||||
rule.port_range
|
||||
)
|
||||
defp proto(ip) do
|
||||
case ip_type("#{ip}") do
|
||||
"IPv4" -> "ip"
|
||||
"IPv6" -> "ip6"
|
||||
"unknown" -> raise "Unknown protocol."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,4 +13,5 @@ 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
|
||||
|
||||
@@ -2,4 +2,8 @@ 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
|
||||
|
||||
Reference in New Issue
Block a user