--- /dev/null
+
+Puppet::Type.type(:firewallchain).provide :iptables_chain do
+ @doc = "Iptables chain provider"
+
+ has_feature :iptables_chain
+ has_feature :policy
+
+ commands :iptables => '/sbin/iptables'
+ commands :iptables_save => '/sbin/iptables-save'
+
+ commands :ip6tables => '/sbin/ip6tables'
+ commands :ip6tables_save => '/sbin/ip6tables-save'
+
+ optional_commands( { :ebtables => '/sbin/ebtables',
+ :ebtables_save => '/sbin/ebtables-save'
+ } )
+
+ defaultfor :kernel => :linux
+
+ # chain name is greedy so we anchor from the end.
+ # [\d+:\d+] doesn't exist on ebtables
+ Mapping = { :IPv4 => { :tables => method( :iptables ), :save => method( :iptables_save), :re => /^:(.+)\s(\S+)\s\[\d+:\d+\]$/ },
+ :IPv6 => { :tables => method( :ip6tables ), :save => method( :ip6tables_save ), :re => /^:(.+)\s(\S+)\s\[\d+:\d+\]$/ },
+ :ethernet => { :tables => method( :ebtables ), :save => method( :ebtables_save ), :re => /^:(.+)\s(\S+)$/ }
+ }
+ InternalChains = /^(PREROUTING|POSTROUTING|BROUTING|INPUT|FORWARD|OUTPUT)$/
+ Tables = 'NAT|MANGLE|FILTER|RAW|RAWPOST|BROUTE|'
+ Nameformat = /^(#{Tables}):(.+):(IP(v[46])?|ethernet|)$/
+
+ def create
+ # can't create internal chains
+ if @resource[:name] =~ InternalChains
+ self.warn "Attempting to create internal chain #{@resource[:name]}"
+ end
+ allvalidchains do |t, table, chain, protocol|
+ if properties[:ensure] == protocol
+ debug "Skipping Inserting chain #{chain} on table #{table} (#{protocol}) already exists"
+ else
+ debug "Inserting chain #{chain} on table #{table} (#{protocol}) using #{t}"
+ t.call ['-t',table,'-N',chain]
+ if @resource[:policy] != :empty
+ t.call ['-t',table,'-P',chain,@resource[:policy].to_s.upcase]
+ end
+ end
+ end
+ end
+
+ def destroy
+ # can't delete internal chains
+ if @resource[:name] =~ InternalChains
+ self.warn "Attempting to destroy internal chain #{@resource[:name]}"
+ end
+ allvalidchains do |t, table, chain|
+ debug "Deleting chain #{chain} on table #{table}"
+ t.call ['-t',table,'-X',chain]
+ end
+ end
+
+ def exists?
+ # we want puppet to call create on 1/2 completed rules (i.e. :ensure => :IPv4/6)
+ properties[:ensure] == :present
+ end
+
+ def policy=(value)
+ return if value == :empty
+ allvalidchains do |t, table, chain|
+ p = ['-t',table,'-P',chain,value.to_s.upcase]
+ debug "[set policy] #{t} #{p}"
+ t.call p
+ end
+ end
+
+ def policy
+ debug "[get policy] #{@resource[:name]} =#{@property_hash[:policy].to_s.downcase}"
+ return @property_hash[:policy].to_s.downcase
+ end
+
+ def self.prefetch(resources)
+ debug("[prefetch(resources)]")
+ instances.each do |prov|
+ if resource = resources[prov.name]
+ 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.
+ def query
+ self.class.instances.each do |instance|
+ if instance.name == self.name
+ debug "query found #{self.name}" % instance.properties.inspect
+ return instance.properties
+ end
+ end
+ nil
+ end
+
+ def self.instances
+ debug "[instances]"
+ table = nil
+ chains = []
+ hash = {}
+
+ Mapping.each { |p, c|
+ begin
+ c[:save].call.split("\n").each do |line|
+ if line =~ c[:re] then
+ name = (table == 'filter' ? '' : table.upcase) + ':' + $1
+ policy = $2 == '-' ? :empty : $2.downcase.to_sym
+ if ( p == :IPv4 or p == :IPv6 ) && table != 'nat'
+ if hash[name]
+ # duplicate so create a {table}:{chain}:IP instance
+ ippolicy = hash[name][:policy] == policy ? policy : :inconsistent
+ hash.delete(name)
+ chains << new({:name => name + ':', :policy => ippolicy, :ensure => :present })
+ debug "[dup] '#{name}:' #{ippolicy}"
+ else
+ hash[name] = { :policy => policy, :protocol => p }
+ end
+ end
+ name += ':' + p.to_s
+ chains << new({:name => name, :policy => policy, :ensure => :present })
+ debug "[instance] '#{name}' #{policy}"
+ elsif line =~ /^\*(\S+)/
+ table = $1
+ elsif line =~ /^($|-A|COMMIT|#)/
+ # other stuff we don't care about
+ else
+ debug "unrecognised line: #{line}"
+ end
+ end
+ rescue Puppet::Error
+ # ignore command not found for ebtables or anything that doesn't exist
+ end
+ }
+ # put all the chain names that exist in one IP stack into a 1/2 completed (:ensure) state
+ # The create method will check this and complete only what's required
+ hash.each { |key, value|
+ x = {:name => key + ':', :ensure => value[:protocol], :policy => :empty}
+ debug "halfstate #{x.inspect}"
+ chains << new(x)
+ }
+ chains
+ end
+
+ def allvalidchains
+ @resource[:name].match(Nameformat)
+ table = ($1=='') ? 'filter' : $1.downcase
+ chain = $2
+ protocol = $3
+ if protocol == 'IP' || protocol == ''
+ yield Mapping[:IPv4][:tables],table,chain,:IPv4
+ yield Mapping[:IPv6][:tables],table,chain,:IPv6
+ else
+ yield Mapping[protocol][:tables],table,chain,protocol.to_sym
+ end
+ end
+
+end
--- /dev/null
+
+Puppet::Type.newtype(:firewallchain) do
+
+ @doc = <<-EOS
+ This type provides the capability to manage iptables chains and policies on
+ internal chains within puppet.
+ EOS
+
+ #InternalChains = /^(PREROUTING|POSTROUTING|BROUTING|INPUT|FORWARD|OUTPUT)$/
+ #Tables = 'NAT|MANGLE|FILTER|RAW|RAWPOST|BROUTE|'
+ ## Technically colons (':') are allowed in table names however it requires
+ ## ruby-1.9 to do a regex to allow a backslash escaping of the colon.
+ ## ruby-1.9 regex: Nameformat = /^(<table>#{Tables}):(<chain>([^:]*(?<!\\))+):(<protocol>IP(v[46])?|EB)?$/
+ #Nameformat = /^(#{Tables}):([^:]+):(IP(v[46])?|ethernet:)$/
+
+ feature :iptables_chain, "The provider provides iptables chain features."
+ feature :policy, "Default policy (inbuilt chains only)"
+
+ #autorequire(:firewallchain) do
+ # if @parameters[:name] =~ /:$/
+ # [ @parameters[:name] + 'IPv4', @parameters[:name] + 'IPv6']
+ # end
+ #end
+
+ ensurable do
+ defaultvalues
+ defaultto :present
+ end
+
+ newparam(:name) do
+ desc <<-EOS
+ The canonical name of the chain.
+ EOS
+ isnamevar
+
+ validate do |value|
+ if value !~ Nameformat then
+ raise ArgumentError, "Inbuilt chains must be in the form {chain}:{table}:{protocol} where {table} is one of FILTER, NAT, MANGLE, RAW, RAWPOST, BROUTE or empty (alias for filter), chain can be anything without colons or one of PREROUTING, POSTROUTING, BROUTING, INPUT, FORWARD, OUTPUT for the inbuilt chains, and {protocol} being empty or IP (both meaning IPv4 and IPv6), IPv4, IPv6, ethernet (ethernet bridging) got '#{value}' table:'#{$1}' chain:'#{$2}' protocol:'#{$3}'"
+ else
+ table = $1
+ chain = $2
+ protocol = $3
+ case table
+ when /^(FILTER|)$/
+ if chain =~ /^(PREROUTING|POSTROUTING|BROUTING)$/
+ raise ArgumentError, "INPUT, OUTPUT and FORWARD are the only inbuilt chains that can be used in table 'filter'"
+ end
+ when 'MANGLE'
+ if chain =~ InternalChains && chain == 'BROUTING'
+ raise ArgumentError, "PREROUTING, POSTROUTING, INPUT, FORWARD and OUTPUT are the only inbuilt chains that can be used in table 'mangle'"
+ end
+ when 'NAT'
+ if chain =~ /^(BROUTING|INPUT|FORWARD)$/
+ raise ArgumentError, "PREROUTING, POSTROUTING and OUTPUT are the only inbuilt chains that can be used in table 'nat'"
+ end
+ if protocol =~/^(IP(v6)?)?$/
+ raise ArgumentError, "table nat isn't valid in IPv6 (or the default IP which is IPv4 and IPv6). You must specify ':IPv4' as the name suffix"
+ end
+ when 'RAW'
+ if chain =~ /^(POSTROUTING|BROUTING|INPUT|FORWARD)$/
+ raise ArgumentError,'PREROUTING and OUTPUT are the only inbuilt chains in the table \'raw\''
+ end
+ when 'BROUTE'
+ if protocol != 'ethernet'
+ raise ArgumentError,'BROUTE is only valid with protocol \'ethernet\''
+ end
+ if chain =~ /^PREROUTING|POSTROUTING|INPUT|FORWARD|OUTPUT$/
+ raise ArgumentError,'BROUTING is the only inbuilt chain allowed on on table \'BROUTE\''
+ end
+ end
+ if chain == 'BROUTING' && ( protocol != 'ethernet' || table!='BROUTE')
+ raise ArgumentError,'BROUTING is the only inbuilt chain allowed on on table \'BROUTE\' with protocol \'ethernet\' i.e. \'BROUTE:BROUTING:enternet\''
+ end
+ end
+ end
+ end
+
+ newproperty(:policy) do
+ desc <<-EOS
+ This is the action to when the end of the chain is reached.
+ It can only be set on inbuilt chains ( INPUT, FORWARD, OUTPUT,
+ PREROUTING, POSTROUTING) and can be one of:
+
+ * accept - the packet is accepted
+ * drop - the packet is dropped
+ * queue - the packet is passed userspace
+ * return - the packet is returned to calling (jump) queue
+ or the default of inbuilt chains
+ EOS
+ newvalues(:accept, :drop, :queue, :return, :empty)
+ defaultto do
+ # ethernet chain have an ACCEPT default while other haven't got an allowed value
+ if @resource[:name] =~ /:ethernet$/
+ :accept
+ else
+ :empty
+ end
+ end
+ end
+
+ validate do
+ debug("[validate]")
+
+ value(:name).match(Nameformat)
+ table = $1
+ chain = $2
+ protocol = $3
+
+ # Check that we're not removing an internal chain
+ if chain =~ InternalChains && value(:ensure).to_s == 'absent'
+ self.fail "Cannot remove in-built chains"
+ end
+
+ if value(:policy) == :empty && protocol == 'ethernet'
+ self.fail "you must set a non-empty policy on all ethernet table chains"
+ end
+
+ # Check that we're not setting a policy on a user chain
+ if chain !~ InternalChains && value(:policy).to_s != 'empty' && protocol != 'ethernet'
+ self.fail "policy can only be set on in-built chains (with the exceptionn of ethernet chains) (table:#{table} chain:#{chain} protocol:#{protocol})"
+ end
+
+ # no DROP policy on nat table
+ if table == 'nat' &&
+ value(:policy).to_s == 'DROP'
+ self.fail 'The "nat" table is not intended for filtering, the use of DROP is therefore inhibited'
+ end
+ end
+end
+
--- /dev/null
+#!/usr/bin/env rspec
+
+require 'spec_helper'
+require 'puppet/provider/confine/exists'
+
+describe 'iptables chain provider detection' do
+ let(:exists) {
+ Puppet::Provider::Confine::Exists
+ }
+
+ before :each do
+ # Reset the default provider
+ Puppet::Type.type(:firewallchain).defaultprovider = nil
+ end
+
+ it "should default to iptables provider if /sbin/(eb|ip|ip6)tables[-save] exists" do
+ # Stub lookup for /sbin/iptables & /sbin/iptables-save
+ exists.any_instance.stubs(:which).with("/sbin/ebtables").
+ returns "/sbin/ebtables"
+ exists.any_instance.stubs(:which).with("/sbin/ebtables-save").
+ returns "/sbin/ebtables-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"
+
+ exists.any_instance.stubs(:which).with("/sbin/ip6tables").
+ returns "/sbin/ip6tables"
+ exists.any_instance.stubs(:which).with("/sbin/ip6tables-save").
+ returns "/sbin/ip6tables-save"
+
+ # Every other command should return false so we don't pick up any
+ # other providers
+ exists.any_instance.stubs(:which).with() { |value|
+ value !~ /\/sbin\/(eb|ip|ip6)tables(-save)?$/
+ }.returns false
+
+ # Create a resource instance and make sure the provider is iptables
+ resource = Puppet::Type.type(:firewallchain).new({
+ :name => ':test:',
+ })
+ resource.provider.class.to_s.should == "Puppet::Type::Firewallchain::ProviderIptables_chain"
+ 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(:firewallchain).new({
+ :name => ':test:' }) }.should raise_error(Puppet::DevError,
+ "Could not find a default provider for firewallchain")
+ end
+end
+
+describe 'iptables chain provider' do
+ let(:provider) { Puppet::Type.type(:firewallchain).provider(:iptables_chain) }
+ let(:resource) {
+ Puppet::Type.type(:firewallchain).new({
+ :name => ':test:',
+ })
+ }
+
+ before :each do
+ Puppet::Type::Firewallchain.stubs(:defaultprovider).returns provider
+ provider.stubs(:command).with(:ebtables_save).returns "/sbin/ebtables-save"
+ provider.stubs(:command).with(:iptables_save).returns "/sbin/iptables-save"
+ provider.stubs(:command).with(:ip6tables_save).returns "/sbin/ip6tables-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/ebtables-save']).returns("")
+ provider.expects(:execute).with(['/sbin/iptables-save']).returns("")
+ provider.expects(:execute).with(['/sbin/ip6tables-save']).returns("")
+
+ provider.instances.each do |chain|
+ chain.should be_instance_of(provider)
+ chain.properties[:provider].to_s.should == provider.name.to_s
+ end
+ end
+
+end
+
+describe 'iptables chain resource parsing' do
+ let(:provider) { Puppet::Type.type(:firewallchain).provider(:iptables_chain) }
+
+ before :each do
+ ebtables = ['BROUTE:BROUTING:ethernet',
+ 'BROUTE:broute:ethernet',
+ ':INPUT:ethernet',
+ ':FORWARD:ethernet',
+ ':OUTPUT:ethernet',
+ ':filter:ethernet',
+ ':filterdrop:ethernet',
+ ':filterreturn:ethernet',
+ 'NAT:PREROUTING:ethernet',
+ 'NAT:OUTPUT:ethernet',
+ 'NAT:POSTROUTING:ethernet',
+ ]
+ provider.expects(:execute).with(['/sbin/ebtables-save']).returns('
+*broute
+:BROUTING ACCEPT
+:broute ACCEPT
+
+*filter
+:INPUT ACCEPT
+:FORWARD ACCEPT
+:OUTPUT ACCEPT
+:filter ACCEPT
+:filterdrop DROP
+:filterreturn RETURN
+
+*nat
+:PREROUTING ACCEPT
+:OUTPUT ACCEPT
+:POSTROUTING ACCEPT
+')
+
+ iptables = [
+ 'raw:PREROUTING:IPv4',
+ 'raw:OUTPUT:IPv4',
+ 'raw:raw:IPv4',
+ 'mangle:PREROUTING:IPv4',
+ 'mangle:INPUT:IPv4',
+ 'mangle:FORWARD:IPv4',
+ 'mangle:OUTPUT:IPv4',
+ 'mangle:POSTROUTING:IPv4',
+ 'mangle:mangle:IPv4',
+ 'NAT:PREROUTING:IPv4',
+ 'NAT:OUTPUT:IPv4',
+ 'NAT:POSTROUTING:IPv4',
+ 'NAT:mangle:IPv4',
+ 'NAT:mangle:IPv4',
+ 'NAT:mangle:IPv4',
+ ':$5()*&%\'"^$): :IPv4',
+ ]
+ provider.expects(:execute).with(['/sbin/iptables-save']).returns('
+# Generated by iptables-save v1.4.9 on Mon Jan 2 01:20:06 2012
+*raw
+:PREROUTING ACCEPT [12:1780]
+:OUTPUT ACCEPT [19:1159]
+:raw - [0:0]
+COMMIT
+# Completed on Mon Jan 2 01:20:06 2012
+# Generated by iptables-save v1.4.9 on Mon Jan 2 01:20:06 2012
+*mangle
+:PREROUTING ACCEPT [12:1780]
+:INPUT ACCEPT [12:1780]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [19:1159]
+:POSTROUTING ACCEPT [19:1159]
+:mangle - [0:0]
+COMMIT
+# Completed on Mon Jan 2 01:20:06 2012
+# Generated by iptables-save v1.4.9 on Mon Jan 2 01:20:06 2012
+*nat
+:PREROUTING ACCEPT [2242:639750]
+:OUTPUT ACCEPT [5176:326206]
+:POSTROUTING ACCEPT [5162:325382]
+COMMIT
+# Completed on Mon Jan 2 01:20:06 2012
+# Generated by iptables-save v1.4.9 on Mon Jan 2 01:20:06 2012
+*filter
+:INPUT ACCEPT [0:0]
+:FORWARD DROP [0:0]
+:OUTPUT ACCEPT [5673:420879]
+:$5()*&%\'"^$): - [0:0]
+COMMIT
+# Completed on Mon Jan 2 01:20:06 2012
+')
+ ip6tables = [
+ 'raw:PREROUTING:IPv6',
+ 'raw:OUTPUT:IPv6',
+ 'raw:ff:IPv6',
+ 'mangle:PREROUTING:IPv6',
+ 'mangle:INPUT:IPv6',
+ 'mangle:FORWARD:IPv6',
+ 'mangle:OUTPUT:IPv6',
+ 'mangle:POSTROUTING:IPv6',
+ 'mangle:ff:IPv6',
+ ':INPUT:IPv6',
+ ':FORWARD:IPv6',
+ ':OUTPUT:IPv6',
+ ':test:IPv6',
+ ]
+ provider.expects(:execute).with(['/sbin/ip6tables-save']).returns('
+# Generated by ip6tables-save v1.4.9 on Mon Jan 2 01:31:39 2012
+*raw
+:PREROUTING ACCEPT [2173:489241]
+:OUTPUT ACCEPT [0:0]
+:ff - [0:0]
+COMMIT
+# Completed on Mon Jan 2 01:31:39 2012
+# Generated by ip6tables-save v1.4.9 on Mon Jan 2 01:31:39 2012
+*mangle
+:PREROUTING ACCEPT [2301:518373]
+:INPUT ACCEPT [0:0]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:POSTROUTING ACCEPT [0:0]
+:ff - [0:0]
+COMMIT
+# Completed on Mon Jan 2 01:31:39 2012
+# Generated by ip6tables-save v1.4.9 on Mon Jan 2 01:31:39 2012
+*filter
+:INPUT ACCEPT [0:0]
+:FORWARD DROP [0:0]
+:OUTPUT ACCEPT [20:1292]
+:test - [0:0]
+COMMIT
+# Completed on Mon Jan 2 01:31:39 2012
+')
+ @all = ebtables + iptables + ip6tables
+ # IPv4 and IPv6 names also exist as resources {table}:{chain}:IP and {table}:{chain}:
+ iptables.each { |name| @all += [ name[0..-3], name[0..-5] ] }
+ ip6tables.each { |name| @all += [ name[0..-3], name[0..-5] ] }
+ end
+
+ it 'should have all in parsed resources' do
+ provider.instances.each do |resource|
+ @all.include?(resource.name)
+ end
+ end
+
+end
--- /dev/null
+#!/usr/bin/env rspec
+
+require 'spec_helper'
+
+firewallchain = Puppet::Type.type(:firewallchain)
+
+describe firewallchain do
+ before :each do
+ @class = firewallchain
+ @provider = stub 'provider'
+ @provider.stubs(:name).returns(:iptables_chain)
+ Puppet::Type::Firewallchain.stubs(:defaultprovider).returns @provider
+ @resource = @class.new({:name => ':INPUT:', :policy => :accept })
+ end
+
+ it 'should have :name be its namevar' do
+ @class.key_attributes.should == [:name]
+ end
+
+ describe ':name' do
+ {'' => ['INPUT','OUTPUT','FORWARD'],
+ 'NAT' => ['PREROUTING', 'POSTROUTING', 'OUTPUT'],
+ 'MANGLE' => [ 'PREROUTING', 'POSTROUTING', 'INPUT', 'FORWARD', 'OUTPUT' ],
+ 'FILTER' => ['INPUT','OUTPUT','FORWARD'],
+ 'RAW' => [ 'PREROUTING', 'OUTPUT'],
+ 'BROUTE' => ['BROUTING']
+ }.each_pair do |table, allowedinternalchains|
+ ['', 'IPv4', 'IPv6', 'IP', 'ethernet'].each do |protocol|
+ [ 'test', '$5()*&%\'"^$09):' ].each do |chainname|
+ name = "#{table}:#{chainname}:#{protocol}"
+ if table == 'NAT' && ['IPv6','','IP'].include?(protocol)
+ it "should fail #{name}" do
+ lambda { @resource[:name] = name }.should raise_error(Puppet::Error)
+ end
+ elsif protocol != 'ethernet' && table == 'BROUTE'
+ it "should fail #{name}" do
+ lambda { @resource[:name] = name }.should raise_error(Puppet::Error)
+ end
+ else
+ it "should accept name #{name}" do
+ @resource[:name] = name
+ @resource[:name].should == name
+ end
+ end
+ end # chainname
+ end # protocol
+
+ [ 'PREROUTING', 'POSTROUTING', 'BROUTING', 'INPUT', 'FORWARD', 'OUTPUT' ].each do |internalchain|
+ name = table + ':' + internalchain + ':'
+ if internalchain == 'BROUTING'
+ name += 'ethernet'
+ elsif table == 'NAT'
+ name += 'IPv4'
+ end
+ if allowedinternalchains.include? internalchain
+ it "should allow #{name}" do
+ @resource[:name] = name
+ @resource[:name].should == name
+ end
+ else
+ it "should fail #{name}" do
+ lambda { @resource[:name] = name }.should raise_error(Puppet::Error)
+ end
+ end
+ end # internalchain
+
+ end # table, allowedinternalchainnames
+
+ it 'should fail with invalid table names' do
+ lambda { @resource[:name] = 'wrongtablename:test:' }.should raise_error(Puppet::Error)
+ end
+
+ it 'should fail with invalid protocols names' do
+ lambda { @resource[:name] = ':test:IPv5' }.should raise_error(Puppet::Error)
+ end
+
+ end
+
+ describe ':policy' do
+
+ [:accept, :drop, :queue, :return].each do |policy|
+ it "should accept policy #{policy}" do
+ @resource[:policy] = policy
+ @resource[:policy].should == policy
+ end
+ end
+
+ it 'should fail when value is not recognized' do
+ lambda { @resource[:policy] = 'not valid' }.should raise_error(Puppet::Error)
+ end
+
+ [:accept, :drop, :queue, :return].each do |policy|
+ it "non-inbuilt chains should not accept policy #{policy}" do
+ lambda { @class.new({:name => ':testchain:', :policy => policy }) }.should raise_error(Puppet::Error)
+ end
+ it "non-inbuilt chains can accept policies on protocol = ethernet (policy #{policy})" do
+ @class.new({:name => ':testchain:ethernet', :policy => policy }).should be_instance_of(@provider)
+ end
+ end
+
+ end
+
+end