commit baad986efb158ad59c13177a4b787effcba1a7af
Author: Vadym Chepkov <vchepkov@gmail.com>
Date:   Sun Oct 22 17:29:00 2023 -0400

    add ftp helper
    
    This adds ability to enable a connection tracker helper and provides typical ftp rules
    
    Co-authored-by: Vadym Chepkov <vchepkov@gmail.com>
    Co-authored-by: Yury Bushmelev <jay4mail@gmail.com>

diff --git a/REFERENCE.md b/REFERENCE.md
index 600fbea..534c3f1 100644
--- a/REFERENCE.md
+++ b/REFERENCE.md
@@ -20,6 +20,7 @@ Enable this option to support Ceph's Monitor Daemon.
 * [`nftables::rules::dhcpv6_client`](#nftables--rules--dhcpv6_client): allow DHCPv6 requests in to a host
 * [`nftables::rules::dns`](#nftables--rules--dns): manage in dns
 * [`nftables::rules::docker_ce`](#nftables--rules--docker_ce): Default firewall configuration for Docker-CE
+* [`nftables::rules::ftp`](#nftables--rules--ftp): manage in ftp (with conntrack helper)
 * [`nftables::rules::http`](#nftables--rules--http): manage in http
 * [`nftables::rules::https`](#nftables--rules--https): manage in https
 * [`nftables::rules::icinga2`](#nftables--rules--icinga2): manage in icinga2
@@ -96,6 +97,7 @@ and Manager Daemons (MGR).
 * [`nftables::chain`](#nftables--chain): manage a chain
 * [`nftables::config`](#nftables--config): manage a config snippet
 * [`nftables::file`](#nftables--file): Insert a file into the nftables configuration
+* [`nftables::helper`](#nftables--helper): manage a conntrack helper
 * [`nftables::rule`](#nftables--rule): Provides an interface to create a firewall rule
 * [`nftables::rules::dnat4`](#nftables--rules--dnat4): manage a ipv4 dnat rule
 * [`nftables::rules::masquerade`](#nftables--rules--masquerade): masquerade all outgoing traffic
@@ -584,6 +586,33 @@ Flag to control whether the class should create the base common chains.
 
 Default value: `true`
 
+### <a name="nftables--rules--ftp"></a>`nftables::rules::ftp`
+
+manage in ftp (with conntrack helper)
+
+#### Parameters
+
+The following parameters are available in the `nftables::rules::ftp` class:
+
+* [`enable_passive`](#-nftables--rules--ftp--enable_passive)
+* [`passive_ports`](#-nftables--rules--ftp--passive_ports)
+
+##### <a name="-nftables--rules--ftp--enable_passive"></a>`enable_passive`
+
+Data type: `Boolean`
+
+Enable FTP passive mode support
+
+Default value: `true`
+
+##### <a name="-nftables--rules--ftp--passive_ports"></a>`passive_ports`
+
+Data type: `Nftables::Port::Range`
+
+Set the FTP passive mode port range
+
+Default value: `'10090-10100'`
+
 ### <a name="nftables--rules--http"></a>`nftables::rules::http`
 
 manage in http
@@ -1610,6 +1639,50 @@ auto included in the main nft configuration
 
 Default value: `'file-'`
 
+### <a name="nftables--helper"></a>`nftables::helper`
+
+manage a conntrack helper
+
+#### Examples
+
+##### FTP helper
+
+```puppet
+nftables::helper { 'ftp-standard':
+  content => 'type "ftp" protocol tcp;',
+}
+```
+
+#### Parameters
+
+The following parameters are available in the `nftables::helper` defined type:
+
+* [`content`](#-nftables--helper--content)
+* [`table`](#-nftables--helper--table)
+* [`helper`](#-nftables--helper--helper)
+
+##### <a name="-nftables--helper--content"></a>`content`
+
+Data type: `String`
+
+Conntrack helper definition.
+
+##### <a name="-nftables--helper--table"></a>`table`
+
+Data type: `Pattern[/^(ip|ip6|inet)-[a-zA-Z0-9_]+$/]`
+
+The name of the table to add this helper to.
+
+Default value: `'inet-filter'`
+
+##### <a name="-nftables--helper--helper"></a>`helper`
+
+Data type: `Pattern[/^[a-zA-Z0-9_][A-z0-9_-]*$/]`
+
+The symbolic name for the helper.
+
+Default value: `$title`
+
 ### <a name="nftables--rule"></a>`nftables::rule`
 
 Provides an interface to create a firewall rule
diff --git a/files/config/puppet-inet-filter.nft b/files/config/puppet-inet-filter.nft
index 0bebad5..438e5c3 100644
--- a/files/config/puppet-inet-filter.nft
+++ b/files/config/puppet-inet-filter.nft
@@ -1 +1,2 @@
+  include "inet-filter-helper-*.nft"
   include "inet-filter-chain-*.nft"
diff --git a/manifests/helper.pp b/manifests/helper.pp
new file mode 100644
index 0000000..8e1212e
--- /dev/null
+++ b/manifests/helper.pp
@@ -0,0 +1,51 @@
+# @summary manage a conntrack helper
+#
+# @example FTP helper
+#  nftables::helper { 'ftp-standard':
+#    content => 'type "ftp" protocol tcp;',
+#  }
+#
+# @param content
+#   Conntrack helper definition.
+# @param table
+#   The name of the table to add this helper to.
+# @param helper
+#   The symbolic name for the helper.
+define nftables::helper (
+  String $content,
+  Pattern[/^(ip|ip6|inet)-[a-zA-Z0-9_]+$/] $table = 'inet-filter',
+  Pattern[/^[a-zA-Z0-9_][A-z0-9_-]*$/] $helper = $title,
+) {
+  $concat_name = "nftables-${table}-helper-${helper}"
+
+  concat {
+    $concat_name:
+      path           => "/etc/nftables/puppet-preflight/${table}-helper-${helper}.nft",
+      owner          => root,
+      group          => root,
+      mode           => $nftables::default_config_mode,
+      ensure_newline => true,
+      require        => Package['nftables'],
+  } ~> Exec['nft validate'] -> file {
+    "/etc/nftables/puppet/${table}-helper-${helper}.nft":
+      ensure => file,
+      source => "/etc/nftables/puppet-preflight/${table}-helper-${helper}.nft",
+      owner  => root,
+      group  => root,
+      mode   => $nftables::default_config_mode,
+  } ~> Service['nftables']
+
+  concat::fragment {
+    default:
+      target => $concat_name;
+    "${concat_name}-header":
+      order   => '00',
+      content => "# Start of fragment order:00 ${helper} header\nct helper ${helper} {";
+    "${concat_name}-body":
+      order   => '98',
+      content => $content;
+    "${concat_name}-footer":
+      order   => '99',
+      content => "# Start of fragment order:99 ${helper} footer\n}";
+  }
+}
diff --git a/manifests/rules/ftp.pp b/manifests/rules/ftp.pp
new file mode 100644
index 0000000..83ea930
--- /dev/null
+++ b/manifests/rules/ftp.pp
@@ -0,0 +1,42 @@
+# @summary manage in ftp (with conntrack helper)
+#
+# @param enable_passive
+#   Enable FTP passive mode support
+#
+# @param passive_ports
+#   Set the FTP passive mode port range
+#
+class nftables::rules::ftp (
+  Boolean $enable_passive = true,
+  Nftables::Port::Range $passive_ports = '10090-10100',
+) {
+  nftables::helper { 'ftp-standard':
+    content => ' type "ftp" protocol tcp;',
+  }
+  nftables::chain { 'PRE': }
+  nftables::rule {
+    'PRE-type':
+      order   => '01',
+      content => 'type filter hook prerouting priority filter';
+    'PRE-policy':
+      order   => '02',
+      content => 'policy accept';
+    'PRE-helper':
+      order   => '03',
+      content => 'tcp dport 21 ct helper set "ftp-standard"';
+  }
+  nftables::rule { 'default_in-ftp':
+    content => 'tcp dport 21 accept',
+  }
+  if $enable_passive {
+    nftables::rule { 'INPUT-ftp':
+      order   => '10',
+      content => "ct helper \"ftp\" tcp dport ${passive_ports} accept",
+    }
+  } else {
+    nftables::rule { 'INPUT-ftp':
+      order   => '10',
+      content => 'ct helper "ftp" accept',
+    }
+  }
+}
diff --git a/spec/acceptance/all_rules_spec.rb b/spec/acceptance/all_rules_spec.rb
index 0786e9b..6a33383 100644
--- a/spec/acceptance/all_rules_spec.rb
+++ b/spec/acceptance/all_rules_spec.rb
@@ -91,6 +91,7 @@ describe 'nftables class' do
       include nftables::rules::mdns
       include nftables::rules::igmp
       include nftables::rules::wsd
+      include nftables::rules::ftp
       include nftables::rules::out::igmp
       include nftables::rules::out::mldv2
       include nftables::rules::out::mdns
diff --git a/spec/classes/rules/ftp_spec.rb b/spec/classes/rules/ftp_spec.rb
new file mode 100644
index 0000000..86b6fe0
--- /dev/null
+++ b/spec/classes/rules/ftp_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'nftables::rules::ftp' do
+  on_supported_os.each do |os, os_facts|
+    context "on #{os}" do
+      let(:facts) { os_facts }
+      # Required for nftables::helper (default_config_mode)
+      let(:pre_condition) { 'include nftables' }
+
+      context 'default options' do
+        it { is_expected.to contain_nftables__helper('ftp-standard') }
+        it { is_expected.to contain_nftables__chain('PRE') }
+        it { is_expected.to contain_nftables__rule('PRE-type') }
+        it { is_expected.to contain_nftables__rule('PRE-policy') }
+        it { is_expected.to contain_nftables__rule('PRE-helper') }
+        it { is_expected.to contain_nftables__rule('default_in-ftp') }
+        it { is_expected.to contain_nftables__rule('INPUT-ftp').with_content('ct helper "ftp" tcp dport 10090-10100 accept') }
+      end
+
+      context 'with passive_ports set' do
+        let(:params) { { passive_ports: '12345-23456' } }
+
+        it { is_expected.to contain_nftables__rule('INPUT-ftp').with_content('ct helper "ftp" tcp dport 12345-23456 accept') }
+      end
+
+      context 'with passive mode disabled' do
+        let(:params) { { enable_passive: false } }
+
+        it { is_expected.to contain_nftables__rule('INPUT-ftp').with_content('ct helper "ftp" accept') }
+      end
+    end
+  end
+end
