From 6ac58afa74616c19d852064ba4fb3de1094142f5 Mon Sep 17 00:00:00 2001 From: Jamil Bou Kheir Date: Thu, 2 Sep 2021 23:42:49 +0000 Subject: [PATCH] Add/Delete rule is working --- .../live/rule_live/rule_list_component.ex | 2 +- apps/fz_wall/lib/fz_wall/cli/live.ex | 94 ++++++++++++------- apps/fz_wall/mix.exs | 1 + mix.lock | 1 + 4 files changed, 65 insertions(+), 33 deletions(-) diff --git a/apps/fz_http/lib/fz_http_web/live/rule_live/rule_list_component.ex b/apps/fz_http/lib/fz_http_web/live/rule_live/rule_list_component.ex index d7c63f0b9..1eeeaafe2 100644 --- a/apps/fz_http/lib/fz_http_web/live/rule_live/rule_list_component.ex +++ b/apps/fz_http/lib/fz_http_web/live/rule_live/rule_list_component.ex @@ -42,7 +42,7 @@ defmodule FzHttpWeb.RuleLive.RuleListComponent do end end - defp action(id) do + def action(id) do case id do :allowlist -> :accept diff --git a/apps/fz_wall/lib/fz_wall/cli/live.ex b/apps/fz_wall/lib/fz_wall/cli/live.ex index d9bac8cbd..34b9c3689 100644 --- a/apps/fz_wall/lib/fz_wall/cli/live.ex +++ b/apps/fz_wall/lib/fz_wall/cli/live.ex @@ -17,68 +17,79 @@ defmodule FzWall.CLI.Live do "forward" is the Netfilter hook we want to tie into. """ def setup do - exec!("#{nft()} add table ip #{@table_name}") + # Start with a blank slate + teardown() - exec!( - "#{nft()} 'add chain ip #{@table_name} forward { type filter hook forward priority 0 ; }'" - ) + for protocol <- ["ip", "ip6"] do + exec!("#{nft()} add table #{protocol} #{@table_name}") - exec!("#{nft()} 'add rule ip #{@table_name} forward counter accept'") + exec!( + "#{nft()} 'add chain #{protocol} #{@table_name} forward { type filter hook forward priority 0 ; }'" + ) + + exec!("#{nft()} 'add rule #{protocol} #{@table_name} forward counter accept'") + end end @doc """ Flushes and removes the FireZone nftables table and base chain. """ def teardown do - exec!("#{nft()} delete table ip #{@table_name}") + for protocol <- ["ip", "ip6"] do + exec!("#{nft()} delete table #{protocol} #{@table_name}") + end end @doc """ Adds nftables rule. """ - def add_rule({dest, action}) do - exec!("#{nft()} 'add rule ip #{@table_name} forward ip daddr #{dest} #{action}'") + def add_rule({proto, dest, action}) do + exec!(""" + #{nft()} 'add rule #{proto} #{@table_name} forward\ + #{proto} daddr #{standardized_dest(dest)} #{action}' + """) + end + + @doc """ + List currently loaded rules. + """ + def list_rules do + exec!("#{nft()} -a list table ip firezone") + exec!("#{nft()} -a list table ip6 firezone") end @doc """ Deletes nftables rule. """ - def delete_rule_spec({dest, action} = rule_spec) do - case get_rule_handle("ip daddr #{dest} #{action}") do - {:ok, handle_num} -> - exec!("#{nft()} delete rule #{@table_name} forward handle #{handle_num}") + def delete_rule({proto, dest, action}) do + rule_str = "#{proto} daddr #{standardized_dest(dest)} #{action}" + rules = exec!("#{nft()} -a list table #{proto} #{@table_name}") - {:error, cmd_output} -> + case rule_handle_regex(~r/#{rule_str}.*# handle (?\d+)/, rules) do + nil -> raise(""" ###################################################### Could not get handle to delete rule! - Rule spec: #{rule_spec} + Rule spec: #{rule_str} - Current chain: - #{cmd_output} + Current rules: + #{rules} ###################################################### """) - end - end - def get_rule_handle(rule_str) do - cmd_output = exec!("#{nft()} list table #{@table_name}") - - case rule_handle_regex(~r/#{rule_str}.*# handle (?\d+)/, cmd_output) do [handle] -> - {:ok, handle} - - [] -> - {:error, cmd_output} + exec!("#{nft()} delete rule #{proto} #{@table_name} forward handle #{handle}") end end - defp rule_handle_regex(regex, cmd_output) do - Regex.run(regex, cmd_output, capture: :all_names) - end - - def restore(_rules) do - # XXX: Implement me + @doc """ + Restores rules. + """ + def restore(rules) do + # XXX: Priority? + for rule_spec <- rules do + add_rule(rule_spec) + end end def egress_address do @@ -102,6 +113,25 @@ defmodule FzWall.CLI.Live do end end + @doc """ + Standardized IP addresses and CIDR ranges so that we can + parse them out of the nftables rulesets. + """ + def standardized_dest(dest) do + if String.contains?(dest, "/") do + dest + |> InetCidr.parse() + |> InetCidr.to_string() + else + {:ok, addr} = dest |> String.to_charlist() |> :inet.parse_address() + :inet.ntoa(addr) |> List.to_string() + end + end + + defp rule_handle_regex(regex, rules) do + Regex.run(regex, rules, capture: :all_names) + end + defp egress_interface do case :os.type() do {:unix, :linux} -> diff --git a/apps/fz_wall/mix.exs b/apps/fz_wall/mix.exs index 70a98da3e..9e921397f 100644 --- a/apps/fz_wall/mix.exs +++ b/apps/fz_wall/mix.exs @@ -35,6 +35,7 @@ defmodule FzWall.MixProject do defp deps do [ {:fz_common, in_umbrella: true}, + {:inet_cidr, "~> 1.0.0"}, {:credo, "~> 1.4", only: [:dev, :test], runtime: false}, {:excoveralls, "~> 0.13", only: :test} ] diff --git a/mix.lock b/mix.lock index 21c3db937..af97bbd10 100644 --- a/mix.lock +++ b/mix.lock @@ -26,6 +26,7 @@ "hackney": {:hex, :hackney, "1.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},