Closed agperson closed 8 years ago
It would be great to see support for this, particularly when wanting to create rules such as -A FORWARD -j REJECT --reject-with icmp-host-prohibited
.
I had to get NAT working for my load balancer recipe. I needed all VIPs on the load balancer to send packets out through themselves, rather than the original IP of the host. After failing to get it working using the :raw option, I hacked together some changes to the cookbook to get it working. The reason my change is considered a hack is because of the converge of the ruleset. It's converged using a sort.uniq. Iptables looks at both nat and filter sections as different sections, each requiring their own COMMIT line. But the sort.uniq sees 2 commit lines and removes one. I hacked around it by modifying the COMMIT line to be COMMIT_NAT and COMMIT_FILTER in the defaultruleset, and right AFTER the sort.uniq, when it's running through the lines to inject them, I replace anything COMMIT* with COMMIT. Very hackish, but unless/until there is a different way to roll through these and make them uniq, excluding the COMMIT lines, this is what is needed... Ruby uniq converts anything/everything into a hash... Hashes can't contain anything but unique values. It's a great way to make something unique, but in this scenario, we need a function that would allow us to specify things we want to keep. Especially since we already incorporate line numbers so we have that to our advantage..
A better way might actually be to inject the default ruleset AFTER this sort.uniq takes place. Then we can simply sort the hash by value to get it back in the proper order, and everything is happy... Anyways... YMMV.
Basically, the command I needed support for was exactly this...
iptables -t nat -A POSTROUTING -o eth1 -p tcp -d 172.28.128.21 -j SNAT --to-source 172.28.128.6
iptables -t nat -A POSTROUTING -o eth1 -p tcp -d 172.28.128.22 -j SNAT --to-source 172.28.128.6
Where 172.28.128.21 and.22 are servers in the load balanced pool and 172.28.128.6 is the VIP on the load balancer. Using keepaliveD on the load balancer to add the IP to the eth1 interface, but the traffic always flows out of the first IP configured on the interface...
Now, my load balancer recipe calls the firewall_rule like so:
firewall_rule "api1.vagrant.powerdms.net postrouting src nat to 172.28.128.6" do
direction :post
command :snat
destination "172.28.128.21"
dest_interface "eth1"
protocol :tcp
to_source "172.28.128.6"
end
end
firewall_rule "api2.vagrant.powerdms.net postrouting src nat to 172.28.128.6" do
direction :post
command :snat
destination "172.28.128.22"
dest_interface "eth1"
protocol :tcp
to_source "172.28.128.6"
end
end
The iptables file then gets generated like so:
# position 1
*nat
# position 2
:PREROUTING ACCEPT
# position 3
:POSTROUTING ACCEPT
# position 50
-A POSTROUTING -d 172.28.128.21 -o eth1 -p tcp -m tcp -m comment --comment "api01.vagrant.powerdms.net postrouting src nat to 172.28.128.6" -j SNAT --to-source 172.28.128.6
-A POSTROUTING -d 172.28.128.22 -o eth1 -p tcp -m tcp -m comment --comment "api02.vagrant.powerdms.net postrouting src nat to 172.28.128.6" -j SNAT --to-source 172.28.128.6
# position 100
COMMIT
# position 101
*filter
# position 102
:INPUT DROP
# position 103
:FORWARD DROP
# position 104
:OUTPUT ACCEPT
# position 150
-A INPUT -p tcp -m tcp -m multiport --dports 22 -m comment --comment "allow world to ssh" -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -m comment --comment "established" -j ACCEPT
-A INPUT -p icmp -m comment --comment "Allow ICMP" -j ACCEPT
-A INPUT -p tcp -m tcp -m multiport --dports 80 -m comment --comment "http" -j ACCEPT
-A INPUT -p tcp -m tcp -m multiport --dports 443 -m comment --comment "web_https" -j ACCEPT
-A INPUT -p tcp -m tcp -m multiport --dports 6380 -m comment --comment "redis_rw" -j ACCEPT
-A INPUT -p tcp -m tcp -m multiport --dports 6388 -m comment --comment "redis_ro" -j ACCEPT
-A INPUT -p tcp -m tcp -m multiport --dports 9200 -m comment --comment "elasticsearch" -j ACCEPT
-A INPUT -p tcp -m tcp -m multiport --dports 55555 -m comment --comment "hirus_compare" -j ACCEPT
-A INPUT -p tcp -m tcp -m multiport --dports 55556 -m comment --comment "hirus_convert" -j ACCEPT
-A INPUT -p tcp -m tcp -m multiport --dports 55557 -m comment --comment "hirus_backup" -j ACCEPT
# position 200
COMMIT
And now the traffic destined for 172.28.128.6 gets sent out masked as the correct IP and the servers in the load balanced pool see traffic coming from the correct IP as they should, instead of from the original eth1 IP (172.28.128.2)...
libraries/helpers.rb : When converging the uniq lines into contents, replace any line that has COMMIT_ with COMMIT.
contents << "# position #{sorted_value}"
rules.each do |k, v|
next unless v == sorted_value
+ if k.include?("COMMIT_")
+ k = "COMMIT"
+ end
contents << k
end
end
libraries/helpers_iptables.rb : Several things are changing here...
CHAIN = { in: 'INPUT', out: 'OUTPUT', pre: 'PREROUTING', post: 'POSTROUTING' } unless defined? CHAIN # , nil => "FORWARD"}
- TARGET = { allow: 'ACCEPT', reject: 'REJECT', deny: 'DROP', masquerade: 'MASQUERADE', redirect: 'REDIRECT', log: 'LOG --log-prefix "iptables: " --log-level 7' } unless defined? TARGET
+ TARGET = { allow: 'ACCEPT', reject: 'REJECT', deny: 'DROP', masquerade: 'MASQUERADE', redirect: 'REDIRECT', log: 'LOG --log-prefix "iptables: " --log-level 7', snat: 'SNAT' } unless defined? TARGET
def build_firewall_rule(current_node, rule_resource, ipv6 = false)
el5 = (current_node['platform'] == 'rhel' || current_node['platform'] == 'centos') && Gem::Dependency.new('', '~> 5.0').match?('', current_node['platform_version'])
@@ -13,16 +13,19 @@ def build_firewall_rule(current_node, rule_resource, ipv6 = false)
if rule_resource.raw
firewall_rule = rule_resource.raw.strip
else
firewall_rule = '-A '
if rule_resource.direction
firewall_rule << "#{CHAIN[rule_resource.direction.to_sym]} "
else
firewall_rule << 'FORWARD '
end
- if [:pre, :post].include?(rule_resource.direction)
- firewall_rule << '-t nat '
- end
+ #if [:pre, :post].include?(rule_resource.direction)
+ # firewall_rule << '-t nat '
+ #end
+
# Iptables order of prameters is important here see example output below:
# -A INPUT -s 1.2.3.4/32 -d 5.6.7.8/32 -i lo -p tcp -m tcp -m state --state NEW -m comment --comment "hello" -j DROP
@@ -46,6 +49,10 @@ def build_firewall_rule(current_node, rule_resource, ipv6 = false)
end
firewall_rule << "-j #{TARGET[rule_resource.command.to_sym]} "
firewall_rule << "--to-ports #{rule_resource.redirect_port} " if rule_resource.action == :redirect
+
+ # Adding to-source for iptables -t nat type of rules
+ firewall_rule << "--to-source #{rule_resource.to_source} " if rule_resource.command == :snat
+
firewall_rule.strip!
@@ -93,11 +100,16 @@ def iptables_default_allow!(new_resource)
def default_ruleset(current_node)
{
- '*filter' => 1,
- ":INPUT #{current_node['firewall']['iptables']['defaults'][:policy][:input]}" => 2,
- ":FORWARD #{current_node['firewall']['iptables']['defaults'][:policy][:forward]}" => 3,
- ":OUTPUT #{current_node['firewall']['iptables']['defaults'][:policy][:output]}" => 4,
- 'COMMIT' => 100
+ '*nat' => 1,
+ ':PREROUTING ACCEPT' => 2,
+ ':POSTROUTING ACCEPT' => 3,
+ ':OUTPUT ACCEPT' => 4,
+ 'COMMIT_NAT' => 100,
+ '*filter' => 101,
+ ":INPUT #{current_node['firewall']['iptables']['defaults'][:policy][:input]}" => 102,
+ ":FORWARD #{current_node['firewall']['iptables']['defaults'][:policy][:forward]}" => 103,
+ ":OUTPUT #{current_node['firewall']['iptables']['defaults'][:policy][:output]}" => 104,
+ 'COMMIT_FILTER' => 200
}
end
libraries/provider_firewall_rule_iptables.rb : Modified types.each do loop to check whether the command uses SNAT in the line, and if it does, it generates a line number using nat_position, instead of position. This gives us a proper ordering/numbering of all of our lines..
types.each do |iptables_type|
# build rules to apply with weight
k = build_firewall_rule(node, new_resource, iptables_type == 'ip6tables')
- v = new_resource.position
-
+ unless k.include?("SNAT")
+ v = new_resource.position
+ else
+ v = new_resource.nat_position
+ end
+
libraries/resource_firewall_rule.rb :
attribute(:firewall_name, kind_of: String, default: 'default')
- attribute(:command, kind_of: Symbol, equal_to: [:reject, :allow, :deny, :masquerade, :redirect, :log], default: :allow)
+ attribute(:command, kind_of: Symbol, equal_to: [:reject, :allow, :deny, :masquerade, :redirect, :log, :snat], default: :allow)
attribute(:protocol, kind_of: [Integer, Symbol], default: :tcp,
callbacks: { 'must be either :tcp, :udp, :icmp, :\'ipv6-icmp\', :icmpv6, :none, or a valid IP protocol number' => lambda do |p|
@@ -31,7 +31,9 @@ class Resource::FirewallRule < Chef::Resource::LWRPBase
attribute(:dest_port, kind_of: [Integer, Array, Range])
attribute(:dest_interface, kind_of: String)
- attribute(:position, kind_of: Integer, default: 50)
+
+ attribute(:nat_position, kind_of: Integer, default: 50)
+ attribute(:position, kind_of: Integer, default: 150)
attribute(:stateful, kind_of: [Symbol, Array])
attribute(:redirect_port, kind_of: Integer)
attribute(:description, kind_of: String, name_attribute: true)
@@ -43,6 +45,9 @@ class Resource::FirewallRule < Chef::Resource::LWRPBase
attribute(:program, kind_of: String)
attribute(:service, kind_of: String)
+ # --to-source option for nat in iptables
+ attribute(:to_source, kind_of: String)
+
# for when you just want to pass a raw rule
attribute(:raw, kind_of: String)
end
Thanks @timwelch. I've added a workaround similar to yours, and released v2.4.0. I think we also need to do some refactoring as I've described in the commit message. If you have any opinions on that, let me know :)
For comprehensive iptables support, in addition to the filter chain, also support raw, nat, and mangle.
There is an attempt in helpers_iptables.rb (#L24) to support NAT, but this isn't actually compatible with
iptables-restore
. This change is going to require a bit of restructuring.