Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for IPv6 hop limiting #208

Merged
merged 2 commits into from Sep 13, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 18 additions & 2 deletions lib/puppet/provider/firewall/ip6tables.rb
Expand Up @@ -2,6 +2,7 @@
@doc = "Ip6tables type provider"

has_feature :iptables
has_feature :hop_limiting
has_feature :rate_limiting
has_feature :snat
has_feature :dnat
Expand All @@ -15,6 +16,9 @@
has_feature :mark
has_feature :tcp_flags
has_feature :pkttype
has_feature :ishasmorefrags
has_feature :islastfrag
has_feature :isfirstfrag

optional_commands({
:ip6tables => 'ip6tables',
Expand All @@ -39,6 +43,7 @@ def self.iptables_save(*args)
:icmp => "-m icmp6 --icmpv6-type",
:iniface => "-i",
:jump => "-j",
:hop_limit => "-m hl --hl-eq",
:limit => "-m limit --limit",
:log_level => "--log-level",
:log_prefix => "--log-prefix",
Expand All @@ -55,7 +60,10 @@ def self.iptables_save(*args)
: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
Expand All @@ -73,8 +81,16 @@ def self.iptables_save(*args)
# 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, :hop_limit, :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
23 changes: 13 additions & 10 deletions lib/puppet/provider/firewall/iptables.rb
Expand Up @@ -76,6 +76,11 @@
: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
Expand Down Expand Up @@ -154,10 +159,6 @@ def self.rule_to_hash(line, table, counter)
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
####################
Expand All @@ -167,14 +168,15 @@ def self.rule_to_hash(line, table, counter)
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

Expand Down Expand Up @@ -214,7 +216,7 @@ def self.rule_to_hash(line, table, counter)
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]}."
Expand Down Expand Up @@ -309,13 +311,14 @@ def general_args
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
Expand Down
37 changes: 37 additions & 0 deletions lib/puppet/type/firewall.rb
Expand Up @@ -28,6 +28,7 @@
installed.
EOS

feature :hop_limiting, "Hop limiting features."
feature :rate_limiting, "Rate limiting features."
feature :snat, "Source NATing"
feature :dnat, "Destination NATing"
Expand All @@ -45,6 +46,9 @@
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."
Expand Down Expand Up @@ -549,6 +553,14 @@ def should_to_s(value)
end
end

# Hop limiting properties
newproperty(:hop_limit, :required_features => :hop_limiting) do
desc <<-EOS
Hop limiting value for matched packets.
EOS
newvalue(/^\d+$/)
end

# Rate limiting properties
newproperty(:limit, :required_features => :rate_limiting) do
desc <<-EOS
Expand Down Expand Up @@ -649,6 +661,31 @@ def should_to_s(value)
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.
Expand Down
107 changes: 107 additions & 0 deletions spec/fixtures/ip6tables/conversion_hash.rb
@@ -0,0 +1,107 @@
# 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"],
},
'hop_limit' => {
:params => {
:name => "100 hop limit",
:hop_limit => 255,
:provider => 'ip6tables',
:table => "filter",
},
:args => ["-t", :filter, "-p", :tcp, "-m", "comment", "--comment", "100 hop limit", "-m", "hl", "--hl-eq", 255],
},
}
77 changes: 77 additions & 0 deletions spec/unit/puppet/provider/iptables_spec.rb
Expand Up @@ -162,3 +162,80 @@
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