Adds tests for ipv6, too.
ip6tables handles fragmentation differently. There's a special
module and a couple of matchers which are all needed to
implement a stateless firewall correctly.
known_boolean handling with etc has been generified.
The known_boolean functionality was partly tailored
to the :socket feature.
has_feature :mark
has_feature :tcp_flags
has_feature :pkttype
+ has_feature :ishasmorefrags
+ has_feature :islastfrag
+ has_feature :isfirstfrag
optional_commands({
:ip6tables => 'ip6tables',
:toports => "--to-ports",
:tosource => "--to-source",
:uid => "-m owner --uid-owner",
- :pkttype => "-m pkttype --pkt-type"
+ :pkttype => "-m pkttype --pkt-type",
+ :ishasmorefrags => "-m frag --fragid 0 --fragmore",
+ :islastfrag => "-m frag --fragid 0 --fraglast",
+ :isfirstfrag => "-m frag --fragid 0 --fragfirst",
}
# Create property methods dynamically
# we need it to properly parse and apply rules, if the order of resource
# changes between puppet runs, the changed rules will be re-applied again.
# This order can be determined by going through iptables source code or just tweaking and trying manually
+ # (Note: on my CentOS 6.4 ip6tables-save returns -m frag on the place
+ # I put it when calling the command. So compability with manual changes
+ # not provided with current parser [georg.koester])
@resource_list = [:table, :source, :destination, :iniface, :outiface,
- :proto, :gid, :uid, :sport, :dport, :port, :pkttype, :name, :state, :icmp, :limit, :burst, :jump,
+ :proto, :ishasmorefrags, :islastfrag, :isfirstfrag, :gid, :uid, :sport, :dport, :port, :pkttype, :name, :state, :icmp, :limit, :burst, :jump,
:todest, :tosource, :toports, :log_level, :log_prefix, :reject]
+ # These are known booleans that do not take a value, but we want to munge
+ # to true if they exist.
+ @known_booleans = [:ishasmorefrags, :islastfrag, :isfirstfrag]
+
end
:isfragment => "-f",
}
+ # These are known booleans that do not take a value, but we want to munge
+ # to true if they exist.
+ @known_booleans = [:socket, :isfragment]
+
+
# Create property methods dynamically
(@resource_map.keys << :chain << :table << :action).each do |property|
define_method "#{property}" do
keys = []
values = line.dup
- # These are known booleans that do not take a value, but we want to munge
- # to true if they exist.
- known_booleans = [:socket, :isfragment]
-
####################
# PRE-PARSE CLUDGING
####################
values = values.sub(/--tcp-flags (\S*) (\S*)/, '--tcp-flags "\1 \2"')
# Trick the system for booleans
- known_booleans.each do |bool|
- if bool == :socket then
- values = values.sub(/#{@resource_map[bool]}/, '-m socket true')
- end
+ @known_booleans.each do |bool|
+ # append "true" because all params are expected to have values
if bool == :isfragment then
+ # -f requires special matching:
# 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(/-f(?!l)(?=.*--comment)/, '-f true')
+ else
+ values = values.sub(/#{@resource_map[bool]}/, "#{@resource_map[bool]} true")
end
end
end
# Convert booleans removing the previous cludge we did
- known_booleans.each do |bool|
+ @known_booleans.each do |bool|
if hash[bool] != nil then
unless hash[bool] == "true" then
raise "Parser error: #{bool} was meant to be a boolean but received value: #{hash[bool]}."
args = []
resource_list = self.class.instance_variable_get('@resource_list')
resource_map = self.class.instance_variable_get('@resource_map')
+ known_booleans = self.class.instance_variable_get('@known_booleans')
resource_list.each do |res|
resource_value = nil
if (resource[res]) then
resource_value = resource[res]
# If socket is true then do not add the value as -m socket is standalone
- if res == :socket then
+ if known_booleans.include?(res) then
resource_value = nil
end
if res == :isfragment then
feature :isfragment, "Match fragments"
feature :address_type, "The ability match on source or destination address type"
feature :iprange, "The ability match on source or destination IP range "
+ feature :ishasmorefrags, "Match a non-last fragment of a fragmented ipv6 packet - might be first"
+ feature :islastfrag, "Match the last fragment of an ipv6 packet"
+ feature :isfirstfrag, "Match the first fragment of a fragmented ipv6 packet"
# provider specific features
feature :iptables, "The provider provides iptables features."
newvalues(:true, :false)
end
+ newproperty(:ishasmorefrags, :required_features => :ishasmorefrags) do
+ desc <<-EOS
+ If true, matches if the packet has it's 'more fragments' bit set. ipv6.
+ EOS
+
+ newvalues(:true, :false)
+ end
+
+ newproperty(:islastfrag, :required_features => :islastfrag) do
+ desc <<-EOS
+ If true, matches if the packet is the last fragment. ipv6.
+ EOS
+
+ newvalues(:true, :false)
+ end
+
+ newproperty(:isfirstfrag, :required_features => :isfirstfrag) do
+ desc <<-EOS
+ If true, matches if the packet is the first fragment.
+ Sadly cannot be negated. ipv6.
+ EOS
+
+ newvalues(:true, :false)
+ end
+
newparam(:line) do
desc <<-EOS
Read-only property for caching the rule line.
--- /dev/null
+# These hashes allow us to iterate across a series of test data
+# creating rspec examples for each parameter to ensure the input :line
+# extrapolates to the desired value for the parameter in question. And
+# vice-versa
+
+# This hash is for testing a line conversion to a hash of parameters
+# which will be used to create a resource.
+ARGS_TO_HASH6 = {
+ 'source_destination_ipv6_no_cidr' => {
+ :line => '-A INPUT -s 2001:db8:85a3::8a2e:370:7334 -d 2001:db8:85a3::8a2e:370:7334 -m comment --comment "000 source destination ipv6 no cidr"',
+ :table => 'filter',
+ :provider => 'ip6tables',
+ :params => {
+ :source => '2001:db8:85a3::8a2e:370:7334/128',
+ :destination => '2001:db8:85a3::8a2e:370:7334/128',
+ },
+ },
+ 'source_destination_ipv6_netmask' => {
+ :line => '-A INPUT -s 2001:db8:1234::/ffff:ffff:ffff:0000:0000:0000:0000:0000 -d 2001:db8:4321::/ffff:ffff:ffff:0000:0000:0000:0000:0000 -m comment --comment "000 source destination ipv6 netmask"',
+ :table => 'filter',
+ :provider => 'ip6tables',
+ :params => {
+ :source => '2001:db8:1234::/48',
+ :destination => '2001:db8:4321::/48',
+ },
+ },
+}
+
+# This hash is for testing converting a hash to an argument line.
+HASH_TO_ARGS6 = {
+ 'zero_prefixlen_ipv6' => {
+ :params => {
+ :name => '100 zero prefix length ipv6',
+ :table => 'filter',
+ :provider => 'ip6tables',
+ :source => '::/0',
+ :destination => '::/0',
+ },
+ :args => ['-t', :filter, '-p', :tcp, '-m', 'comment', '--comment', '100 zero prefix length ipv6'],
+ },
+ 'source_destination_ipv4_no_cidr' => {
+ :params => {
+ :name => '000 source destination ipv4 no cidr',
+ :table => 'filter',
+ :provider => 'ip6tables',
+ :source => '1.1.1.1',
+ :destination => '2.2.2.2',
+ },
+ :args => ['-t', :filter, '-s', '1.1.1.1/32', '-d', '2.2.2.2/32', '-p', :tcp, '-m', 'comment', '--comment', '000 source destination ipv4 no cidr'],
+ },
+ 'source_destination_ipv6_no_cidr' => {
+ :params => {
+ :name => '000 source destination ipv6 no cidr',
+ :table => 'filter',
+ :provider => 'ip6tables',
+ :source => '2001:db8:1234::',
+ :destination => '2001:db8:4321::',
+ },
+ :args => ['-t', :filter, '-s', '2001:db8:1234::/128', '-d', '2001:db8:4321::/128', '-p', :tcp, '-m', 'comment', '--comment', '000 source destination ipv6 no cidr'],
+ },
+ 'source_destination_ipv6_netmask' => {
+ :params => {
+ :name => '000 source destination ipv6 netmask',
+ :table => 'filter',
+ :provider => 'ip6tables',
+ :source => '2001:db8:1234::/ffff:ffff:ffff:0000:0000:0000:0000:0000',
+ :destination => '2001:db8:4321::/ffff:ffff:ffff:0000:0000:0000:0000:0000',
+ },
+ :args => ['-t', :filter, '-s', '2001:db8:1234::/48', '-d', '2001:db8:4321::/48', '-p', :tcp, '-m', 'comment', '--comment', '000 source destination ipv6 netmask'],
+ },
+ 'frag_ishasmorefrags' => {
+ :params => {
+ :name => "100 has more fragments",
+ :ishasmorefrags => true,
+ :provider => 'ip6tables',
+ :table => "filter",
+ },
+ :args => ["-t", :filter, "-p", :tcp, "-m", "frag", "--fragid", "0", "--fragmore", "-m", "comment", "--comment", "100 has more fragments"],
+ },
+ 'frag_islastfrag' => {
+ :params => {
+ :name => "100 last fragment",
+ :islastfrag => true,
+ :provider => 'ip6tables',
+ :table => "filter",
+ },
+ :args => ["-t", :filter, "-p", :tcp, "-m", "frag", "--fragid", "0", "--fraglast", "-m", "comment", "--comment", "100 last fragment"],
+ },
+ 'frag_isfirstfrags' => {
+ :params => {
+ :name => "100 first fragment",
+ :isfirstfrag => true,
+ :provider => 'ip6tables',
+ :table => "filter",
+ },
+ :args => ["-t", :filter, "-p", :tcp, "-m", "frag", "--fragid", "0", "--fragfirst", "-m", "comment", "--comment", "100 first fragment"],
+ },
+}
end
end
end
+
+describe 'ip6tables provider' do
+ let(:provider6) { Puppet::Type.type(:firewall).provider(:ip6tables) }
+ let(:resource) {
+ Puppet::Type.type(:firewall).new({
+ :name => '000 test foo',
+ :action => 'accept',
+ :provider => "ip6tables",
+ })
+ }
+
+ before :each do
+ Puppet::Type::Firewall.stubs(:ip6tables).returns provider6
+ provider6.stubs(:command).with(:ip6tables_save).returns "/sbin/ip6tables-save"
+
+ # Stub iptables version
+ Facter.fact(:ip6tables_version).stubs(:value).returns("1.4.7")
+
+ Puppet::Util::Execution.stubs(:execute).returns ""
+ Puppet::Util.stubs(:which).with("ip6tables-save").
+ returns "/sbin/ip6tables-save"
+ end
+
+ it 'should be able to get a list of existing rules' do
+ provider6.instances.each do |rule|
+ rule.should be_instance_of(provider6)
+ rule.properties[:provider6].to_s.should == provider6.name.to_s
+ end
+ end
+
+ it 'should ignore lines with fatal errors' do
+ Puppet::Util::Execution.stubs(:execute).with(['/sbin/ip6tables-save']).
+ returns("FATAL: Could not load /lib/modules/2.6.18-028stab095.1/modules.dep: No such file or directory")
+
+ provider6.instances.length.should == 0
+ end
+
+ # Load in ruby hash for test fixtures.
+ load 'spec/fixtures/ip6tables/conversion_hash.rb'
+
+ describe 'when converting rules to resources' do
+ ARGS_TO_HASH6.each do |test_name,data|
+ describe "for test data '#{test_name}'" do
+ let(:resource) { provider6.rule_to_hash(data[:line], data[:table], 0) }
+
+ # If this option is enabled, make sure the parameters exactly match
+ if data[:compare_all] then
+ it "the parameter hash keys should be the same as returned by rules_to_hash" do
+ resource.keys.should =~ data[:params].keys
+ end
+ end
+
+ # Iterate across each parameter, creating an example for comparison
+ data[:params].each do |param_name, param_value|
+ it "the parameter '#{param_name.to_s}' should match #{param_value.inspect}" do
+ resource[param_name].should == data[:params][param_name]
+ end
+ end
+ end
+ end
+ end
+
+ describe 'when working out general_args' do
+ HASH_TO_ARGS6.each do |test_name,data|
+ describe "for test data '#{test_name}'" do
+ let(:resource) { Puppet::Type.type(:firewall).new(data[:params]) }
+ let(:provider6) { Puppet::Type.type(:firewall).provider(:ip6tables) }
+ let(:instance) { provider6.new(resource) }
+
+ it 'general_args should be valid' do
+ instance.general_args.flatten.should == data[:args]
+ end
+ end
+ end
+ end
+end
+