From e4db4b965cd00d295d2dac0802fef78d1d115c17 Mon Sep 17 00:00:00 2001 From: Steven Webster Date: Mon, 2 May 2022 13:07:49 -0400 Subject: [PATCH] Add support for random fully flag Enable puppet-firewall parsing of --random-fully rules A problem may occur if puppet attempts to inject a firewall rule while the underlying iptables/ip6tables has existing rules which use the --random-fully flag in the NAT table. The issue occurs because puppet-firewall first makes a call to iptables-save/ip6tables-save to parse the existing rules (to determine if the rule already exists). If it finds a rule with --random-fully, it will immediately bail out. The current version(s) of puppet-firewall in StarlingX are old enough that they don't have parsing logic for the --random-fully flag that was initially supported in iptables version 1.6.2+. Now that StarlingX uses iptables 1.8.4, we must account for the possibility that various components (ie. kubernetes) will make use of --random-fully rules. This feature has been implemented upstream in the following commits: https://github.com/puppetlabs/puppetlabs-firewall/commits/ 9a4bc6a81cf0cd4a56ba458fadac830a2c4df529 0ea2b74c0b4a451a37bae8c2ff105b72481ab485 This commit ports back the above commits Signed-off-by: Steven Webster --- lib/puppet/provider/firewall/ip6tables.rb | 9 +++++- lib/puppet/provider/firewall/iptables.rb | 12 +++++++- lib/puppet/type/firewall.rb | 10 +++++++ spec/acceptance/firewall_spec.rb | 32 ++++++++++++++++++++++ spec/fixtures/ip6tables/conversion_hash.rb | 7 +++++ 5 files changed, 68 insertions(+), 2 deletions(-) diff --git a/lib/puppet/provider/firewall/ip6tables.rb b/lib/puppet/provider/firewall/ip6tables.rb index c8b3f64..cdb981a 100644 --- a/lib/puppet/provider/firewall/ip6tables.rb +++ b/lib/puppet/provider/firewall/ip6tables.rb @@ -49,6 +49,11 @@ Puppet::Type.type(:firewall).provide :ip6tables, :parent => :iptables, :source = mark_flag = '--set-xmark' end + kernelversion = Facter.value('kernelversion') + if (kernelversion && Puppet::Util::Package.versioncmp(kernelversion, '3.13') >= 0) && + (ip6tables_version && Puppet::Util::Package.versioncmp(ip6tables_version, '1.6.2') >= 0) + has_feature :random_fully + end def initialize(*args) ip6tables_version = Facter.value('ip6tables_version') @@ -109,6 +114,7 @@ Puppet::Type.type(:firewall).provide :ip6tables, :parent => :iptables, :source = :proto => "-p", :queue_num => "--queue-num", :queue_bypass => "--queue-bypass", + :random_fully => "--random-fully", :rdest => "--rdest", :reap => "--reap", :recent => "-m recent", @@ -168,6 +174,7 @@ Puppet::Type.type(:firewall).provide :ip6tables, :parent => :iptables, :source = :islastfrag, :isfirstfrag, :log_uid, + :random_fully, :rsource, :rdest, :reap, @@ -244,7 +251,7 @@ Puppet::Type.type(:firewall).provide :ip6tables, :parent => :iptables, :source = :ctstate, :icmp, :hop_limit, :limit, :burst, :length, :recent, :rseconds, :reap, :rhitcount, :rttl, :rname, :mask, :rsource, :rdest, :ipset, :string, :string_algo, :string_from, :string_to, :jump, :clamp_mss_to_pmtu, :gateway, :todest, - :tosource, :toports, :checksum_fill, :log_level, :log_prefix, :log_uid, :reject, :set_mss, :set_dscp, :set_dscp_class, :mss, :queue_num, :queue_bypass, + :tosource, :toports, :checksum_fill, :log_level, :log_prefix, :log_uid, :random_fully, :reject, :set_mss, :set_dscp, :set_dscp_class, :mss, :queue_num, :queue_bypass, :set_mark, :match_mark, :connlimit_above, :connlimit_mask, :connmark, :time_start, :time_stop, :month_days, :week_days, :date_start, :date_stop, :time_contiguous, :kernel_timezone, :src_cc, :dst_cc, :name] diff --git a/lib/puppet/provider/firewall/iptables.rb b/lib/puppet/provider/firewall/iptables.rb index b05ba43..767bdc0 100644 --- a/lib/puppet/provider/firewall/iptables.rb +++ b/lib/puppet/provider/firewall/iptables.rb @@ -58,6 +58,12 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir mark_flag = '--set-xmark' end + kernelversion = Facter.value('kernelversion') + if (kernelversion && Puppet::Util::Package.versioncmp(kernelversion, '3.13') >= 0) && + (iptables_version && Puppet::Util::Package.versioncmp(iptables_version, '1.6.2') >= 0) + has_feature :random_fully + end + @protocol = "IPv4" @resource_map = { @@ -102,6 +108,7 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir :proto => "-p", :queue_num => "--queue-num", :queue_bypass => "--queue-bypass", + :random_fully => "--random-fully", :random => "--random", :rdest => "--rdest", :reap => "--reap", @@ -167,6 +174,7 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir :clamp_mss_to_pmtu, :isfragment, :log_uid, + :random_fully, :random, :rdest, :reap, @@ -288,7 +296,7 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir :string_from, :string_to, :jump, :goto, :clusterip_new, :clusterip_hashmode, :clusterip_clustermac, :clusterip_total_nodes, :clusterip_local_node, :clusterip_hash_init, :queue_num, :queue_bypass, :nflog_group, :nflog_prefix, :nflog_range, :nflog_threshold, :clamp_mss_to_pmtu, :gateway, - :set_mss, :set_dscp, :set_dscp_class, :todest, :tosource, :toports, :to, :checksum_fill, :random, :log_prefix, + :set_mss, :set_dscp, :set_dscp_class, :todest, :tosource, :toports, :to, :checksum_fill, :random_fully, :random, :log_prefix, :log_level, :log_uid, :reject, :set_mark, :match_mark, :mss, :connlimit_above, :connlimit_mask, :connmark, :time_start, :time_stop, :month_days, :week_days, :date_start, :date_stop, :time_contiguous, :kernel_timezone, :src_cc, :dst_cc, :name] @@ -418,6 +426,8 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir # only replace those -f that are not followed by an l to # distinguish between -f and the '-f' inside of --tcp-flags. values = values.sub(/\s-f(?!l)(?=.*--comment)/, ' -f true') + elsif bool == :random + values = values.sub(/#{resource_map[bool]}(\s|$)(?!"!")/, "#{resource_map[bool]} true") else # append `true` to booleans that are not already negated (followed by "!") values = values.sub(/#{resource_map[bool]}(?! "!")/, "#{resource_map[bool]} true") diff --git a/lib/puppet/type/firewall.rb b/lib/puppet/type/firewall.rb index 6deab2b..1637688 100644 --- a/lib/puppet/type/firewall.rb +++ b/lib/puppet/type/firewall.rb @@ -67,6 +67,7 @@ Puppet::Type.newtype(:firewall) do feature :string_matching, "String matching features" feature :queue_num, "Which NFQUEUE to send packets to" feature :queue_bypass, "If nothing is listening on queue_num, allow packets to bypass the queue" + feature :random_fully, "The ability to use --random-fully flag" # provider specific features feature :iptables, "The provider provides iptables features." @@ -569,6 +570,15 @@ Puppet::Type.newtype(:firewall) do EOS end + newproperty(:random_fully, :required_features => :random_fully) do + desc <<-EOS + When using a jump value of "MASQUERADE", "DNAT", "REDIRECT", or "SNAT" + this boolean will enable randomized port mapping. + EOS + + newvalues(:true, :false) + end + newproperty(:random, :required_features => :dnat) do desc <<-EOS When using a jump value of "MASQUERADE", "DNAT", "REDIRECT", or "SNAT" diff --git a/spec/acceptance/firewall_spec.rb b/spec/acceptance/firewall_spec.rb index 8eee85b..617ebe5 100644 --- a/spec/acceptance/firewall_spec.rb +++ b/spec/acceptance/firewall_spec.rb @@ -2416,5 +2416,37 @@ describe 'firewall basics', docker: true do end end + describe 'random-fully' do + supports_random_fully = if os[:family] == 'redhat' && os[:release].start_with?('8') + true + elsif os[:family] == 'debian' && os[:release].start_with?('10') + true + else + false + end + + before(:all) do + pp = <<-EOS + firewall { '901 - set random-fully': + table => 'nat', + chain => 'POSTROUTING', + jump => 'MASQUERADE', + random_fully => true, + } + EOS + apply_manifest(pp, :catch_failures => true) + end + + it 'adds random-fully rule', if: supports_random_fully do + shell('iptables-save') do |r| + expect(r.stdout).to match(%r{-A POSTROUTING -p tcp -m comment --comment "901 - set random-fully" -j MASQUERADE --random-fully}) + end + end + it 'adds rule without random-fully', unless: supports_random_fully do + shell('iptables-save') do |r| + expect(r.stdout).to match(%r{-A POSTROUTING -p tcp -m comment --comment "901 - set random-fully" -j MASQUERADE}) + end + end + end end diff --git a/spec/fixtures/ip6tables/conversion_hash.rb b/spec/fixtures/ip6tables/conversion_hash.rb index 8174875..ad94ac4 100644 --- a/spec/fixtures/ip6tables/conversion_hash.rb +++ b/spec/fixtures/ip6tables/conversion_hash.rb @@ -33,6 +33,13 @@ ARGS_TO_HASH6 = { :sport => ['547'], :dport => ['546'], }, + 'random-fully' => { + line: '-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully', + table: 'filter', + provider: 'ip6tables', + params: { + random_fully: 'true', + }, } } -- 2.29.2