commit 8efbdf9a01abb95c29b2239ff292016cd17d6857
Author: tr <tr@immerda.ch>
Date:   Sun Aug 30 01:05:14 2020 +0200

    Refactoring

diff --git a/files/config/puppet-filter.nft b/files/config/puppet-filter.nft
index 6f05797..e8bae6e 100644
--- a/files/config/puppet-filter.nft
+++ b/files/config/puppet-filter.nft
@@ -1,5 +1,5 @@
 table inet filter {
-  include "/etc/nftables/puppet/filter-chains-*.nft"
+  include "/etc/nftables/puppet/filter-chain-*.nft"
 
   # something we want for all
   chain global {
@@ -11,41 +11,4 @@ table inet filter {
     ip protocol icmp icmp type echo-request limit rate 4/second accept
     ip6 nexthdr ipv6-icmp icmpv6 type echo-request 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/manifests/chain.pp b/manifests/chain.pp
new file mode 100644
index 0000000..ea0e8db
--- /dev/null
+++ b/manifests/chain.pp
@@ -0,0 +1,41 @@
+# manage a chain
+define nftables::chain(
+  String[1]
+    $table = 'filter',
+  Pattern[/^[a-zA-Z0-9_]+$/]
+    $chain = $title,
+  Optional[Pattern[/^\d\d-[a-zA-Z0-9_]+$/]]
+    $inject = undef,
+){
+  $concat_name = "nftables-${table}-chain-${chain}"
+
+  concat{
+    $concat_name:
+      path           => "/etc/nftables/puppet/${table}-chain-${chain}.nft",
+      owner          => root,
+      group          => root,
+      mode           => '0640',
+      ensure_newline => true,
+      require        => Package['nftables'],
+      notify         => Service['nftables'],
+  }
+
+  concat::fragment{
+    default:
+      target => $concat_name;
+    "${concat_name}-header":
+      order   => '00',
+      content => "chain ${chain} {";
+    "${concat_name}-footer":
+      order   => '99',
+      content => '}';
+  }
+
+  if $inject {
+    $data = split($inject, '-')
+    nftables::rule{ "${data[1]}-jump_${chain}":
+      order   => $data[0],
+      content => "jump ${chain}",
+    }
+  }
+}
diff --git a/manifests/chain_file.pp b/manifests/chain_file.pp
deleted file mode 100644
index d527631..0000000
--- a/manifests/chain_file.pp
+++ /dev/null
@@ -1,29 +0,0 @@
-# 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/filter/chain.pp b/manifests/filter/chain.pp
deleted file mode 100644
index a7d26c0..0000000
--- a/manifests/filter/chain.pp
+++ /dev/null
@@ -1,19 +0,0 @@
-# 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]}\n",
-  }
-  nftables::chain_file{
-    "filter@${data[1]}":;
-  }
-}
diff --git a/manifests/init.pp b/manifests/init.pp
index a1428ae..7c47d70 100644
--- a/manifests/init.pp
+++ b/manifests/init.pp
@@ -40,14 +40,77 @@ class nftables (
       source => 'puppet:///modules/nftables/config/puppet-ip-nat.nft';
   }
 
-  nftables::filter::chain{
+  nftables::chain{
     [
-      'forward-default_fwd',
-      'output-default_out',
-      'input-default_in',
+      'INPUT',
+      'OUTPUT',
+      'FORWARD',
     ]:;
   }
 
+  nftables::chain{
+    'default_in':
+      inject => '10-INPUT';
+    'default_out':
+      inject => '10-OUTPUT';
+    'default_fwd':
+      inject => '10-FORWARD';
+  }
+
+  # filter-chain-INPUT
+  nftables::rule{
+    'INPUT-type':
+      order   => '01',
+      content => 'type filter hook input priority 0';
+    'INPUT-policy':
+      order   => '02',
+      content => 'policy drop';
+    'INPUT-lo':
+      order   => '03',
+      content => 'iifname lo accept';
+    'INPUT-jump_global':
+      order   => '04',
+      content => 'jump global';
+    'INPUT-log_rejected':
+      order   => '98',
+      content => 'log prefix "[nftables] INPUT Rejected: " flags all counter reject with icmpx type port-unreachable';
+  }
+
+  # filter-chain-OUTPUT
+  nftables::rule{
+    'OUTPUT-type':
+      order   => '01',
+      content => 'type filter hook output priority 0';
+    'OUTPUT-policy':
+      order   => '02',
+      content => 'policy drop';
+    'OUTPUT-lo':
+      order   => '03',
+      content => 'oifname lo accept';
+    'OUTPUT-jump_global':
+      order   => '04',
+      content => 'jump global';
+    'OUTPUT-log_rejected':
+      order   => '98',
+      content => 'log prefix "[nftables] OUTPUT Rejected: " flags all counter reject with icmpx type port-unreachable';
+  }
+
+  # filter-chain-FORWARD
+  nftables::rule{
+    'FORWARD-type':
+      order   => '01',
+      content => 'type filter hook forward priority 0';
+    'FORWARD-policy':
+      order   => '02',
+      content => 'policy drop';
+    'FORWARD-jump_global':
+      order   => '03',
+      content => 'jump global';
+    'FORWARD-log_rejected':
+      order   => '98',
+      content => 'log prefix "[nftables] FORWARD Rejected: " flags all counter reject with icmpx type port-unreachable';
+  }
+
   # basic ingoing rules
   if $in_ssh {
     include nftables::rules::ssh
diff --git a/manifests/filter/chain/rule.pp b/manifests/rule.pp
similarity index 51%
rename from manifests/filter/chain/rule.pp
rename to manifests/rule.pp
index 340b6e5..5b6f66f 100644
--- a/manifests/filter/chain/rule.pp
+++ b/manifests/rule.pp
@@ -1,32 +1,36 @@
-# manage a filter chain rule
+# manage a chain rule
 # Name should be:
 #   CHAIN_NAME-rulename
-define nftables::filter::chain::rule(
+define nftables::rule(
   Enum['present','absent']
     $ensure = 'present',
-  Pattern[/^[a-z0-9_]+\-[0-9a-z]+$/]
+  Pattern[/^[a-zA-Z0-9_]+-[a-zA-Z0-9_]+$/]
     $rulename = $title,
   Pattern[/^\d\d$/]
     $order = '50',
   Optional[String]
+    $table = 'filter',
+  Optional[String]
     $content = undef,
   Optional[Variant[String,Array[String,1]]]
     $source = undef,
 ){
+
   if $ensure == 'present' {
-    $data = split($rulename,'-')
+    $data = split($rulename, '-')
+
     concat::fragment{
-      "nftables-filter-chain-rule-${rulename}":
+      "nftables-${table}-chain-${data[0]}-rule-${data[1]}":
         order  => $order,
-        target => "nftables-chain-filter-${data[0]}",
+        target => "nftables-${table}-chain-${data[0]}",
     }
 
     if $content {
-      Concat::Fragment["nftables-filter-chain-rule-${rulename}"]{
+      Concat::Fragment["nftables-${table}-chain-${data[0]}-rule-${data[1]}"]{
         content => "  ${content}",
       }
     } else {
-      Concat::Fragment["nftables-filter-chain-rule-${rulename}"]{
+      Concat::Fragment["nftables-${table}-chain-${data[0]}-rule-${data[1]}"]{
         source => $source,
       }
     }
diff --git a/manifests/rules/icinga2.pp b/manifests/rules/icinga2.pp
index 8751acb..cbfc9c4 100644
--- a/manifests/rules/icinga2.pp
+++ b/manifests/rules/icinga2.pp
@@ -3,7 +3,7 @@ class nftables::rules::icinga2(
   Array[Integer,1]
     $ports = [5665],
 ) {
-  nftables::filter::chain::rule{
+  nftables::rule{
     'default_in-icinga2':
       content => "tcp dport {${join($ports,', ')}} accept",
   }
diff --git a/manifests/rules/out/chrony.pp b/manifests/rules/out/chrony.pp
index c59e897..a79568b 100644
--- a/manifests/rules/out/chrony.pp
+++ b/manifests/rules/out/chrony.pp
@@ -1,6 +1,6 @@
 # manage out chrony
 class nftables::rules::out::chrony {
-  nftables::filter::chain::rule{
+  nftables::rule{
     'default_out-chrony':
       content => 'udp dport 123 accept',
   }
diff --git a/manifests/rules/out/dhcp.pp b/manifests/rules/out/dhcp.pp
index 2fc2248..90193fd 100644
--- a/manifests/rules/out/dhcp.pp
+++ b/manifests/rules/out/dhcp.pp
@@ -1,6 +1,6 @@
 # manage out dhcp
 class nftables::rules::out::dhcp {
-  nftables::filter::chain::rule{
+  nftables::rule{
     'default_out-dhcpc':
       content => 'udp sport {67, 68} udp dport {67, 68} accept';
   }
diff --git a/manifests/rules/out/dns.pp b/manifests/rules/out/dns.pp
index a91e339..32c3600 100644
--- a/manifests/rules/out/dns.pp
+++ b/manifests/rules/out/dns.pp
@@ -6,34 +6,34 @@ class nftables::rules::out::dns (
   if $dns_server {
     any2array($dns_server).each |$index,$dns| {
 
-      nftables::filter::chain::rule{
+      nftables::rule{
         "default_out-dnsudp-${index}":
       }
       if $dns =~ /:/ {
-        Nftables::Filter::Chain::Rule["default_out-dnsudp-${index}"]{
+        Nftables::Rule["default_out-dnsudp-${index}"]{
           content => "ip6 daddr ${dns} udp dport 53 accept",
         }
       } else {
-        Nftables::Filter::Chain::Rule["default_out-dnsudp-${index}"]{
+        Nftables::Rule["default_out-dnsudp-${index}"]{
           content => "ip daddr ${dns} udp dport 53 accept",
         }
       }
 
-      nftables::filter::chain::rule{
+      nftables::rule{
         "default_out-dnstcp-${index}":
       }
       if $dns =~ /:/ {
-        Nftables::Filter::Chain::Rule["default_out-dnstcp-${index}"]{
+        Nftables::Rule["default_out-dnstcp-${index}"]{
           content => "ip6 daddr ${dns} tcp dport 53 accept",
         }
       } else {
-        Nftables::Filter::Chain::Rule["default_out-dnstcp-${index}"]{
+        Nftables::Rule["default_out-dnstcp-${index}"]{
           content => "ip daddr ${dns} tcp dport 53 accept",
         }
       }
     }
   } else {
-    nftables::filter::chain::rule{
+    nftables::rule{
       'default_out-dnsudp':
         content => 'udp dport 53 accept';
       'default_out-dnstcp':
diff --git a/manifests/rules/out/http.pp b/manifests/rules/out/http.pp
index 3d27728..656a2c6 100644
--- a/manifests/rules/out/http.pp
+++ b/manifests/rules/out/http.pp
@@ -1,6 +1,6 @@
 # manage out http
 class nftables::rules::out::http {
-  nftables::filter::chain::rule{
+  nftables::rule{
     'default_out-http':
       content => 'tcp dport 80 accept';
   }
diff --git a/manifests/rules/out/https.pp b/manifests/rules/out/https.pp
index 4f184ee..b5c2734 100644
--- a/manifests/rules/out/https.pp
+++ b/manifests/rules/out/https.pp
@@ -1,6 +1,6 @@
 # manage out https
 class nftables::rules::out::https {
-  nftables::filter::chain::rule{
+  nftables::rule{
     'default_out-https':
       content => 'tcp dport 443 accept';
   }
diff --git a/manifests/rules/out/puppet.pp b/manifests/rules/out/puppet.pp
index 14e19d1..c5342e3 100644
--- a/manifests/rules/out/puppet.pp
+++ b/manifests/rules/out/puppet.pp
@@ -6,15 +6,15 @@ class nftables::rules::out::puppet(
     $puppetserver_port = 8140,
 ) {
   any2array($puppetmaster).each |$index,$pm| {
-    nftables::filter::chain::rule{
+    nftables::rule{
       "default_out-puppet-${index}":
     }
     if $pm =~ /:/ {
-      Nftables::Filter::Chain::Rule["default_out-puppet-${index}"]{
+      nftables::rule["default_out-puppet-${index}"]{
         content => "ip6 daddr ${pm} tcp dport ${puppetserver_port} accept",
       }
     } else {
-      Nftables::Filter::Chain::Rule["default_out-puppet-${index}"]{
+      nftables::rule["default_out-puppet-${index}"]{
         content => "ip daddr ${pm} tcp dport ${puppetserver_port} accept",
       }
     }
diff --git a/manifests/rules/out/smtp.pp b/manifests/rules/out/smtp.pp
index 907c9d1..2988ef5 100644
--- a/manifests/rules/out/smtp.pp
+++ b/manifests/rules/out/smtp.pp
@@ -1,6 +1,6 @@
 # manage out smtp
 class nftables::rules::out::smtp {
-  nftables::filter::chain::rule{
+  nftables::rule{
     'default_out-smtp':
       content => 'tcp dport 25 accept',
   }
diff --git a/manifests/rules/out/ssh.pp b/manifests/rules/out/ssh.pp
index 850046e..e5676d5 100644
--- a/manifests/rules/out/ssh.pp
+++ b/manifests/rules/out/ssh.pp
@@ -1,6 +1,6 @@
 # manage out ssh
 class nftables::rules::out::ssh {
-  nftables::filter::chain::rule{
+  nftables::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
index 8c3d20b..2b5bc6f 100644
--- a/manifests/rules/out/ssh/remove.pp
+++ b/manifests/rules/out/ssh/remove.pp
@@ -1,6 +1,6 @@
 # disable outgoing ssh
 class nftables::rules::out::ssh::remove inherits nftables::rules::out::ssh {
-  Nftables::Filter::Chain::Rule['default_out-ssh']{
+  Nftables::Rule['default_out-ssh']{
     ensure => absent,
   }
 }
diff --git a/manifests/rules/out/tor.pp b/manifests/rules/out/tor.pp
index 1f23371..951f484 100644
--- a/manifests/rules/out/tor.pp
+++ b/manifests/rules/out/tor.pp
@@ -1,6 +1,6 @@
 # manage out tor
 class nftables::rules::out::tor {
-  nftables::filter::chain::rule{
+  nftables::rule{
     'default_out-tor':
       content => 'tcp dport 9001 accept',
   }
diff --git a/manifests/rules/out/wireguard.pp b/manifests/rules/out/wireguard.pp
index 0743a86..bc30bfb 100644
--- a/manifests/rules/out/wireguard.pp
+++ b/manifests/rules/out/wireguard.pp
@@ -3,7 +3,7 @@ class nftables::rules::out::wireguard (
   Array[Integer,1]
     $ports = [51820],
 ) {
-  nftables::filter::chain::rule{
+  nftables::rule{
     'default_out-wireguard':
       content => "udp dport {${join($ports,', ')}} accept",
   }
diff --git a/manifests/rules/puppet.pp b/manifests/rules/puppet.pp
index 93dba05..2829fa3 100644
--- a/manifests/rules/puppet.pp
+++ b/manifests/rules/puppet.pp
@@ -3,7 +3,7 @@ class nftables::rules::puppet(
   Array[Integer,1]
     $ports = [8140],
 ) {
-  nftables::filter::chain::rule{
+  nftables::rule{
     'default_in-puppet':
       content => "tcp dport {${join($ports,', ')}} accept",
   }
diff --git a/manifests/rules/ssh.pp b/manifests/rules/ssh.pp
index 5081688..6659e76 100644
--- a/manifests/rules/ssh.pp
+++ b/manifests/rules/ssh.pp
@@ -3,7 +3,7 @@ class nftables::rules::ssh(
   Array[Integer,1]
     $ports = [22],
 ) {
-  nftables::filter::chain::rule{
+  nftables::rule{
     'default_in-ssh':
       content => "tcp dport {${join($ports,', ')}} accept",
   }
diff --git a/manifests/rules/tor.pp b/manifests/rules/tor.pp
index 8b88ddd..0f03129 100644
--- a/manifests/rules/tor.pp
+++ b/manifests/rules/tor.pp
@@ -3,7 +3,7 @@ class nftables::rules::tor(
   Array[Integer,1]
     $ports = [9001],
 ) {
-  nftables::filter::chain::rule{
+  nftables::rule{
     'default_in-tor':
       content => "tcp dport {${join($ports,', ')}} accept",
   }
diff --git a/manifests/rules/wireguard.pp b/manifests/rules/wireguard.pp
index 9240da3..3881099 100644
--- a/manifests/rules/wireguard.pp
+++ b/manifests/rules/wireguard.pp
@@ -3,7 +3,7 @@ class nftables::rules::wireguard(
   Array[Integer,1]
     $ports = [51820],
 ) {
-  nftables::filter::chain::rule{
+  nftables::rule{
     'default_in-wireguard':
       content => "udp dport {${join($ports,', ')}} accept",
   }
diff --git a/spec/classes/filter_spec.rb b/spec/classes/filter_spec.rb
index 6ec89d3..b284a0a 100644
--- a/spec/classes/filter_spec.rb
+++ b/spec/classes/filter_spec.rb
@@ -17,124 +17,233 @@ describe 'nftables' do
       )}
 
       context 'chain input' do
-        it { is_expected.to contain_file('/etc/nftables/puppet/filter-input-chains-50-default_in.nft').with(
-          :ensure => 'file',
-          :owner  => 'root',
-          :group  => 'root',
-          :mode   => '0640',
+        it { is_expected.to contain_concat('nftables-filter-chain-INPUT').with(
+          :path           => '/etc/nftables/puppet/filter-chain-INPUT.nft',
+          :owner          => 'root',
+          :group          => 'root',
+          :mode           => '0640',
+          :ensure_newline => true,
         )}
-        it { is_expected.to contain_file('/etc/nftables/puppet/filter-input-chains-50-default_in.nft').with_content(
-          /^jump default_in$/,
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-INPUT-header').with(
+          :target  => 'nftables-filter-chain-INPUT',
+          :content => /^chain INPUT {$/,
+          :order   => '00',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-INPUT-rule-type').with(
+          :target  => 'nftables-filter-chain-INPUT',
+          :content => /^  type filter hook input priority 0$/,
+          :order   => '01',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-INPUT-rule-policy').with(
+          :target  => 'nftables-filter-chain-INPUT',
+          :content => /^  policy drop$/,
+          :order   => '02',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-INPUT-rule-lo').with(
+          :target  => 'nftables-filter-chain-INPUT',
+          :content => /^  iifname lo accept$/,
+          :order   => '03',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-INPUT-rule-jump_global').with(
+          :target  => 'nftables-filter-chain-INPUT',
+          :content => /^  jump global$/,
+          :order   => '04',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-INPUT-rule-jump_default_in').with(
+          :target  => 'nftables-filter-chain-INPUT',
+          :content => /^  jump default_in$/,
+          :order   => '10',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-INPUT-rule-log_rejected').with(
+          :target  => 'nftables-filter-chain-INPUT',
+          :content => /^  log prefix \"\[nftables\] INPUT Rejected: \" flags all counter reject with icmpx type port-unreachable$/,
+          :order   => '98',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-INPUT-footer').with(
+          :target  => 'nftables-filter-chain-INPUT',
+          :content => /^}$/,
+          :order   => '99',
         )}
 
-        it { is_expected.to contain_concat('nftables-chain-filter-default_in').with(
-          :path           => '/etc/nftables/puppet/filter-chains-default_in.nft',
+        it { is_expected.to contain_concat('nftables-filter-chain-default_in').with(
+          :path           => '/etc/nftables/puppet/filter-chain-default_in.nft',
           :owner          => 'root',
           :group          => 'root',
-          :mode           => '0644',
+          :mode           => '0640',
           :ensure_newline => true,
         )}
-        it { is_expected.to contain_concat__fragment('filter@default_in-header').with(
-          :target  => 'nftables-chain-filter-default_in',
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-default_in-header').with(
+          :target  => 'nftables-filter-chain-default_in',
           :content => /^chain default_in {$/,
           :order   => '00',
         )}
-        it { is_expected.to contain_concat__fragment('filter@default_in-footer').with(
-          :target  => 'nftables-chain-filter-default_in',
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-default_in-footer').with(
+          :target  => 'nftables-filter-chain-default_in',
           :content => /^}$/,
           :order   => '99',
         )}
-        it { is_expected.to contain_concat__fragment('nftables-filter-chain-rule-default_in-ssh').with(
-          :target  => 'nftables-chain-filter-default_in',
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-default_in-rule-ssh').with(
+          :target  => 'nftables-filter-chain-default_in',
           :content => /^  tcp dport \{22\} accept$/,
           :order   => '50',
         )}
       end
 
-      context 'chain forward' do
-        it { is_expected.to contain_file('/etc/nftables/puppet/filter-forward-chains-50-default_fwd.nft').with(
-          :ensure => 'file',
-          :owner  => 'root',
-          :group  => 'root',
-          :mode   => '0640',
-        )}
-        it { is_expected.to contain_file('/etc/nftables/puppet/filter-forward-chains-50-default_fwd.nft').with_content(
-          /^jump default_fwd$/,
-        )}
-
-        it { is_expected.to contain_concat('nftables-chain-filter-default_fwd').with(
-          :path           => '/etc/nftables/puppet/filter-chains-default_fwd.nft',
+      context 'chain output' do
+        it { is_expected.to contain_concat('nftables-filter-chain-OUTPUT').with(
+          :path           => '/etc/nftables/puppet/filter-chain-OUTPUT.nft',
           :owner          => 'root',
           :group          => 'root',
-          :mode           => '0644',
+          :mode           => '0640',
           :ensure_newline => true,
         )}
-        it { is_expected.to contain_concat__fragment('filter@default_fwd-header').with(
-          :target  => 'nftables-chain-filter-default_fwd',
-          :content => /^chain default_fwd {$/,
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-OUTPUT-header').with(
+          :target  => 'nftables-filter-chain-OUTPUT',
+          :content => /^chain OUTPUT {$/,
           :order   => '00',
         )}
-        it { is_expected.to contain_concat__fragment('filter@default_fwd-footer').with(
-          :target  => 'nftables-chain-filter-default_fwd',
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-OUTPUT-rule-type').with(
+          :target  => 'nftables-filter-chain-OUTPUT',
+          :content => /^  type filter hook output priority 0$/,
+          :order   => '01',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-OUTPUT-rule-policy').with(
+          :target  => 'nftables-filter-chain-OUTPUT',
+          :content => /^  policy drop$/,
+          :order   => '02',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-OUTPUT-rule-lo').with(
+          :target  => 'nftables-filter-chain-OUTPUT',
+          :content => /^  oifname lo accept$/,
+          :order   => '03',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-OUTPUT-rule-jump_global').with(
+          :target  => 'nftables-filter-chain-OUTPUT',
+          :content => /^  jump global$/,
+          :order   => '04',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-OUTPUT-rule-jump_default_out').with(
+          :target  => 'nftables-filter-chain-OUTPUT',
+          :content => /^  jump default_out$/,
+          :order   => '10',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-OUTPUT-rule-log_rejected').with(
+          :target  => 'nftables-filter-chain-OUTPUT',
+          :content => /^  log prefix \"\[nftables\] OUTPUT Rejected: \" flags all counter reject with icmpx type port-unreachable$/,
+          :order   => '98',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-OUTPUT-footer').with(
+          :target  => 'nftables-filter-chain-OUTPUT',
           :content => /^}$/,
           :order   => '99',
         )}
-      end
-
-      context 'chain output' do
-        it { is_expected.to contain_file('/etc/nftables/puppet/filter-output-chains-50-default_out.nft').with(
-          :ensure => 'file',
-          :owner  => 'root',
-          :group  => 'root',
-          :mode   => '0640',
-        )}
-        it { is_expected.to contain_file('/etc/nftables/puppet/filter-output-chains-50-default_out.nft').with_content(
-          /^jump default_out$/,
-        )}
 
-        it { is_expected.to contain_concat('nftables-chain-filter-default_out').with(
-          :path           => '/etc/nftables/puppet/filter-chains-default_out.nft',
+        it { is_expected.to contain_concat('nftables-filter-chain-default_out').with(
+          :path           => '/etc/nftables/puppet/filter-chain-default_out.nft',
           :owner          => 'root',
           :group          => 'root',
-          :mode           => '0644',
+          :mode           => '0640',
           :ensure_newline => true,
         )}
-        it { is_expected.to contain_concat__fragment('filter@default_out-header').with(
-          :target  => 'nftables-chain-filter-default_out',
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-default_out-header').with(
+          :target  => 'nftables-filter-chain-default_out',
           :content => /^chain default_out {$/,
           :order   => '00',
         )}
-        it { is_expected.to contain_concat__fragment('filter@default_out-footer').with(
-          :target  => 'nftables-chain-filter-default_out',
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-default_out-footer').with(
+          :target  => 'nftables-filter-chain-default_out',
           :content => /^}$/,
           :order   => '99',
         )}
-        it { is_expected.to contain_concat__fragment('nftables-filter-chain-rule-default_out-chrony').with(
-          :target  => 'nftables-chain-filter-default_out',
-          :content => /^  udp dport 123 accept$/,
-          :order   => '50',
-        )}
-        it { is_expected.to contain_concat__fragment('nftables-filter-chain-rule-default_out-dnsudp').with(
-          :target  => 'nftables-chain-filter-default_out',
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-default_out-rule-dnsudp').with(
+          :target  => 'nftables-filter-chain-default_out',
           :content => /^  udp dport 53 accept$/,
           :order   => '50',
         )}
-        it { is_expected.to contain_concat__fragment('nftables-filter-chain-rule-default_out-dnstcp').with(
-          :target  => 'nftables-chain-filter-default_out',
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-default_out-rule-dnstcp').with(
+          :target  => 'nftables-filter-chain-default_out',
           :content => /^  tcp dport 53 accept$/,
           :order   => '50',
         )}
-        it { is_expected.to contain_concat__fragment('nftables-filter-chain-rule-default_out-http').with(
-          :target  => 'nftables-chain-filter-default_out',
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-default_out-rule-chrony').with(
+          :target  => 'nftables-filter-chain-default_out',
+          :content => /^  udp dport 123 accept$/,
+          :order   => '50',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-default_out-rule-http').with(
+          :target  => 'nftables-filter-chain-default_out',
           :content => /^  tcp dport 80 accept$/,
           :order   => '50',
         )}
-        it { is_expected.to contain_concat__fragment('nftables-filter-chain-rule-default_out-https').with(
-          :target  => 'nftables-chain-filter-default_out',
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-default_out-rule-https').with(
+          :target  => 'nftables-filter-chain-default_out',
           :content => /^  tcp dport 443 accept$/,
           :order   => '50',
         )}
       end
+
+      context 'chain forward' do
+        it { is_expected.to contain_concat('nftables-filter-chain-FORWARD').with(
+          :path           => '/etc/nftables/puppet/filter-chain-FORWARD.nft',
+          :owner          => 'root',
+          :group          => 'root',
+          :mode           => '0640',
+          :ensure_newline => true,
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-FORWARD-header').with(
+          :target  => 'nftables-filter-chain-FORWARD',
+          :content => /^chain FORWARD {$/,
+          :order   => '00',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-FORWARD-rule-type').with(
+          :target  => 'nftables-filter-chain-FORWARD',
+          :content => /^  type filter hook forward priority 0$/,
+          :order   => '01',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-FORWARD-rule-policy').with(
+          :target  => 'nftables-filter-chain-FORWARD',
+          :content => /^  policy drop$/,
+          :order   => '02',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-FORWARD-rule-jump_global').with(
+          :target  => 'nftables-filter-chain-FORWARD',
+          :content => /^  jump global$/,
+          :order   => '03',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-FORWARD-rule-jump_default_fwd').with(
+          :target  => 'nftables-filter-chain-FORWARD',
+          :content => /^  jump default_fwd$/,
+          :order   => '10',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-FORWARD-rule-log_rejected').with(
+          :target  => 'nftables-filter-chain-FORWARD',
+          :content => /^  log prefix \"\[nftables\] FORWARD Rejected: \" flags all counter reject with icmpx type port-unreachable$/,
+          :order   => '98',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-FORWARD-footer').with(
+          :target  => 'nftables-filter-chain-FORWARD',
+          :content => /^}$/,
+          :order   => '99',
+        )}
+
+        it { is_expected.to contain_concat('nftables-filter-chain-default_fwd').with(
+          :path           => '/etc/nftables/puppet/filter-chain-default_fwd.nft',
+          :owner          => 'root',
+          :group          => 'root',
+          :mode           => '0640',
+          :ensure_newline => true,
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-default_fwd-header').with(
+          :target  => 'nftables-filter-chain-default_fwd',
+          :content => /^chain default_fwd {$/,
+          :order   => '00',
+        )}
+        it { is_expected.to contain_concat__fragment('nftables-filter-chain-default_fwd-footer').with(
+          :target  => 'nftables-filter-chain-default_fwd',
+          :content => /^}$/,
+          :order   => '99',
+        )}
+      end
     end
   end
 end
