| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,292 @@ | ||
| ## puppetlabs-firewall module | ||
|
|
||
| ### Overview | ||
|
|
||
| This is the puppet-firewall module. Here we are providing a module which can be used to configure various firewalls | ||
|
|
||
| ### Disclaimer | ||
|
|
||
| Warning! While this software is written in the best interest of quality it has not been formally tested by our QA teams. Use at your own risk, but feel free to enjoy and perhaps improve it while you do. | ||
|
|
||
| Please see the included Apache Software License for more legal details regarding warranty. | ||
|
|
||
| ### Installation | ||
|
|
||
| From github, download the module into your modulepath on your Puppetmaster. If you are not sure where your module path is try this command: | ||
|
|
||
| puppet --configprint modulepath | ||
|
|
||
| Depending on the version of Puppet, you may need to restart the puppetmasterd (or Apache) process before this module will work. | ||
|
|
||
| This module uses both Ruby based providers so your Puppet configuration (ie. puppet.conf) must include the following items: | ||
|
|
||
| [agent] | ||
| pluginsync = true | ||
|
|
||
| The module will not operate normally without these features enabled. | ||
|
|
||
| ### Quickstart | ||
|
|
||
| Once the module is in the correct modulepath, you should be able to create some | ||
| firewall rules like the below examples. Remember, that rules are lexically | ||
| ordered by the resource title at this point. | ||
|
|
||
| Basic accept ICMP request example: | ||
|
|
||
| firewall { "000 accept all icmp requests": | ||
| proto => "icmp", | ||
| action => "accept", | ||
| } | ||
|
|
||
| Deny all: | ||
|
|
||
| firewall { "999 drop all other requests": | ||
| action => "drop", | ||
| } | ||
|
|
||
| Source NAT example (perfect for a virtualization host): | ||
|
|
||
| firewall { '100 snat for network foo2': | ||
| chain => 'POSTROUTING', | ||
| jump => 'MASQUERADE', | ||
| proto => 'all', | ||
| outiface => "eth0", | ||
| source => ['10.1.2.0/24'], | ||
| table => 'nat', | ||
| } | ||
|
|
||
| You can make firewall rules persistent with the following iptables example: | ||
|
|
||
| exec { "persist-firewall": | ||
| command => $operatingsystem ? { | ||
| "debian" => "/sbin/iptables-save > /etc/iptables/rules.v4", | ||
| /(RedHat|CentOS)/ => "/sbin/iptables-save > /etc/sysconfig/iptables", | ||
| } | ||
| refreshonly => true, | ||
| } | ||
| Firewall { | ||
| notify => Exec["persist-firewall"] | ||
| } | ||
|
|
||
| If you wish to ensure any reject rules are executed last, try using stages. | ||
| The following example shows the creation of a class which is where your | ||
| last rules should run, this however should belong in a puppet module. | ||
|
|
||
| class my_fw::drop { | ||
| iptables { "999 drop all": | ||
| action => "drop" | ||
| } | ||
| } | ||
|
|
||
| stage { pre: before => Stage[main] } | ||
| stage { post: require => Stage[main] } | ||
|
|
||
| class { "my_fw::drop": stage => "post" } | ||
|
|
||
| By placing the 'my_fw::drop' class in the post stage it will always be inserted | ||
| last thereby avoiding locking you out before the accept rules are inserted. | ||
|
|
||
| ### Supported firewalls | ||
|
|
||
| Currently we support: | ||
|
|
||
| * Iptables | ||
|
|
||
| But plans are to support lots of other firewall implementations: | ||
|
|
||
| * Linux IPv6 (ip6tables) | ||
| * FreeBSD (ipf) | ||
| * Mac OS X (ipfw) | ||
| * OpenBSD (pf) | ||
| * Cisco (ASA and basic access lists) | ||
|
|
||
| If you have knowledge in these rules and wish to contribute to this project | ||
| feel free to submit patches (after signing a Puppetlabs CLA :-). | ||
|
|
||
| ### Generic Properties | ||
|
|
||
| #### ensure | ||
|
|
||
| Creates rule when present, removes it when absent. | ||
|
|
||
| #### name | ||
|
|
||
| * namevar | ||
|
|
||
| Name of firewall rule. This at the moment also is used for ordering, so its | ||
| common practice to prefix all rules with numbers to force ordering. For example: | ||
|
|
||
| name => "000 accept local traffic" | ||
|
|
||
| This will occur very early. | ||
|
|
||
| #### action | ||
|
|
||
| This is the action to perform on a match. Can be one of: | ||
|
|
||
| * accept - the packet is accepted | ||
| * reject - the packet is rejected with a suitable ICMP response | ||
| * drop - the packet is dropped | ||
|
|
||
| If you specify no value it will simply match the rule but perform no | ||
| action unless you provide a provider specific parameter (such as 'jump'). | ||
|
|
||
| #### proto | ||
|
|
||
| Protocol to filter. By default this is 'tcp'. | ||
|
|
||
| #### source | ||
|
|
||
| An array of source addresses. For example: | ||
|
|
||
| source => ['192.168.2.0/24', '10.2.3.0/24'] | ||
|
|
||
| #### destination | ||
|
|
||
| An array of destination addresses to match. For example: | ||
|
|
||
| destination => ['192.168.2.0/24', '10.2.3.0/24'] | ||
|
|
||
| #### sport | ||
|
|
||
| For protocols that support ports, this is a list of source ports to filter on. | ||
|
|
||
| #### dport | ||
|
|
||
| For protocols that support ports, this is a list of destination ports to filter on. | ||
|
|
||
| ### Iptables Properties | ||
|
|
||
| #### chain | ||
|
|
||
| Name of the chain to use. Can be one of the built-ins: | ||
|
|
||
| * INPUT | ||
| * FORWARD | ||
| * OUTPUT | ||
| * PREROUTING | ||
| * POSTROUTING | ||
|
|
||
| Or you can provide a user-based chain. | ||
|
|
||
| The default value is 'INPUT'. | ||
|
|
||
| #### table | ||
|
|
||
| Table to use. Can be one of: | ||
|
|
||
| * nat | ||
| * mangle | ||
| * filter | ||
| * raw | ||
| * rawpost | ||
|
|
||
| By default the setting is 'filter'. | ||
|
|
||
| #### jump | ||
|
|
||
| Action to perform when filter is matched for iptables. Can be one of: | ||
|
|
||
| * QUEUE | ||
| * RETURN | ||
| * DNAT | ||
| * SNAT | ||
| * LOG | ||
| * MASQUERADE | ||
| * REDIRECT | ||
|
|
||
| But any valid chain name is allowed. | ||
|
|
||
| For the values ACCEPT, DROP and REJECT you must use the generic | ||
| 'action' parameter. This is to enfore the use of generic parameters where | ||
| possible for maximum cross-platform modelling. | ||
|
|
||
| If you set both 'accept' and 'jump' parameters, you will get an error as | ||
| only one of the options should be set. | ||
|
|
||
| ### Interface Matching Properties | ||
|
|
||
| #### iniface | ||
|
|
||
| Input interface to filter on. | ||
|
|
||
| #### outiface | ||
|
|
||
| Output interface to filter on. | ||
|
|
||
| ### NAT Properties | ||
|
|
||
| #### tosource | ||
|
|
||
| When using jump => "SNAT" you can specify the new source address using this | ||
| parameter. | ||
|
|
||
| #### todestination | ||
|
|
||
| When using jump => "DNAT" you can specify the new destination address using | ||
| this paramter. | ||
|
|
||
| #### toports | ||
|
|
||
| Specifies a range of ports to use for masquerade. | ||
|
|
||
| ### Reject Properties | ||
|
|
||
| #### reject | ||
|
|
||
| When combined with jump => "REJECT" you can specify a different icmp response | ||
| to be sent back to the packet sender. | ||
|
|
||
| ### Logging Properties | ||
|
|
||
| #### log_level | ||
|
|
||
| When combined with jump => "LOG" specifies the log level to log to. | ||
|
|
||
| #### log_prefix | ||
|
|
||
| When combined with jump => "LOG" specifies the log prefix to use when logging. | ||
|
|
||
| ### ICMP Matching Properties | ||
|
|
||
| #### icmp | ||
|
|
||
| Specifies the type of ICMP to match. | ||
|
|
||
| ### State Matching Properties | ||
|
|
||
| #### state | ||
|
|
||
| When matching using stateful inspection you can match on different states such | ||
| as: | ||
|
|
||
| * INVALID | ||
| * ESTABLISHED | ||
| * NEW | ||
| * RELATED | ||
|
|
||
| ### Rate Limiting Properties | ||
|
|
||
| #### limit | ||
|
|
||
| A rate to limit matched packets in the form of: | ||
|
|
||
| rate/[/second/|/minute|/hour|/day] | ||
|
|
||
| #### burst | ||
|
|
||
| Maximum initial packets to match before limit checks (above) apply. | ||
|
|
||
| ### Testing | ||
|
|
||
| Make sure you have: | ||
|
|
||
| rake | ||
|
|
||
| Install the necessary gems: | ||
|
|
||
| gem install rspec | ||
|
|
||
| And run the tests from the root of the source code: | ||
|
|
||
| rake test |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| require 'rake' | ||
| require 'rspec/core/rake_task' | ||
|
|
||
| task :default do | ||
| sh %{rake -T} | ||
| end | ||
|
|
||
| # Aliases for spec. The (s) versions are used by rvm specs/tests. | ||
| task :test => [:spec] | ||
| task :tests => [:spec] | ||
| task :specs => [:spec] | ||
|
|
||
| desc 'Run all RSpec tests' | ||
| RSpec::Core::RakeTask.new(:spec) do |t| | ||
| t.rspec_opts = ['--color'] | ||
| end | ||
|
|
||
| desc 'Generate code coverage' | ||
| RSpec::Core::RakeTask.new(:coverage) do |t| | ||
| t.rcov = true | ||
| t.rcov_opts = ['--exclude', 'spec'] | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| firewall { '000 allow foo': | ||
| dport => [7061, 7062], | ||
| jump => 'ACCEPT', | ||
| proto => 'tcp', | ||
| provider => 'ip6tables' | ||
| } | ||
|
|
||
| firewall { '001 allow boo': | ||
| jump => 'ACCEPT', | ||
| iniface => 'eth0', | ||
| sport => 123, | ||
| dport => 123, | ||
| proto => 'tcp', | ||
| destination => '::1/128', | ||
| provider => 'ip6tables' | ||
| } | ||
|
|
||
| firewall { '002 foo': | ||
| dport => 1233, | ||
| proto => 'tcp', | ||
| jump => 'DROP', | ||
| provider => 'ip6tables' | ||
| } | ||
|
|
||
| firewall { '005 INPUT disregard DHCP': | ||
| dport => ['bootpc', 'bootps'], | ||
| jump => 'DROP', | ||
| proto => 'udp', | ||
| provider => 'ip6tables' | ||
| } | ||
|
|
||
| firewall { '006 INPUT disregard netbios': | ||
| dport => ['netbios-ns', 'netbios-dgm', 'netbios-ssn'], | ||
| jump => 'DROP', | ||
| proto => 'udp', | ||
| provider => 'ip6tables' | ||
| } | ||
|
|
||
| firewall { '006 Disregard CIFS': | ||
| dport => 'microsoft-ds', | ||
| jump => 'DROP', | ||
| proto => 'tcp', | ||
| provider => 'ip6tables' | ||
| } | ||
|
|
||
| firewall { '010 icmp': | ||
| proto => 'ipv6-icmp', | ||
| icmp => 'echo-reply', | ||
| jump => 'ACCEPT', | ||
| provider => 'ip6tables' | ||
| } | ||
|
|
||
| firewall { '010 INPUT allow loopback': | ||
| iniface => 'lo', | ||
| chain => 'INPUT', | ||
| jump => 'ACCEPT', | ||
| provider => 'ip6tables' | ||
| } | ||
|
|
||
| firewall { '050 INPUT drop invalid': | ||
| state => 'INVALID', | ||
| jump => 'DROP', | ||
| provider => 'ip6tables' | ||
| } | ||
|
|
||
| firewall { '051 INPUT allow related and established': | ||
| state => ['RELATED', 'ESTABLISHED'], | ||
| jump => 'ACCEPT', | ||
| provider => 'ip6tables' | ||
| } | ||
|
|
||
| firewall { '053 INPUT allow ICMP': | ||
| icmp => '8', | ||
| proto => 'ipv6-icmp', | ||
| jump => 'ACCEPT', | ||
| provider => 'ip6tables' | ||
| } | ||
|
|
||
| firewall { '055 INPUT allow DNS': | ||
| sport => 'domain', | ||
| proto => 'udp', | ||
| jump => 'ACCEPT', | ||
| provider => 'ip6tables' | ||
| } | ||
|
|
||
| firewall { '999 FORWARD drop': | ||
| chain => 'FORWARD', | ||
| jump => 'DROP', | ||
| provider => 'ip6tables' | ||
| } | ||
|
|
||
| firewall { '001 OUTPUT allow loopback': | ||
| chain => 'OUTPUT', | ||
| outiface => 'lo', | ||
| jump => 'ACCEPT', | ||
| provider => 'ip6tables' | ||
| } | ||
|
|
||
| firewall { '100 OUTPUT drop invalid': | ||
| chain => 'OUTPUT', | ||
| state => 'INVALID', | ||
| jump => 'DROP', | ||
| provider => 'ip6tables' | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| firewall { '000 allow packets with valid state': | ||
| state => ['RELATED', 'ESTABLISHED'], | ||
| jump => 'ACCEPT', | ||
| } | ||
| firewall { '001 allow icmp': | ||
| proto => 'icmp', | ||
| jump => 'ACCEPT', | ||
| } | ||
| firewall { '002 allow all to lo interface': | ||
| iniface => 'lo', | ||
| jump => 'ACCEPT', | ||
| } | ||
| firewall { '100 allow http': | ||
| proto => 'tcp', | ||
| dport => '80', | ||
| jump => 'ACCEPT', | ||
| } | ||
| firewall { '100 allow ssh': | ||
| proto => 'tcp', | ||
| dport => '22', | ||
| jump => 'ACCEPT', | ||
| } | ||
| firewall { '100 allow mysql from internal': | ||
| proto => 'tcp', | ||
| dport => '3036', | ||
| source => '10.5.5.0/24', | ||
| jump => 'ACCEPT', | ||
| } | ||
| firewall { '999 drop everything else': | ||
| jump => 'DROP', | ||
| } | ||
|
|
||
| resources { 'firewall': | ||
| purge => true, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| #!/bin/bash | ||
|
|
||
| puppet apply --debug --libdir ../../lib readme.pp |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| firewall { '000 allow foo': | ||
| dport => [7061, 7062], | ||
| action => accept, | ||
| proto => "tcp", | ||
| } | ||
|
|
||
| firewall { '001 allow boo': | ||
| action => accept, | ||
| iniface => "eth0", | ||
| sport => "123", | ||
| dport => "123", | ||
| proto => "tcp", | ||
| destination => "1.1.1.0/24", | ||
| source => "2.2.2.0/24", | ||
| } | ||
|
|
||
| firewall { '100 snat for network foo2': | ||
| chain => 'POSTROUTING', | ||
| jump => 'MASQUERADE', | ||
| proto => 'all', | ||
| outiface => 'eth0', | ||
| source => '10.1.2.0/24', | ||
| table => 'nat' | ||
| } | ||
|
|
||
| firewall { '999 bar': | ||
| action => accept, | ||
| dport => "1233", | ||
| proto => "tcp", | ||
| } | ||
|
|
||
| firewall { '002 foo': | ||
| action => drop, | ||
| dport => "1233", | ||
| proto => "tcp", | ||
| } | ||
|
|
||
| firewall { '010 icmp': | ||
| action => accept, | ||
| proto => "icmp", | ||
| icmp => "echo-reply", | ||
| } | ||
|
|
||
| firewall { '010 INPUT allow loopback': | ||
| action => accept, | ||
| iniface => 'lo', | ||
| chain => 'INPUT', | ||
| } | ||
|
|
||
| firewall { '005 INPUT disregard DHCP': | ||
| action => drop, | ||
| dport => ['bootpc', 'bootps'], | ||
| proto => 'udp' | ||
| } | ||
|
|
||
| firewall { '006 INPUT disregard netbios': | ||
| action => drop, | ||
| proto => 'udp', | ||
| dport => ['netbios-ns', 'netbios-dgm', 'netbios-ssn'], | ||
| } | ||
|
|
||
| firewall { '006 Disregard CIFS': | ||
| action => drop, | ||
| dport => 'microsoft-ds', | ||
| proto => 'tcp' | ||
| } | ||
|
|
||
| firewall { '050 INPUT drop invalid': | ||
| action => drop, | ||
| state => 'INVALID', | ||
| } | ||
|
|
||
| firewall { '051 INPUT allow related and established': | ||
| action => accept, | ||
| state => ['RELATED', 'ESTABLISHED'], | ||
| } | ||
|
|
||
| firewall { '053 INPUT allow ICMP': | ||
| action => accept, | ||
| icmp => '8', | ||
| proto => 'icmp', | ||
| } | ||
|
|
||
| firewall { '055 INPUT allow DNS': | ||
| action => accept, | ||
| proto => 'udp', | ||
| sport => 'domain' | ||
| } | ||
|
|
||
| firewall { '999 FORWARD drop': | ||
| action => drop, | ||
| chain => 'FORWARD', | ||
| } | ||
|
|
||
| firewall { '001 OUTPUT allow loopback': | ||
| action => accept, | ||
| chain => 'OUTPUT', | ||
| outiface => 'lo', | ||
| } | ||
|
|
||
| firewall { '100 OUTPUT drop invalid': | ||
| action => drop, | ||
| chain => 'OUTPUT', | ||
| state => 'INVALID', | ||
| } | ||
|
|
||
| resources { 'firewall': | ||
| purge => true | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| class Puppet::Provider::Firewall < Puppet::Provider | ||
|
|
||
| # Prefetch our rule list. This is ran once every time before any other | ||
| # action (besides initialization of each object). | ||
| def self.prefetch(resources) | ||
| debug("[prefetch(resources)]") | ||
| instances.each do |prov| | ||
| if resource = resources[prov.name] || resources[prov.name.downcase] | ||
| resource.provider = prov | ||
| end | ||
| end | ||
| end | ||
|
|
||
| # Look up the current status. This allows us to conventiently look up | ||
| # existing status with properties[:foo]. | ||
| def properties | ||
| if @property_hash.empty? | ||
| @property_hash = query || {:ensure => :absent} | ||
| @property_hash[:ensure] = :absent if @property_hash.empty? | ||
| end | ||
| @property_hash.dup | ||
| end | ||
|
|
||
| # Pull the current state of the list from the full list. We're | ||
| # getting some double entendre here.... | ||
| def query | ||
| self.class.instances.each do |instance| | ||
| if instance.name == self.name or instance.name.downcase == self.name | ||
| return instance.properties | ||
| end | ||
| end | ||
| nil | ||
| end | ||
|
|
||
| # Executed if method is missing. In this case we are going to catch | ||
| # unqualified property methods for dynamic property setting and getting. | ||
| def method_missing(meth, *args, &block) | ||
| dynamic_methods = self.class.instance_variable_get('@resource_map').keys | ||
| dynamic_methods << :chain | ||
| dynamic_methods << :table | ||
| dynamic_methods << :action | ||
|
|
||
| if dynamic_methods.include?(meth.to_sym) then | ||
| if @property_hash[meth.to_sym] then | ||
| return @property_hash[meth.to_sym] | ||
| else | ||
| return nil | ||
| end | ||
| elsif dynamic_methods.include?(meth.to_s.chomp("=").to_sym) then | ||
| debug("Args: #{args}") | ||
| @property_hash[:needs_change] = true | ||
| return true | ||
| end | ||
|
|
||
| debug("Dynamic methods: #{dynamic_methods.join(' ')}") | ||
| debug("Method missing: #{meth}. Calling super.") | ||
|
|
||
| super | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| Puppet::Type.type(:firewall).provide :ip6tables, :parent => :iptables, :source => :iptables do | ||
| @doc = "Ip6tables type provider" | ||
|
|
||
| has_feature :iptables | ||
| has_feature :rate_limiting | ||
| has_feature :snat | ||
| has_feature :dnat | ||
| has_feature :interface_match | ||
| has_feature :icmp_match | ||
| has_feature :state_match | ||
| has_feature :reject_type | ||
| has_feature :log_level | ||
| has_feature :log_prefix | ||
|
|
||
| commands :iptables => '/sbin/ip6tables' | ||
| commands :iptables_save => '/sbin/ip6tables-save' | ||
|
|
||
| @resource_map = { | ||
| :burst => "--limit-burst", | ||
| :destination => "-d", | ||
| :dport => "-m multiport --dports", | ||
| :icmp => "-m icmp6 --icmpv6-type", | ||
| :iniface => "-i", | ||
| :jump => "-j", | ||
| :limit => "--limit", | ||
| :log_level => "--log-level", | ||
| :log_prefix => "--log-prefix", | ||
| :name => "-m comment --comment", | ||
| :outiface => "-o", | ||
| :proto => "-p", | ||
| :reject => "--reject-with", | ||
| :source => "-s", | ||
| :state => "-m state --state", | ||
| :sport => "-m multiport --sports", | ||
| :table => "-t", | ||
| :todest => "--to-destination", | ||
| :toports => "--to-ports", | ||
| :tosource => "--to-source", | ||
| } | ||
|
|
||
| @resource_list = [:table, :source, :destination, :iniface, :outiface, | ||
| :proto, :sport, :dport, :name, :state, :icmp, :limit, :burst, :jump, | ||
| :todest, :tosource, :toports, :log_level, :log_prefix, :reject] | ||
|
|
||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,236 @@ | ||
| require 'puppet/provider/firewall' | ||
| require 'digest/md5' | ||
|
|
||
| Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Firewall do | ||
| include Puppet::Util::Firewall | ||
|
|
||
| @doc = "Iptables type provider" | ||
|
|
||
| has_feature :iptables | ||
| has_feature :rate_limiting | ||
| has_feature :snat | ||
| has_feature :dnat | ||
| has_feature :interface_match | ||
| has_feature :icmp_match | ||
| has_feature :state_match | ||
| has_feature :reject_type | ||
| has_feature :log_level | ||
| has_feature :log_prefix | ||
|
|
||
| commands :iptables => '/sbin/iptables' | ||
| commands :iptables_save => '/sbin/iptables-save' | ||
|
|
||
| defaultfor :kernel => :linux | ||
|
|
||
| @resource_map = { | ||
| :burst => "--limit-burst", | ||
| :destination => "-d", | ||
| :dport => "-m multiport --dports", | ||
| :icmp => "-m icmp --icmp-type", | ||
| :iniface => "-i", | ||
| :jump => "-j", | ||
| :limit => "--limit", | ||
| :log_level => "--log-level", | ||
| :log_prefix => "--log-prefix", | ||
| :name => "-m comment --comment", | ||
| :outiface => "-o", | ||
| :proto => "-p", | ||
| :reject => "--reject-with", | ||
| :source => "-s", | ||
| :state => "-m state --state", | ||
| :sport => "-m multiport --sports", | ||
| :table => "-t", | ||
| :todest => "--to-destination", | ||
| :toports => "--to-ports", | ||
| :tosource => "--to-source", | ||
| } | ||
|
|
||
| @resource_list = [:table, :source, :destination, :iniface, :outiface, | ||
| :proto, :sport, :dport, :name, :state, :icmp, :limit, :burst, :jump, | ||
| :todest, :tosource, :toports, :log_level, :log_prefix, :reject] | ||
|
|
||
| def insert | ||
| debug 'Inserting rule %s' % resource[:name] | ||
| iptables insert_args | ||
| end | ||
|
|
||
| def update | ||
| debug 'Updating rule %s' % resource[:name] | ||
| iptables update_args | ||
| end | ||
|
|
||
| def delete | ||
| debug 'Deleting rule %s' % resource[:name] | ||
| iptables delete_args | ||
| end | ||
|
|
||
| def exists? | ||
| properties[:ensure] != :absent | ||
| end | ||
|
|
||
| # Flush the property hash once done. | ||
| def flush | ||
| debug("[flush]") | ||
| if @property_hash.delete(:needs_change) | ||
| notice("Properties changed - updating rule") | ||
| update | ||
| end | ||
| @property_hash.clear | ||
| end | ||
|
|
||
| def self.instances | ||
| debug "[instances]" | ||
| table = nil | ||
| rules = [] | ||
| counter = 1 | ||
|
|
||
| # String#lines would be nice, but we need to support Ruby 1.8.5 | ||
| iptables_save.split("\n").each do |line| | ||
| unless line =~ /^\#\s+|^\:\S+|^COMMIT/ | ||
| if line =~ /^\*/ | ||
| table = line.sub(/\*/, "") | ||
| else | ||
| if hash = rule_to_hash(line, table, counter) | ||
| rules << new(hash) | ||
| counter += 1 | ||
| end | ||
| end | ||
| end | ||
| end | ||
| rules | ||
| end | ||
|
|
||
| def self.rule_to_hash(line, table, counter) | ||
| hash = {} | ||
| keys = [] | ||
| values = line.dup | ||
|
|
||
| @resource_list.reverse.each do |k| | ||
| if values.slice!(/\s#{@resource_map[k]}/) | ||
| keys << k | ||
| end | ||
| end | ||
|
|
||
| # Manually remove chain | ||
| values.slice!('-A') | ||
| keys << :chain | ||
|
|
||
| keys.zip(values.scan(/"[^"]*"|\S+/).reverse) { |f, v| hash[f] = v.gsub(/"/, '') } | ||
|
|
||
| [:dport, :sport, :state].each do |prop| | ||
| hash[prop] = hash[prop].split(',') if ! hash[prop].nil? | ||
| end | ||
|
|
||
| # This forces all existing, commentless rules to be moved to the bottom of the stack. | ||
| # Puppet-firewall requires that all rules have comments (resource names) and will fail if | ||
| # a rule in iptables does not have a comment. We get around this by appending a high level | ||
| if ! hash[:name] | ||
| hash[:name] = "9999 #{Digest::MD5.hexdigest(line)}" | ||
| end | ||
|
|
||
| hash[:line] = line | ||
| hash[:provider] = self.name.to_s | ||
| hash[:table] = table | ||
| hash[:ensure] = :present | ||
|
|
||
| # Munge some vars here ... | ||
|
|
||
| # Proto should equal 'all' if undefined | ||
| hash[:proto] = "all" if !hash.include?(:proto) | ||
|
|
||
| # If the jump parameter is set to one of: ACCEPT, REJECT or DROP then | ||
| # we should set the action parameter instead. | ||
| if ['ACCEPT','REJECT','DROP'].include?(hash[:jump]) then | ||
| hash[:action] = hash[:jump].downcase | ||
| hash.delete(:jump) | ||
| end | ||
|
|
||
| hash | ||
| end | ||
|
|
||
| def insert_args | ||
| args = [] | ||
| args << ["-I", resource[:chain], insert_order] | ||
| args << general_args | ||
| args | ||
| end | ||
|
|
||
| def update_args | ||
| args = [] | ||
| args << ["-R", resource[:chain], insert_order] | ||
| args << general_args | ||
| args | ||
| end | ||
|
|
||
| def delete_args | ||
| count = [] | ||
| line = properties[:line].gsub(/\-A/, '-D').split | ||
|
|
||
| # Grab all comment indices | ||
| line.each do |v| | ||
| if v =~ /"/ | ||
| count << line.index(v) | ||
| end | ||
| end | ||
|
|
||
| if ! count.empty? | ||
| # Remove quotes and set first comment index to full string | ||
| line[count.first] = line[count.first..count.last].join(' ').gsub(/"/, '') | ||
|
|
||
| # Make all remaining comment indices nil | ||
| ((count.first + 1)..count.last).each do |i| | ||
| line[i] = nil | ||
| end | ||
| end | ||
|
|
||
| # Return array without nils | ||
| line.compact | ||
| end | ||
|
|
||
| def general_args | ||
| debug "Current resource: %s" % resource.class | ||
|
|
||
| args = [] | ||
| resource_list = self.class.instance_variable_get('@resource_list') | ||
| resource_map = self.class.instance_variable_get('@resource_map') | ||
|
|
||
| resource_list.each do |res| | ||
| resource_value = nil | ||
| if (resource[res]) then | ||
| resource_value = resource[res] | ||
| elsif res == :jump and resource[:action] then | ||
| # In this case, we are substituting jump for action | ||
| resource_value = resource[:action].to_s.upcase | ||
| else | ||
| next | ||
| end | ||
|
|
||
| args << resource_map[res].split(' ') | ||
|
|
||
| if resource_value.is_a?(Array) | ||
| args << resource_value.join(',') | ||
| else | ||
| args << resource_value | ||
| end | ||
| end | ||
|
|
||
| args | ||
| end | ||
|
|
||
| def insert_order | ||
| debug("[insert_order]") | ||
| rules = [] | ||
|
|
||
| # Find list of current rules based on chain | ||
| self.class.instances.each do |rule| | ||
| rules << rule.name if rule.chain == resource[:chain].to_s | ||
| end | ||
|
|
||
| # No rules at all? Just bail now. | ||
| return 1 if rules.empty? | ||
|
|
||
| my_rule = resource[:name].to_s | ||
| rules << my_rule | ||
| rules.sort.index(my_rule) + 1 | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,310 @@ | ||
| # Puppet Firewall type | ||
| require 'puppet/util/firewall' | ||
|
|
||
| Puppet::Type.newtype(:firewall) do | ||
| include Puppet::Util::Firewall | ||
|
|
||
| @doc = "This type provides the capability to manage firewall rules within | ||
| puppet." | ||
|
|
||
| feature :rate_limiting, "Rate limiting features." | ||
| feature :snat, "Source NATing" | ||
| feature :dnat, "Destination NATing" | ||
| feature :interface_match, "Interface matching" | ||
| feature :icmp_match, "Matching ICMP types" | ||
| feature :state_match, "Matching stateful firewall states" | ||
| feature :reject_type, "The ability to control reject messages" | ||
| feature :log_level, "The ability to control the log level" | ||
| feature :log_prefix, "The ability to add prefixes to log messages" | ||
|
|
||
| # provider specific features | ||
| feature :iptables, "The provider provides iptables features." | ||
|
|
||
| ensurable do | ||
| desc "Manage the state of this rule." | ||
|
|
||
| newvalue(:present) do | ||
| provider.insert | ||
| end | ||
|
|
||
| newvalue(:absent) do | ||
| provider.delete | ||
| end | ||
|
|
||
| defaultto :present | ||
| end | ||
|
|
||
| newparam(:name) do | ||
| desc "The canonical name of the rule." | ||
| isnamevar | ||
|
|
||
| # Keep rule names simple - they must start with a number | ||
| newvalues(/^\d+[a-zA-Z0-9\s\-_]+$/) | ||
| end | ||
|
|
||
| newproperty(:action) do | ||
| desc "Action to perform on this rule." | ||
| newvalues(:accept, :reject, :drop) | ||
| end | ||
|
|
||
| # Generic matching properties | ||
| newproperty(:source) do | ||
| desc "The source IP address to match." | ||
| end | ||
|
|
||
| newproperty(:destination) do | ||
| desc "The destination IP address to match." | ||
| end | ||
|
|
||
| newproperty(:sport, :array_matching => :all) do | ||
| desc "The source port to match for this filter (if the protocol supports | ||
| ports). Will accept a single element or an array." | ||
|
|
||
| munge do |value| | ||
| @resource.string_to_port(value) | ||
| end | ||
|
|
||
| def should_to_s(value) | ||
| value = [value] unless value.is_a?(Array) | ||
| value.join(',') | ||
| end | ||
| end | ||
|
|
||
| newproperty(:dport, :array_matching => :all) do | ||
| desc "The destination port to match for this filter (if the protocol | ||
| supports ports). Will accept a single element or an array." | ||
|
|
||
| munge do |value| | ||
| @resource.string_to_port(value) | ||
| end | ||
|
|
||
| def should_to_s(value) | ||
| value = [value] unless value.is_a?(Array) | ||
| value.join(',') | ||
| end | ||
| end | ||
|
|
||
| newproperty(:proto) do | ||
| desc "The specific protocol to match for this rule." | ||
| newvalues(:tcp, :udp, :icmp, :"ipv6-icmp", :esp, :ah, :vrrp, :igmp, :all) | ||
| defaultto "tcp" | ||
| end | ||
|
|
||
| # Iptables specific | ||
| newproperty(:chain, :required_features => :iptables) do | ||
| desc "The value for the iptables -A parameter. Normal values are: 'INPUT', | ||
| 'FORWARD', 'OUTPUT', 'PREROUTING', 'POSTROUTING' but you can also | ||
| specify a user created chain." | ||
|
|
||
| defaultto "INPUT" | ||
| newvalue(/^[a-zA-Z0-9\-_]+$/) | ||
| end | ||
|
|
||
| newproperty(:table, :required_features => :iptables) do | ||
| desc "The value for the iptables -t parameter." | ||
| newvalues(:nat, :mangle, :filter, :raw, :rawpost) | ||
| defaultto "filter" | ||
| end | ||
|
|
||
| newproperty(:jump, :required_features => :iptables) do | ||
| desc <<EOS | ||
| The value for the iptables --jump parameter. Normal values are: | ||
| * QUEUE | ||
| * RETURN | ||
| * DNAT | ||
| * SNAT | ||
| * LOG | ||
| * MASQUERADE | ||
| * REDIRECT. | ||
| But any valid chain name is allowed. | ||
| For the values ACCEPT, DROP and REJECT you must use the generic | ||
| 'action' parameter. This is to enfore the use of generic parameters where | ||
| possible for maximum cross-platform modelling. | ||
| If you set both 'accept' and 'jump' parameters, the jump parameter will take | ||
| precedence. | ||
| EOS | ||
| validate do |value| | ||
| raise ArgumentError, "Jump destination must consist of alphanumeric characters, an underscore or a hyphen." unless value =~ /^[a-zA-Z0-9\-_]+$/ | ||
| raise ArgumentError, "Jump destination should not be one of ACCEPT, REJECT or DENY. Use the action property instead." if ["accept","reject","drop"].include?(value.downcase) | ||
| end | ||
| end | ||
|
|
||
| # Interface specific matching properties | ||
| newproperty(:iniface, :required_features => :interface_match) do | ||
| desc "Match input interface." | ||
| newvalues(/^[a-zA-Z0-9\-_]+$/) | ||
| end | ||
|
|
||
| newproperty(:outiface, :required_features => :interface_match) do | ||
| desc "Match ouput interface." | ||
| newvalues(/^[a-zA-Z0-9\-_]+$/) | ||
| end | ||
|
|
||
| # NAT specific properties | ||
| newproperty(:tosource, :required_features => :snat) do | ||
| desc "For SNAT this is the IP address that will replace the source IP | ||
| address." | ||
| end | ||
|
|
||
| newproperty(:todest, :required_features => :dnat) do | ||
| desc "For DNAT this is the IP address that will replace the destination IP | ||
| address." | ||
| end | ||
|
|
||
| newproperty(:toports, :required_features => :dnat) do | ||
| desc "For DNAT this is the port that will replace the destination port." | ||
| end | ||
|
|
||
| # Reject ICMP type | ||
| newproperty(:reject, :required_features => :reject_type) do | ||
| desc "The ICMP response to reject a packet with." | ||
| end | ||
|
|
||
| # Logging properties | ||
| newproperty(:log_level, :required_features => :log_level) do | ||
| desc "The syslog level to log to." | ||
| end | ||
|
|
||
| newproperty(:log_prefix, :required_features => :log_prefix) do | ||
| desc "The syslog prefix." | ||
| end | ||
|
|
||
| # ICMP matching property | ||
| newproperty(:icmp, :required_features => :icmp_match) do | ||
| desc "When matching ICMP packets, this is the type of ICMP packet to match." | ||
|
|
||
| munge do |value| | ||
| if value.kind_of?(String) | ||
| value = @resource.icmp_name_to_number(value) | ||
| else | ||
| value | ||
| end | ||
|
|
||
| if value == nil && value != "" | ||
| self.fail("cannot work out icmp type") | ||
| end | ||
| value | ||
| end | ||
| end | ||
|
|
||
| newproperty(:state, :array_matching => :all, :required_features => :state_match) do | ||
| desc "Matches a packet based on its state in the firewall stateful inspection | ||
| table." | ||
|
|
||
| newvalues(:INVALID,:ESTABLISHED,:NEW,:RELATED) | ||
|
|
||
| def should_to_s(value) | ||
| value = [value] unless value.is_a?(Array) | ||
| value.join(',') | ||
| end | ||
| end | ||
|
|
||
| # Rate limiting properties | ||
| newproperty(:limit, :required_features => :rate_limiting) do | ||
| desc "Rate limiting value. Example values are: '50/sec', '40/min', | ||
| '30/hour', '10/day'." | ||
| end | ||
|
|
||
| newproperty(:burst, :required_features => :rate_limiting) do | ||
| desc "Rate limiting burst value (per second)." | ||
| newvalue(/^\d+$/) | ||
| end | ||
|
|
||
| newparam(:line) do | ||
| desc 'Read-only property for caching the rule line' | ||
| end | ||
|
|
||
| validate do | ||
| debug("[validate]") | ||
|
|
||
| # TODO: this is put here to skip validation if ensure is not set. This | ||
| # is because there is a revalidation stage called later where the values | ||
| # are not set correctly. I tried tracing it - but have put in this | ||
| # workaround instead to skip. Must get to the bottom of this. | ||
| if ! value(:ensure) | ||
| return | ||
| end | ||
|
|
||
| # First we make sure the chains and tables are valid combinations | ||
| if value(:table).to_s == "filter" && value(:chain) =~ /PREROUTING|POSTROUTING/ | ||
| self.fail "PREROUTING and POSTROUTING cannot be used in table 'filter'" | ||
| end | ||
|
|
||
| if value(:table).to_s == "nat" && value(:chain) =~ /INPUT|FORWARD/ | ||
| self.fail "INPUT and FORWARD cannot be used in table 'nat'" | ||
| end | ||
|
|
||
| if value(:table).to_s == "raw" && value(:chain) =~ /INPUT|FORWARD|POSTROUTING/ | ||
| self.fail "INPUT, FORWARD and POSTROUTING cannot be used in table raw" | ||
| end | ||
|
|
||
| # Now we analyse the individual properties to make sure they apply to | ||
| # the correct combinations. | ||
| if value(:iniface) | ||
| unless value(:chain).to_s =~ /INPUT|FORWARD|PREROUTING/ | ||
| self.fail "Parameter iniface only applies to chains " \ | ||
| "INPUT,FORWARD,PREROUTING" | ||
| end | ||
| end | ||
|
|
||
| if value(:outiface) | ||
| unless value(:chain).to_s =~ /OUTPUT|FORWARD|POSTROUTING/ | ||
| self.fail "Parameter outiface only applies to chains " \ | ||
| "OUTPUT,FORWARD,POSTROUTING" | ||
| end | ||
| end | ||
|
|
||
| if value(:dport) | ||
| unless value(:proto).to_s =~ /tcp|udp|sctp/ | ||
| self.fail "[%s] Parameter dport only applies to sctp, tcp and udp " \ | ||
| "protocols. Current protocol is [%s] and dport is [%s]" % | ||
| [value(:name), should(:proto), should(:dport)] | ||
| end | ||
| end | ||
|
|
||
| if value(:jump).to_s == "DNAT" | ||
| unless value(:table).to_s =~ /nat/ | ||
| self.fail "Parameter jump => DNAT only applies to table => nat" | ||
| end | ||
|
|
||
| unless value(:todest) | ||
| self.fail "Parameter jump => DNAT must have todest parameter" | ||
| end | ||
| end | ||
|
|
||
| if value(:jump).to_s == "SNAT" | ||
| unless value(:table).to_s =~ /nat/ | ||
| self.fail "Parameter jump => SNAT only applies to table => nat" | ||
| end | ||
|
|
||
| unless value(:tosource) | ||
| self.fail "Parameter jump => DNAT must have tosource parameter" | ||
| end | ||
| end | ||
|
|
||
| if value(:jump).to_s == "REDIRECT" | ||
| unless value(:toports) | ||
| self.fail "Parameter jump => REDIRECT missing mandatory toports " \ | ||
| "parameter" | ||
| end | ||
| end | ||
|
|
||
| if value(:jump).to_s == "MASQUERADE" | ||
| unless value(:table).to_s =~ /nat/ | ||
| self.fail "Parameter jump => MASQUERADE only applies to table => nat" | ||
| end | ||
| end | ||
|
|
||
| if value(:burst) && ! value(:limit) | ||
| self.fail "burst makes no sense without limit" | ||
| end | ||
|
|
||
| if value(:action) && value(:jump) | ||
| self.fail "Only one of the parameters 'action' and 'jump' can be set" | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| # Puppet Firewall Module | ||
| # | ||
| # Copyright (C) 2011 Bob.sh Limited | ||
| # Copyright (C) 2008 Camptocamp Association | ||
| # Copyright (C) 2007 Dmitri Priimak | ||
| # | ||
| # This program is free software: you can redistribute it and/or modify | ||
| # it under the terms of the GNU General Public License as published by | ||
| # the Free Software Foundation, either version 3 of the License, or | ||
| # (at your option) any later version. | ||
| # | ||
| # This program is distributed in the hope that it will be useful, | ||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| # GNU General Public License for more details. | ||
| # | ||
| # You should have received a copy of the GNU General Public License | ||
| # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
|
||
| require 'socket' | ||
| require 'puppet/util/ipcidr' | ||
|
|
||
| module Puppet::Util::Firewall | ||
| # Translate the symbolic names for icmp packet types to integers | ||
| def icmp_name_to_number(value_icmp) | ||
| if value_icmp =~ /\d{1,2}$/ | ||
| value_icmp | ||
| else | ||
| case value_icmp | ||
| when "echo-reply" then "0" | ||
| when "destination-unreachable" then "3" | ||
| when "source-quench" then "4" | ||
| when "redirect" then "6" | ||
| when "echo-request" then "8" | ||
| when "router-advertisement" then "9" | ||
| when "router-solicitation" then "10" | ||
| when "time-exceeded" then "11" | ||
| when "parameter-problem" then "12" | ||
| when "timestamp-request" then "13" | ||
| when "timestamp-reply" then "14" | ||
| when "address-mask-request" then "17" | ||
| when "address-mask-reply" then "18" | ||
| else nil | ||
| end | ||
| end | ||
| end | ||
|
|
||
| def string_to_port(value) | ||
| if value.kind_of?(Array) | ||
| ports = [] | ||
| value.each do |port| | ||
| ports << Socket.getservbyname(port) unless port.kind_of?(Integer) | ||
| end | ||
| ports | ||
| else | ||
| Socket.getservbyname(value) | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| # Puppet Firewall Module | ||
| # | ||
| # Copyright (C) 2011 Bob.sh Limited | ||
| # Copyright (C) 2008 Camptocamp Association | ||
| # Copyright (C) 2007 Dmitri Priimak | ||
| # | ||
| # This program is free software: you can redistribute it and/or modify | ||
| # it under the terms of the GNU General Public License as published by | ||
| # the Free Software Foundation, either version 3 of the License, or | ||
| # (at your option) any later version. | ||
| # | ||
| # This program is distributed in the hope that it will be useful, | ||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| # GNU General Public License for more details. | ||
| # | ||
| # You should have received a copy of the GNU General Public License | ||
| # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
|
||
| require 'ipaddr' | ||
|
|
||
| module Puppet | ||
| module Util | ||
| class IPCidr < IPAddr | ||
|
|
||
| def netmask | ||
| _to_string(@mask_addr) | ||
| end | ||
|
|
||
| def prefixlen | ||
| m = case @family | ||
| when Socket::AF_INET | ||
| IN4MASK | ||
| when Socket::AF_INET6 | ||
| IN6MASK | ||
| else | ||
| raise "unsupported address family" | ||
| end | ||
| return $1.length if /\A(1*)(0*)\z/ =~ (@mask_addr & m).to_s(2) | ||
| raise "bad addr_mask format" | ||
| end | ||
|
|
||
| def cidr | ||
| cidr = sprintf("%s/%s", self.to_s, self.prefixlen) | ||
| cidr | ||
| end | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| # 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_HASH = { | ||
| 'long_rule_1' => { | ||
| :line => '-A INPUT -s 1.1.1.1 -d 1.1.1.1 -p tcp -m multiport --dports 7061,7062 -m multiport --sports 7061,7062 -m comment --comment "000 allow foo" -j ACCEPT', | ||
| :table => 'filter', | ||
| :compare_all => true, | ||
| :params => { | ||
| :action => "accept", | ||
| :chain => "INPUT", | ||
| :destination => "1.1.1.1", | ||
| :dport => ["7061","7062"], | ||
| :ensure => :present, | ||
| :line => '-A INPUT -s 1.1.1.1 -d 1.1.1.1 -p tcp -m multiport --dports 7061,7062 -m multiport --sports 7061,7062 -m comment --comment "000 allow foo" -j ACCEPT', | ||
| :name => "000 allow foo", | ||
| :proto => "tcp", | ||
| :provider => "iptables", | ||
| :source => "1.1.1.1", | ||
| :sport => ["7061","7062"], | ||
| :table => "filter", | ||
| }, | ||
| }, | ||
| 'action_drop_1' => { | ||
| :line => '-A INPUT -m comment --comment "000 allow foo" -j DROP', | ||
| :table => 'filter', | ||
| :params => { | ||
| :jump => nil, | ||
| :action => "drop", | ||
| }, | ||
| }, | ||
| 'action_reject_1' => { | ||
| :line => '-A INPUT -m comment --comment "000 allow foo" -j REJECT', | ||
| :table => 'filter', | ||
| :params => { | ||
| :jump => nil, | ||
| :action => "reject", | ||
| }, | ||
| }, | ||
| 'action_nil_1' => { | ||
| :line => '-A INPUT -m comment --comment "000 allow foo"', | ||
| :table => 'filter', | ||
| :params => { | ||
| :jump => nil, | ||
| :action => nil, | ||
| }, | ||
| }, | ||
| 'jump_custom_chain_1' => { | ||
| :line => '-A INPUT -m comment --comment "000 allow foo" -j custom_chain', | ||
| :table => 'filter', | ||
| :params => { | ||
| :jump => "custom_chain", | ||
| :action => nil, | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| # This hash is for testing converting a hash to an argument line. | ||
| HASH_TO_ARGS = { | ||
| 'long_rule_1' => { | ||
| :params => { | ||
| :action => "accept", | ||
| :chain => "INPUT", | ||
| :destination => "1.1.1.1", | ||
| :dport => ["7061","7062"], | ||
| :ensure => :present, | ||
| :name => "000 allow foo", | ||
| :proto => "tcp", | ||
| :source => "1.1.1.1", | ||
| :sport => ["7061","7062"], | ||
| :table => "filter", | ||
| }, | ||
| :args => ["-t", :filter, "-s", "1.1.1.1", "-d", "1.1.1.1", "-p", :tcp, "-m", "multiport", "--sports", "7061,7062", "-m", "multiport", "--dports", "7061,7062", "-m", "comment", "--comment", "000 allow foo", "-j", "ACCEPT"], | ||
| }, | ||
| 'long_rule_2' => { | ||
| :params => { | ||
| :chain => "INPUT", | ||
| :destination => "2.10.13.3/24", | ||
| :dport => ["7061"], | ||
| :ensure => :present, | ||
| :jump => "my_custom_chain", | ||
| :name => "700 allow bar", | ||
| :proto => "udp", | ||
| :source => "1.1.1.1", | ||
| :sport => ["7061","7062"], | ||
| :table => "filter", | ||
| }, | ||
| :args => ["-t", :filter, "-s", "1.1.1.1", "-d", "2.10.13.3/24", "-p", :udp, "-m", "multiport", "--sports", "7061,7062", "-m", "multiport", "--dports", "7061", "-m", "comment", "--comment", "700 allow bar", "-j", "my_custom_chain"], | ||
| }, | ||
| 'no_action' => { | ||
| :params => { | ||
| :name => "100 no action", | ||
| :table => "filter", | ||
| }, | ||
| :args => ["-t", :filter, "-p", :tcp, "-m", "comment", "--comment", | ||
| "100 no action"], | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| require 'rspec' | ||
|
|
||
| class Object | ||
| # This is necessary because the RAL has a 'should' | ||
| # method. | ||
| alias :must :should | ||
| alias :must_not :should_not | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # Some monkey-patching to allow us to test private methods. | ||
| class Class | ||
| def publicize_methods(*methods) | ||
| saved_private_instance_methods = methods.empty? ? self.private_instance_methods : methods | ||
|
|
||
| self.class_eval { public(*saved_private_instance_methods) } | ||
| yield | ||
| self.class_eval { private(*saved_private_instance_methods) } | ||
| end | ||
| end | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| require 'fileutils' | ||
| require 'tempfile' | ||
|
|
||
| # A support module for testing files. | ||
| module PuppetSpec::Files | ||
| # This code exists only to support tests that run as root, pretty much. | ||
| # Once they have finally been eliminated this can all go... --daniel 2011-04-08 | ||
| if Puppet.features.posix? then | ||
| def self.in_tmp(path) | ||
| path =~ /^\/tmp/ or path =~ /^\/var\/folders/ | ||
| end | ||
| elsif Puppet.features.microsoft_windows? | ||
| def self.in_tmp(path) | ||
| tempdir = File.expand_path(File.join(Dir::LOCAL_APPDATA, "Temp")) | ||
| path =~ /^#{tempdir}/ | ||
| end | ||
| else | ||
| fail "Help! Can't find in_tmp for this platform" | ||
| end | ||
|
|
||
| def self.cleanup | ||
| $global_tempfiles ||= [] | ||
| while path = $global_tempfiles.pop do | ||
| fail "Not deleting tmpfile #{path} outside regular tmpdir" unless in_tmp(path) | ||
|
|
||
| begin | ||
| FileUtils.rm_r path, :secure => true | ||
| rescue Errno::ENOENT | ||
| # nothing to do | ||
| end | ||
| end | ||
| end | ||
|
|
||
| def tmpfile(name) | ||
| # Generate a temporary file, just for the name... | ||
| source = Tempfile.new(name) | ||
| path = source.path | ||
| source.close! | ||
|
|
||
| # ...record it for cleanup, | ||
| $global_tempfiles ||= [] | ||
| $global_tempfiles << File.expand_path(path) | ||
|
|
||
| # ...and bam. | ||
| path | ||
| end | ||
|
|
||
| def tmpdir(name) | ||
| path = tmpfile(name) | ||
| FileUtils.mkdir_p(path) | ||
| path | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| module PuppetSpec::Fixtures | ||
| def fixtures(*rest) | ||
| File.join(PuppetSpec::FIXTURE_DIR, *rest) | ||
| end | ||
| def my_fixture_dir | ||
| callers = caller | ||
| while line = callers.shift do | ||
| next unless found = line.match(%r{/spec/(.*)_spec\.rb:}) | ||
| return fixtures(found[1]) | ||
| end | ||
| fail "sorry, I couldn't work out your path from the caller stack!" | ||
| end | ||
| def my_fixture(name) | ||
| file = File.join(my_fixture_dir, name) | ||
| unless File.readable? file then | ||
| fail Puppet::DevError, "fixture '#{name}' for #{my_fixture_dir} is not readable" | ||
| end | ||
| return file | ||
| end | ||
| def my_fixtures(glob = '*', flags = 0) | ||
| files = Dir.glob(File.join(my_fixture_dir, glob), flags) | ||
| unless files.length > 0 then | ||
| fail Puppet::DevError, "fixture '#{glob}' for #{my_fixture_dir} had no files!" | ||
| end | ||
| block_given? and files.each do |file| yield file end | ||
| files | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| require 'stringio' | ||
|
|
||
| ######################################################################## | ||
| # Backward compatibility for Jenkins outdated environment. | ||
| module RSpec | ||
| module Matchers | ||
| module BlockAliases | ||
| alias_method :to, :should unless method_defined? :to | ||
| alias_method :to_not, :should_not unless method_defined? :to_not | ||
| alias_method :not_to, :should_not unless method_defined? :not_to | ||
| end | ||
| end | ||
| end | ||
|
|
||
|
|
||
| ######################################################################## | ||
| # Custom matchers... | ||
| RSpec::Matchers.define :have_matching_element do |expected| | ||
| match do |actual| | ||
| actual.any? { |item| item =~ expected } | ||
| end | ||
| end | ||
|
|
||
|
|
||
| RSpec::Matchers.define :exit_with do |expected| | ||
| actual = nil | ||
| match do |block| | ||
| begin | ||
| block.call | ||
| rescue SystemExit => e | ||
| actual = e.status | ||
| end | ||
| actual and actual == expected | ||
| end | ||
| failure_message_for_should do |block| | ||
| "expected exit with code #{expected} but " + | ||
| (actual.nil? ? " exit was not called" : "we exited with #{actual} instead") | ||
| end | ||
| failure_message_for_should_not do |block| | ||
| "expected that exit would not be called with #{expected}" | ||
| end | ||
| description do | ||
| "expect exit with #{expected}" | ||
| end | ||
| end | ||
|
|
||
|
|
||
| RSpec::Matchers.define :have_printed do |expected| | ||
| match do |block| | ||
| $stderr = $stdout = StringIO.new | ||
|
|
||
| begin | ||
| block.call | ||
| ensure | ||
| $stdout.rewind | ||
| @actual = $stdout.read | ||
|
|
||
| $stdout = STDOUT | ||
| $stderr = STDERR | ||
| end | ||
|
|
||
| if @actual then | ||
| case expected | ||
| when String | ||
| @actual.include? expected | ||
| when Regexp | ||
| expected.match @actual | ||
| else | ||
| raise ArgumentError, "No idea how to match a #{@actual.class.name}" | ||
| end | ||
| end | ||
| end | ||
|
|
||
| failure_message_for_should do |actual| | ||
| if actual.nil? then | ||
| "expected #{expected.inspect}, but nothing was printed" | ||
| else | ||
| "expected #{expected.inspect} to be printed; got:\n#{actual}" | ||
| end | ||
| end | ||
|
|
||
| description do | ||
| "expect #{expected.inspect} to be printed" | ||
| end | ||
|
|
||
| diffable | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # Support code for running stuff with warnings disabled. | ||
| module Kernel | ||
| def with_verbose_disabled | ||
| verbose, $VERBOSE = $VERBOSE, nil | ||
| result = yield | ||
| $VERBOSE = verbose | ||
| return result | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| dir = File.expand_path(File.dirname(__FILE__)) | ||
| $LOAD_PATH.unshift File.join(dir, 'lib') | ||
|
|
||
| # Don't want puppet getting the command line arguments for rake or autotest | ||
| ARGV.clear | ||
|
|
||
| require 'puppet' | ||
| require 'mocha' | ||
| gem 'rspec', '>=2.0.0' | ||
| require 'rspec/expectations' | ||
|
|
||
| # So everyone else doesn't have to include this base constant. | ||
| module PuppetSpec | ||
| FIXTURE_DIR = File.join(dir = File.expand_path(File.dirname(__FILE__)), "fixtures") unless defined?(FIXTURE_DIR) | ||
| end | ||
|
|
||
| require 'pathname' | ||
| require 'tmpdir' | ||
|
|
||
| require 'puppet_spec/verbose' | ||
| require 'puppet_spec/files' | ||
| require 'puppet_spec/fixtures' | ||
| require 'puppet_spec/matchers' | ||
| require 'monkey_patches/alias_should_to_must' | ||
| require 'monkey_patches/publicize_methods' | ||
|
|
||
| Pathname.glob("#{dir}/shared_behaviours/**/*.rb") do |behaviour| | ||
| require behaviour.relative_path_from(Pathname.new(dir)) | ||
| end | ||
|
|
||
| RSpec.configure do |config| | ||
| include PuppetSpec::Fixtures | ||
|
|
||
| config.mock_with :mocha | ||
|
|
||
| config.before :each do | ||
| GC.disable | ||
|
|
||
| # these globals are set by Application | ||
| $puppet_application_mode = nil | ||
| $puppet_application_name = nil | ||
|
|
||
| # REVISIT: I think this conceals other bad tests, but I don't have time to | ||
| # fully diagnose those right now. When you read this, please come tell me | ||
| # I suck for letting this float. --daniel 2011-04-21 | ||
| Signal.stubs(:trap) | ||
|
|
||
| # Set the confdir and vardir to gibberish so that tests | ||
| # have to be correctly mocked. | ||
| Puppet[:confdir] = "/dev/null" | ||
| Puppet[:vardir] = "/dev/null" | ||
|
|
||
| # Avoid opening ports to the outside world | ||
| Puppet.settings[:bindaddress] = "127.0.0.1" | ||
|
|
||
| @logs = [] | ||
| Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(@logs)) | ||
|
|
||
| @log_level = Puppet::Util::Log.level | ||
| end | ||
|
|
||
| config.after :each do | ||
| Puppet.settings.clear | ||
| Puppet::Node::Environment.clear | ||
| Puppet::Util::Storage.clear | ||
| Puppet::Util::ExecutionStub.reset | ||
|
|
||
| PuppetSpec::Files.cleanup | ||
|
|
||
| @logs.clear | ||
| Puppet::Util::Log.close_all | ||
| Puppet::Util::Log.level = @log_level | ||
|
|
||
| GC.enable | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| #!/usr/bin/env rspec | ||
|
|
||
| require 'spec_helper' | ||
| require 'puppet/provider/confine/exists' | ||
|
|
||
| describe 'iptables provider detection' do | ||
| let(:exists) { | ||
| Puppet::Provider::Confine::Exists | ||
| } | ||
|
|
||
| before :each do | ||
| # Reset the default provider | ||
| Puppet::Type.type(:firewall).defaultprovider = nil | ||
| end | ||
|
|
||
| it "should default to iptables provider if /sbin/iptables[-save] exists" do | ||
| # Stub lookup for /sbin/iptables & /sbin/iptables-save | ||
| exists.any_instance.stubs(:which).with("/sbin/iptables"). | ||
| returns "/sbin/iptables" | ||
| exists.any_instance.stubs(:which).with("/sbin/iptables-save"). | ||
| returns "/sbin/iptables-save" | ||
|
|
||
| # Every other command should return false so we don't pick up any | ||
| # other providers | ||
| exists.any_instance.stubs(:which).with() { |value| | ||
| ! ["/sbin/iptables","/sbin/iptables-save"].include?(value) | ||
| }.returns false | ||
|
|
||
| # Create a resource instance and make sure the provider is iptables | ||
| resource = Puppet::Type.type(:firewall).new({ | ||
| :name => '000 test foo', | ||
| }) | ||
| resource.provider.class.to_s.should == "Puppet::Type::Firewall::ProviderIptables" | ||
| end | ||
|
|
||
| it "should raise a default provider error when there are no commands" do | ||
| # Stub all commands lookups so they return nothing | ||
| exists.any_instance.stubs(:which).returns false | ||
|
|
||
| # Instantiate a resource instance and make sure it raises an exception | ||
| lambda { resource = Puppet::Type.type(:firewall).new({ | ||
| :name => '000 test foo' }) }.should raise_error(Puppet::DevError, | ||
| "Could not find a default provider for firewall") | ||
| end | ||
| end | ||
|
|
||
| describe 'iptables provider' do | ||
| let(:provider) { Puppet::Type.type(:firewall).provider(:iptables) } | ||
| let(:resource) { | ||
| Puppet::Type.type(:firewall).new({ | ||
| :name => '000 test foo', | ||
| :action => 'accept', | ||
| }) | ||
| } | ||
|
|
||
| before :each do | ||
| Puppet::Type::Firewall.stubs(:defaultprovider).returns provider | ||
| provider.stubs(:command).with(:iptables_save).returns "/sbin/iptables-save" | ||
| end | ||
|
|
||
| it 'should be able to get a list of existing rules' do | ||
| # Pretend to return nil from iptables | ||
| provider.expects(:execute).with(['/sbin/iptables-save']).returns("") | ||
|
|
||
| provider.instances.each do |rule| | ||
| rule.should be_instance_of(provider) | ||
| rule.properties[:provider].to_s.should == provider.name.to_s | ||
| end | ||
| end | ||
|
|
||
| # Load in ruby hash for test fixtures. | ||
| load 'spec/fixtures/iptables/conversion_hash.rb' | ||
|
|
||
| describe 'when converting rules to resources' do | ||
| ARGS_TO_HASH.each do |test_name,data| | ||
| describe "for test data '#{test_name}'" do | ||
| let(:resource) { provider.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.sort.should == data[:params].keys.sort | ||
| 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_ARGS.each do |test_name,data| | ||
| describe "for test data '#{test_name}'" do | ||
| let(:resource) { Puppet::Type.type(:firewall).new(data[:params]) } | ||
| let(:provider) { Puppet::Type.type(:firewall).provider(:iptables) } | ||
| let(:instance) { provider.new(resource) } | ||
|
|
||
| it 'general_args should be valid' do | ||
| instance.general_args.flatten.should == data[:args] | ||
| end | ||
| end | ||
| end | ||
| end | ||
|
|
||
| describe 'when converting rules without comments to resources' do | ||
| let(:sample_rule) { | ||
| '-A INPUT -s 1.1.1.1 -d 1.1.1.1 -p tcp -m multiport --dports 7061,7062 -m multiport --sports 7061, 7062 -j ACCEPT' | ||
| } | ||
| let(:resource) { provider.rule_to_hash(sample_rule, 'filter', 0) } | ||
| let(:instance) { provider.new(resource) } | ||
|
|
||
| it 'rule name contains a MD5 sum of the line' do | ||
| resource[:name].should == "9999 #{Digest::MD5.hexdigest(resource[:line])}" | ||
| end | ||
| end | ||
|
|
||
| describe 'when creating resources' do | ||
| let(:instance) { provider.new(resource) } | ||
|
|
||
| before :each do | ||
| provider.expects(:execute).with(['/sbin/iptables-save']).returns("") | ||
| end | ||
|
|
||
| it 'insert_args should be an array' do | ||
| instance.insert_args.class.should == Array | ||
| end | ||
| end | ||
|
|
||
| describe 'when modifying resources' do | ||
| let(:instance) { provider.new(resource) } | ||
|
|
||
| before :each do | ||
| provider.expects(:execute).with(['/sbin/iptables-save']).returns "" | ||
| end | ||
|
|
||
| it 'update_args should be an array' do | ||
| instance.update_args.class.should == Array | ||
| end | ||
| end | ||
|
|
||
| describe 'when deleting resources' do | ||
| let(:sample_rule) { | ||
| '-A INPUT -s 1.1.1.1 -d 1.1.1.1 -p tcp -m multiport --dports 7061,7062 -m multiport --sports 7061, 7062 -j ACCEPT' | ||
| } | ||
| let(:resource) { provider.rule_to_hash(sample_rule, 'filter', 0) } | ||
| let(:instance) { provider.new(resource) } | ||
|
|
||
| it 'resource[:line] looks like the original rule' do | ||
| resource[:line] == sample_rule | ||
| end | ||
|
|
||
| it 'delete_args is an array' do | ||
| instance.delete_args.class.should == Array | ||
| end | ||
|
|
||
| it 'delete_args is the same as the rule string when joined' do | ||
| instance.delete_args.join(' ').should == sample_rule.gsub(/\-A/, '-D') | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| #!/usr/bin/env rspec | ||
|
|
||
| require 'spec_helper' | ||
|
|
||
| firewall = Puppet::Type.type(:firewall) | ||
|
|
||
| describe firewall do | ||
| before :each do | ||
| @class = firewall | ||
| @provider = stub 'provider' | ||
| @provider.stubs(:name).returns(:iptables) | ||
| Puppet::Type::Firewall.stubs(:defaultprovider).returns @provider | ||
|
|
||
| @resource = @class.new({:name => '000 test foo'}) | ||
| end | ||
|
|
||
| it 'should have :name be its namevar' do | ||
| @class.key_attributes.should == [:name] | ||
| end | ||
|
|
||
| describe ':name' do | ||
| it 'should accept a name' do | ||
| @resource[:name] = '000-test-foo' | ||
| @resource[:name].should == '000-test-foo' | ||
| end | ||
|
|
||
| it 'should not accept a name with non-ASCII chars' do | ||
| lambda { @resource[:name] = '%*#^(#$' }.should raise_error(Puppet::Error) | ||
| end | ||
| end | ||
|
|
||
| describe ':action' do | ||
| it "should have no default" do | ||
| res = @class.new(:name => "000 test") | ||
| res.parameters[:action].should == nil | ||
| end | ||
|
|
||
| [:accept, :drop, :reject].each do |action| | ||
| it "should accept value #{action}" do | ||
| @resource[:action] = action | ||
| @resource[:action].should == action | ||
| end | ||
| end | ||
|
|
||
| it 'should fail when value is not recognized' do | ||
| lambda { @resource[:action] = 'not valid' }.should raise_error(Puppet::Error) | ||
| end | ||
| end | ||
|
|
||
| describe ':chain' do | ||
| [:INPUT, :FORWARD, :OUTPUT, :PREROUTING, :POSTROUTING].each do |chain| | ||
| it "should accept chain value #{chain}" do | ||
| @resource[:chain] = chain | ||
| @resource[:chain].should == chain | ||
| end | ||
| end | ||
|
|
||
| it 'should fail when the chain value is not recognized' do | ||
| lambda { @resource[:chain] = 'not valid' }.should raise_error(Puppet::Error) | ||
| end | ||
| end | ||
|
|
||
| describe ':table' do | ||
| [:nat, :mangle, :filter, :raw].each do |table| | ||
| it "should accept table value #{table}" do | ||
| @resource[:table] = table | ||
| @resource[:table].should == table | ||
| end | ||
| end | ||
|
|
||
| it "should fail when table value is not recognized" do | ||
| lambda { @resource[:table] = 'not valid' }.should raise_error(Puppet::Error) | ||
| end | ||
| end | ||
|
|
||
| describe ':proto' do | ||
| [:tcp, :udp, :icmp, :esp, :ah, :vrrp, :igmp, :all].each do |proto| | ||
| it "should accept proto value #{proto}" do | ||
| @resource[:proto] = proto | ||
| @resource[:proto].should == proto | ||
| end | ||
| end | ||
|
|
||
| it "should fail when proto value is not recognized" do | ||
| lambda { @resource[:proto] = 'foo' }.should raise_error(Puppet::Error) | ||
| end | ||
| end | ||
|
|
||
| describe ':jump' do | ||
| it "should have no default" do | ||
| res = @class.new(:name => "000 test") | ||
| res.parameters[:jump].should == nil | ||
| end | ||
|
|
||
| ['QUEUE', 'RETURN', 'DNAT', 'SNAT', 'LOG', 'MASQUERADE', 'REDIRECT'].each do |jump| | ||
| it "should accept jump value #{jump}" do | ||
| @resource[:jump] = jump | ||
| @resource[:jump].should == jump | ||
| end | ||
| end | ||
|
|
||
| ['ACCEPT', 'DROP', 'REJECT'].each do |jump| | ||
| it "should now fail when value #{jump}" do | ||
| lambda { @resource[:jump] = jump }.should raise_error(Puppet::Error) | ||
| end | ||
| end | ||
|
|
||
| it "should fail when jump value is not recognized" do | ||
| lambda { @resource[:jump] = '%^&*' }.should raise_error(Puppet::Error) | ||
| end | ||
| end | ||
|
|
||
| [:source, :destination].each do |addr| | ||
| describe addr do | ||
| it "should accept a #{addr} as a string" do | ||
| @resource[addr] = '127.0.0.1' | ||
| @resource[addr].should == '127.0.0.1' | ||
| end | ||
| end | ||
| end | ||
|
|
||
| [:dport, :sport].each do |port| | ||
| describe port do | ||
| it "should accept a #{port} as string" do | ||
| @resource[port] = '22' | ||
| @resource[port].should == [22] | ||
| end | ||
|
|
||
| it "should accept a #{port} as an array" do | ||
| @resource[port] = ['22','23'] | ||
| @resource[port].should == [22,23] | ||
| end | ||
| end | ||
| end | ||
|
|
||
| [:iniface, :outiface].each do |iface| | ||
| describe iface do | ||
| it "should accept #{iface} value as a string" do | ||
| @resource[iface] = 'eth1' | ||
| @resource[iface].should == 'eth1' | ||
| end | ||
| end | ||
| end | ||
|
|
||
| [:tosource, :todest].each do |addr| | ||
| describe addr do | ||
| it "should accept #{addr} value as a string" do | ||
| @resource[addr] = '127.0.0.1' | ||
| end | ||
| end | ||
| end | ||
|
|
||
| describe ':icmp' do | ||
| values = { | ||
| '0' => 'echo-reply', | ||
| '3' => 'destination-unreachable', | ||
| '4' => 'source-quench', | ||
| '6' => 'redirect', | ||
| '8' => 'echo-request', | ||
| '9' => 'router-advertisement', | ||
| '10' => 'router-solicitation', | ||
| '11' => 'time-exceeded', | ||
| '12' => 'parameter-problem', | ||
| '13' => 'timestamp-request', | ||
| '14' => 'timestamp-reply', | ||
| '17' => 'address-mask-request', | ||
| '18' => 'address-mask-reply' | ||
| } | ||
| values.each do |k,v| | ||
| it 'should convert icmp string to number' do | ||
| @resource[:icmp] = v | ||
| @resource[:icmp].should == k | ||
| end | ||
| end | ||
|
|
||
| it 'should accept values as integers' do | ||
| @resource[:icmp] = 9 | ||
| @resource[:icmp].should == 9 | ||
| end | ||
|
|
||
| it 'should fail if icmp type is not recognized' do | ||
| lambda { @resource[:icmp] = 'foo' }.should raise_error(Puppet::Error) | ||
| end | ||
| end | ||
|
|
||
| describe ':state' do | ||
| it 'should accept value as a string' do | ||
| @resource[:state] = :INVALID | ||
| @resource[:state].should == [:INVALID] | ||
| end | ||
|
|
||
| it 'should accept value as an array' do | ||
| @resource[:state] = [:INVALID, :NEW] | ||
| @resource[:state].should == [:INVALID, :NEW] | ||
| end | ||
| end | ||
|
|
||
| describe ':burst' do | ||
| it 'should accept numeric values' do | ||
| @resource[:burst] = 12 | ||
| @resource[:burst].should == 12 | ||
| end | ||
|
|
||
| it 'should fail if value is not numeric' do | ||
| lambda { @resource[:burst] = 'foo' }.should raise_error(Puppet::Error) | ||
| end | ||
| end | ||
|
|
||
| describe ':action and :jump' do | ||
| it 'should allow only 1 to be set at a time' do | ||
| expect { | ||
| @class.new( | ||
| :name => "001-test", | ||
| :action => "accept", | ||
| :jump => "custom_chain" | ||
| ) | ||
| }.should raise_error(Puppet::Error, /^Only one of the parameters 'action' and 'jump' can be set$/) | ||
| end | ||
| end | ||
| end |