commit 0ba57c66a35ed4e9b570d8a6315a33a1c4ba3181
Author: mh <mh@immerda.ch>
Date:   Sat Aug 29 11:50:46 2020 +0200

    initial release

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..51704e4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,81 @@
+# nftables puppet module
+
+This module manages an opinionated nftables configuration
+
+By default it sets up a firewall that drops every incoming
+and outgoing connection.
+
+It only allows outgoing dns,ntp and web traffic.
+
+The config file has a inet filter and a ip nat table setup.
+
+Additionally, the module comes with a basic infrastrcuture
+to hook into different places.
+
+## nftables config
+
+The main configuration file loaded by the nftables service
+will be `files/config/puppet.nft`, all other files created
+by that module go into `files/config/puppet` and will also
+be purged if not managed anymore.
+
+The main configuration file includes dedicated files for
+the filter and nat tables, as well as processes any
+`custom-*.nft` files before hand.
+
+The filter and NAT tables both have all the master chains
+(INPUT,OUTPUT,FORWARD) configured, to which you can hook
+in your own chains that can contain specific rules.
+
+All filter masterchains drop by default.
+By default we have a set of default_MASTERCHAIN chains
+configured to which you can easily add your custom rules.
+
+For specific needs you can add your own chain.
+
+There is a global chain, that defines the default behavior
+for all masterchains.
+
+INPUT and OUTPUT to the loopback device is allowed by default,
+though you could restrict it later.
+
+### nftables::config
+
+Manages a raw file in `/etc/nftables/puppet/${name}.nft`
+
+Use this for any custom table files.
+
+## nftables::chain_file
+
+Prepares a chain file as a `concat` file to which you will be
+able to add dedicated rules through `concat::fragments`.
+
+The name must follow the pattern `TABLE@chain_name`, e.g.
+`filter@my_chain`. This will a) prepare a snippet defining
+the chain, that will be included in the filter table.
+
+This define is more intended as a helper to setup chains
+that will be used for the different tables, through their
+own defines. See `nftables::filter::chain` as an example.
+
+## nftables::filter::chain
+
+This setups a chain for the filter table. You will be able
+to add rules to that chain by using `nftables::filter::chain::rule`.
+
+The name must follow the pattern: `MASTERCHAIN-new_chain_name`, which
+defines to which masterchain that custom chain should be hooked into.
+
+new_chain_name must be unique for all chains.
+
+There is automatically a `jump` instruction added to the masterchain,
+with the order preference.
+
+## nftables::filter::chain::rule
+
+A simple way to add rules to your custom chain. The name must be:
+`CHAIN_NAME-rulename`, where CHAIN_NAME refers to your chain and
+an arbitrary name for your rule.
+The rule will be a `concat::fragment` to the chain `concat`.
+
+You can define the order by using the `order` param.
diff --git a/files/config/puppet-filter.nft b/files/config/puppet-filter.nft
new file mode 100644
index 0000000..9383e0c
--- /dev/null
+++ b/files/config/puppet-filter.nft
@@ -0,0 +1,50 @@
+table inet filter {
+  include "/etc/nftables/puppet/filter-chains-*.nft"
+
+  # something we want for all
+  chain global {
+    ct state established,related accept
+    ct state invalid drop
+
+    ip protocol icmp limit rate 4/second accept
+    ip6 nexthdr ipv6-icmp limit rate 4/second accept
+    ip protocol igmp limit rate 4/second accept
+  }
+
+  chain INPUT {
+    type filter hook input priority 0
+    policy drop
+
+    jump global
+
+    iifname lo accept
+
+    include "/etc/nftables/puppet/filter-input-chains-*.nft"
+
+    log prefix "[nftables] INPUT Rejected: " flags all counter reject with icmpx type port-unreachable
+  }
+
+  chain FORWARD {
+    type filter hook forward priority 0
+    policy drop
+
+    jump global
+
+    include "/etc/nftables/puppet/filter-forward-chains-*.nft"
+
+    log prefix "[nftables] FORWARD Rejected: " flags all counter reject with icmpx type port-unreachable
+  }
+
+  chain OUTPUT {
+    type filter hook output priority 0
+    policy drop
+
+    jump global
+
+    oifname lo accept
+
+    include "/etc/nftables/puppet/filter-output-chains-*.nft"
+
+    log prefix "[nftables] OUTPUT Rejected: " flags all counter reject with icmpx type port-unreachable
+  }
+}
diff --git a/files/config/puppet-nat.nft b/files/config/puppet-nat.nft
new file mode 100644
index 0000000..b06cdeb
--- /dev/null
+++ b/files/config/puppet-nat.nft
@@ -0,0 +1,29 @@
+table nat {
+  include "/etc/nftables/puppet/nat-chains-*.nft"
+
+  chain PREROUTING {
+    type nat hook prerouting priority 0
+    policy accept
+
+    include "/etc/nftables/puppet/nat-chain-prerouting-*.nft"
+  }
+  chain INPUT {
+    type nat hook input priority 100
+    policy accept
+
+    include "/etc/nftables/puppet/nat-chain-input-*.nft"
+  }
+  chain OUTPUT            {
+    type nat hook output priority 0
+    policy accept
+
+    include "/etc/nftables/puppet/nat-chain-output-*.nft"
+  }
+
+  chain POSTROUTING {
+    type nat hook postrouting priority 100
+    policy accept
+
+    include "/etc/nftables/puppet/nat-chain-postrouting-*.nft"
+  }
+}
diff --git a/files/config/puppet.nft b/files/config/puppet.nft
new file mode 100644
index 0000000..87d8e0f
--- /dev/null
+++ b/files/config/puppet.nft
@@ -0,0 +1,9 @@
+# drop any existing nftables ruleset
+flush ruleset
+
+include "/etc/nftables/puppet/custom-*.nft"
+
+include "/etc/nftables/puppet/filter.nft"
+
+include "/etc/nftables/puppet/nat.nft"
+
diff --git a/manifests/chain_file.pp b/manifests/chain_file.pp
new file mode 100644
index 0000000..d527631
--- /dev/null
+++ b/manifests/chain_file.pp
@@ -0,0 +1,29 @@
+# manage a chain file
+# chain must be:
+#   TABLE@chain_name
+define nftables::chain_file(
+  Pattern[/^[a-z0-9]+@[a-z0-9_]+$/] $chain = $title,
+){
+  $data = split($chain,'@')
+  $concat_name = "nftables-chain-${data[0]}-${data[1]}"
+  concat{
+    $concat_name:
+      path           => "/etc/nftables/puppet/${data[0]}-chains-${data[1]}.nft",
+      owner          => root,
+      group          => root,
+      mode           => '0644',
+      ensure_newline => true,
+      require        => Package['nftables'],
+      notify         => Service['nftables'],
+  }
+  concat::fragment{
+    default:
+      target => $concat_name;
+    "${chain}-header":
+      order   => '00',
+      content => "chain ${data[1]} {";
+    "${chain}-footer":
+      order   => '99',
+      content => '}';
+  }
+}
diff --git a/manifests/config.pp b/manifests/config.pp
new file mode 100644
index 0000000..543bdf0
--- /dev/null
+++ b/manifests/config.pp
@@ -0,0 +1,24 @@
+# manage a config snippet
+define nftables::config(
+  Optional[String]
+    $content = undef,
+  Optional[Variant[String,Array[String,1]]]
+    $source = undef,
+){
+  Package['nftables'] -> file{
+    "/etc/nftables/puppet/${name}.nft":
+      owner => root,
+      group => root,
+      mode  => '0640',
+  } ~> Service['nftables']
+
+  if $source {
+    File["/etc/nftables/puppet/${name}.nft"]{
+      source => $source,
+    }
+  } else {
+    File["/etc/nftables/puppet/${name}.nft"]{
+      content => $content,
+    }
+  }
+}
diff --git a/manifests/filter/chain.pp b/manifests/filter/chain.pp
new file mode 100644
index 0000000..7fc5b9e
--- /dev/null
+++ b/manifests/filter/chain.pp
@@ -0,0 +1,19 @@
+# register a filter chain
+# Name should match the following pattern:
+#
+#  MASTERCHAIN-new_chain_name
+define nftables::filter::chain(
+  Pattern[/^[a-z0-9]+\-[a-z0-9_]+$/]
+    $chain_name = $title,
+  Pattern[/^\d{2}$/]
+    $order      = '50',
+){
+  $data = split($chain_name,'-')
+  nftables::config{
+    "filter-${data[0]}-chains-${order}-${data[1]}":
+      content => "jump ${data[1]}",
+  }
+  nftables::chain_file{
+    "filter@${data[1]}":;
+  }
+}
diff --git a/manifests/filter/chain/rule.pp b/manifests/filter/chain/rule.pp
new file mode 100644
index 0000000..a641eb7
--- /dev/null
+++ b/manifests/filter/chain/rule.pp
@@ -0,0 +1,34 @@
+# manage a filter chain rule
+# Name should be:
+#   TABLE_NAME-rulename
+define nftables::filter::chain::rule(
+  Enum['present','absent']
+    $ensure = 'present',
+  Pattern[/^[a-z0-9_]+\-[0-9a-z]+$/]
+    $rulename = $title,
+  Pattern[/^\d\d$/]
+    $order = '50',
+  Optional[String]
+    $content = undef,
+  Optional[Variant[String,Array[String,1]]]
+    $source = undef,
+){
+  if $ensure == 'present' {
+    $data = split($rulename,'-')
+    concat::fragment{
+      "nftables-filter-chain-rule-${rulename}":
+        order   => $order,
+        target => "nftables-chain-filter-${data[0]}",
+    }
+
+    if $content {
+      Concat::Fragment["nftables-filter-chain-rule-${rulename}"]{
+        content => "  ${content}",
+      }
+    } else {
+      Concat::Fragment["nftables-filter-chain-rule-${rulename}"]{
+        source => $source,
+      }
+    }
+  }
+}
diff --git a/manifests/init.pp b/manifests/init.pp
new file mode 100644
index 0000000..1201f89
--- /dev/null
+++ b/manifests/init.pp
@@ -0,0 +1,50 @@
+# manage nftables
+class nftables {
+  package{'nftables':
+    ensure => installed,
+  } -> file_line{
+    'enable_nftables':
+      line   => 'include "/etc/nftables/puppet.nft"',
+      path   => '/etc/sysconfig/nftables.conf',
+      notify => Service['nftables'],
+  } -> file{
+    default:
+      owner  => 'root',
+      group  => 'root',
+      mode   => '0640';
+    '/etc/nftables/puppet.nft':
+      source => 'puppet:///modules/nftables/config/puppet.nft';
+    '/etc/nftables/puppet':
+      ensure  => directory,
+      purge   => true,
+      force   => true,
+      recurse => true;
+  } ~> service{'nftables':
+    ensure    => running,
+    enable    => true,
+  }
+
+  nftables::config{
+    'filter':
+      source => 'puppet:///modules/nftables/config/puppet-filter.nft';
+    'nat':
+      source => 'puppet:///modules/nftables/config/puppet-nat.nft';
+  }
+
+  nftables::filter::chain{
+    [
+      'forward-default_fwd',
+      'output-default_out',
+      'input-default_in',
+    ]:;
+  }
+  # basic outgoing rules
+  nftables::filter::chain::rule{
+    'default_out-dnsudp':
+      content => 'udp dport 53 accept';
+    'default_out-dnstcp':
+      content => 'tcp dport 53 accept';
+    'default_out-web':
+      content => 'tcp dport {80, 443} accept';
+  }
+}
diff --git a/manifests/rules/out/chrony.pp b/manifests/rules/out/chrony.pp
new file mode 100644
index 0000000..c59e897
--- /dev/null
+++ b/manifests/rules/out/chrony.pp
@@ -0,0 +1,7 @@
+# manage out chrony
+class nftables::rules::out::chrony {
+  nftables::filter::chain::rule{
+    'default_out-chrony':
+      content => 'udp dport 123 accept',
+  }
+}
diff --git a/manifests/rules/out/smtp.pp b/manifests/rules/out/smtp.pp
new file mode 100644
index 0000000..907c9d1
--- /dev/null
+++ b/manifests/rules/out/smtp.pp
@@ -0,0 +1,7 @@
+# manage out smtp
+class nftables::rules::out::smtp {
+  nftables::filter::chain::rule{
+    'default_out-smtp':
+      content => 'tcp dport 25 accept',
+  }
+}
diff --git a/manifests/rules/out/ssh.pp b/manifests/rules/out/ssh.pp
new file mode 100644
index 0000000..850046e
--- /dev/null
+++ b/manifests/rules/out/ssh.pp
@@ -0,0 +1,7 @@
+# manage out ssh
+class nftables::rules::out::ssh {
+  nftables::filter::chain::rule{
+    'default_out-ssh':
+      content => 'tcp dport 22 accept',
+  }
+}
diff --git a/manifests/rules/out/ssh/remove.pp b/manifests/rules/out/ssh/remove.pp
new file mode 100644
index 0000000..8c3d20b
--- /dev/null
+++ b/manifests/rules/out/ssh/remove.pp
@@ -0,0 +1,6 @@
+# disable outgoing ssh
+class nftables::rules::out::ssh::remove inherits nftables::rules::out::ssh {
+  Nftables::Filter::Chain::Rule['default_out-ssh']{
+    ensure => absent,
+  }
+}
diff --git a/manifests/rules/ssh.pp b/manifests/rules/ssh.pp
new file mode 100644
index 0000000..5081688
--- /dev/null
+++ b/manifests/rules/ssh.pp
@@ -0,0 +1,10 @@
+# manage in ssh
+class nftables::rules::ssh(
+  Array[Integer,1]
+    $ports = [22],
+) {
+  nftables::filter::chain::rule{
+    'default_in-ssh':
+      content => "tcp dport {${join($ports,', ')}} accept",
+  }
+}
