commit 7b9d6ffc726f8d33668bffce41384cc2375efb4f
Author: Nacho Barrientos <nacho.barrientos@cern.ch>
Date:   Mon May 31 10:06:29 2021 +0200

    Allow creating a totally empty firewall
    
    By setting `nftables::inet_filter` and `nftables::nat` to `false`
    users can now start off from a totally empty firewall and add the
    tables, chains and rules they'd like.
    
    The default skeleton for inet-filter, ip-nat and ip6-nat is kept
    enabled by default.
    
    Fixes #95.

diff --git a/README.md b/README.md
index 6827921..27bde5d 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,13 @@ for all masterchains. This chain is empty by default.
 INPUT and OUTPUT to the loopback device is allowed by
 default, though you could restrict it later.
 
+On the other hand, if you don't want any of the default tables, chains
+and rules created by the module, you can set `nftables::inet_filter`
+and/or `nftables::nat` to `false` and build your whole nftables
+configuration from scratch by using the building blocks provided by
+this module. Looking at `nftables::inet_filter` for inspiration might
+be a good idea.
+
 ## Rules Validation
 
 Initially puppet deploys all configuration to
diff --git a/REFERENCE.md b/REFERENCE.md
index a9ecf6d..9f2366e 100644
--- a/REFERENCE.md
+++ b/REFERENCE.md
@@ -131,6 +131,7 @@ The following parameters are available in the `nftables` class:
 * [`out_icmp`](#out_icmp)
 * [`in_ssh`](#in_ssh)
 * [`in_icmp`](#in_icmp)
+* [`inet_filter`](#inet_filter)
 * [`nat`](#nat)
 * [`sets`](#sets)
 * [`log_prefix`](#log_prefix)
@@ -208,6 +209,14 @@ Allow inbound ICMPv4/v6 traffic.
 
 Default value: ``true``
 
+##### <a name="inet_filter"></a>`inet_filter`
+
+Data type: `Boolean`
+
+Add default tables, chains and rules to process traffic.
+
+Default value: ``true``
+
 ##### <a name="nat"></a>`nat`
 
 Data type: `Boolean`
diff --git a/manifests/init.pp b/manifests/init.pp
index 151b79b..a1a1d99 100644
--- a/manifests/init.pp
+++ b/manifests/init.pp
@@ -37,6 +37,9 @@
 # @param in_icmp
 #   Allow inbound ICMPv4/v6 traffic.
 #
+# @param inet_filter
+#   Add default tables, chains and rules to process traffic.
+#
 # @param nat
 #   Add default tables and chains to process NAT traffic.
 #
@@ -91,6 +94,7 @@ class nftables (
   Boolean $out_all = false,
   Boolean $in_out_conntrack = true,
   Boolean $fwd_conntrack = false,
+  Boolean $inet_filter = true,
   Boolean $nat = true,
   Hash $rules = {},
   Hash $sets = {},
@@ -120,7 +124,12 @@ class nftables (
       recurse => true;
     '/etc/nftables/puppet-preflight.nft':
       ensure  => file,
-      content => epp('nftables/config/puppet.nft.epp', { 'nat' => $nat, 'noflush' => $noflush_tables });
+      content => epp('nftables/config/puppet.nft.epp', {
+          'inet_filter' => $inet_filter,
+          'nat'         => $nat,
+          'noflush'     => $noflush_tables
+        }
+      );
   } ~> exec {
     'nft validate':
       refreshonly => true,
@@ -132,7 +141,12 @@ class nftables (
       mode  => '0640';
     '/etc/nftables/puppet.nft':
       ensure  => file,
-      content => epp('nftables/config/puppet.nft.epp', { 'nat' => $nat, 'noflush' => $noflush_tables });
+      content => epp('nftables/config/puppet.nft.epp', {
+          'inet_filter' => $inet_filter,
+          'nat'         => $nat,
+          'noflush'     => $noflush_tables
+        }
+      );
     '/etc/nftables/puppet':
       ensure  => directory,
       mode    => '0750',
@@ -160,7 +174,10 @@ class nftables (
     enable => $firewalld_enable,
   }
 
-  include nftables::inet_filter
+  if $inet_filter {
+    include nftables::inet_filter
+  }
+
   if $nat {
     include nftables::ip_nat
   }
diff --git a/spec/acceptance/default_spec.rb b/spec/acceptance/default_spec.rb
index dbe4631..b16eb4c 100644
--- a/spec/acceptance/default_spec.rb
+++ b/spec/acceptance/default_spec.rb
@@ -81,4 +81,34 @@ describe 'nftables class' do
       it { is_expected.to be_enabled }
     end
   end
+  context 'with totally empty firewall' do
+    it 'no rules validate okay' do
+      pp = <<-EOS
+      class{'nftables':
+        firewalld_enable => false,
+        inet_filter => false,
+        nat => false,
+      }
+      # nftables cannot be started in docker so replace service with a validation only.
+      systemd::dropin_file{"zzz_docker_nft.conf":
+        ensure  => present,
+        unit    => "nftables.service",
+        content => [
+          "[Service]",
+          "ExecStart=",
+          "ExecStart=/sbin/nft -c -I /etc/nftables/puppet -f /etc/sysconfig/nftables.conf",
+          "ExecReload=",
+          "ExecReload=/sbin/nft -c -I /etc/nftables/puppet -f /etc/sysconfig/nftables.conf",
+          "",
+          ].join("\n"),
+        notify  => Service["nftables"],
+      }
+      EOS
+      apply_manifest(pp, catch_failures: true)
+    end
+    describe service('nftables') do
+      it { is_expected.to be_running }
+      it { is_expected.to be_enabled }
+    end
+  end
 end
diff --git a/spec/classes/nftables_spec.rb b/spec/classes/nftables_spec.rb
index 186fa4e..84e2e2d 100644
--- a/spec/classes/nftables_spec.rb
+++ b/spec/classes/nftables_spec.rb
@@ -83,6 +83,8 @@ describe 'nftables' do
           enable: 'mask',
         )
       }
+      it { is_expected.to contain_class('nftables::inet_filter') }
+      it { is_expected.to contain_class('nftables::ip_nat') }
       it { is_expected.to contain_class('nftables::rules::out::http') }
       it { is_expected.to contain_class('nftables::rules::out::https') }
       it { is_expected.to contain_class('nftables::rules::out::dns') }
@@ -174,6 +176,33 @@ describe 'nftables' do
         }
       end
 
+      context 'with no default filtering rules' do
+        let(:params) do
+          {
+            'inet_filter' => false,
+          }
+        end
+
+        it { is_expected.to contain_class('nftables::ip_nat') }
+        it { is_expected.not_to contain_class('nftables::inet_filter') }
+      end
+
+      context 'with no default tables, chains or rules' do
+        let(:params) do
+          {
+            'inet_filter' => false,
+            'nat' => false,
+          }
+        end
+
+        it { is_expected.not_to contain_class('nftables::ip_nat') }
+        it { is_expected.not_to contain_class('nftables::inet_filter') }
+        it { is_expected.to have_nftables__config_resource_count(0) }
+        it { is_expected.to have_nftables__chain_resource_count(0) }
+        it { is_expected.to have_nftables__rule_resource_count(0) }
+        it { is_expected.to have_nftables__set_resource_count(0) }
+      end
+
       context 'with with noflush_tables parameter' do
         let(:params) do
           {
diff --git a/templates/config/puppet.nft.epp b/templates/config/puppet.nft.epp
index 512217b..9fb9a9a 100644
--- a/templates/config/puppet.nft.epp
+++ b/templates/config/puppet.nft.epp
@@ -1,4 +1,5 @@
 <%- |
+  Boolean $inet_filter,
   Boolean $nat,
   Optional[Array[String[1],1]] $noflush = undef,
 |-%>
@@ -21,7 +22,9 @@ if $noflush and $facts['nftables'] and $facts['nftables']['tables'] {
 <%= $_flush_command.join("\n") %>
 
 include "custom-*.nft"
+<% if $inet_filter { -%>
 include "inet-filter.nft"
+<% } -%>
 <% if $nat { -%>
 include "ip-nat.nft"
 include "ip6-nat.nft"
