Revert "Add initial rough version of port based rules (#874)" (#888)

This reverts commit 55a311adec.
This commit is contained in:
Jamil
2022-08-03 12:34:30 -07:00
committed by GitHub
parent 2a5466a0fb
commit 58e48457ad
16 changed files with 142 additions and 551 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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([])
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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